Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions crates/cext/src/transpiler/passes/sabre_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,13 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout(
1.0,
heuristic::SetScaling::Constant,
)),
Some(heuristic::LookaheadHeuristic::new(
0.5,
20,
heuristic::SetScaling::Size,
)),
Some(
heuristic::LookaheadHeuristic::new(
vec![0.5 / target.num_qubits.unwrap_or(20) as f64],
heuristic::SetScaling::Constant,
)
.expect("number of layers should be valid"),
),
Some(heuristic::DecayHeuristic::new(0.001, 5)),
Some(10 * target.num_qubits.unwrap() as usize),
1e-10,
Expand Down
12 changes: 12 additions & 0 deletions crates/circuit/src/nlayout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ impl NLayout {
}
}
}
impl ::std::ops::Index<VirtualQubit> for NLayout {
type Output = PhysicalQubit;
fn index(&self, index: VirtualQubit) -> &PhysicalQubit {
&self.virt_to_phys[index.index()]
}
}
impl ::std::ops::Index<PhysicalQubit> for NLayout {
type Output = VirtualQubit;
fn index(&self, index: PhysicalQubit) -> &VirtualQubit {
&self.phys_to_virt[index.index()]
}
}

pub fn nlayout(m: &Bound<PyModule>) -> PyResult<()> {
m.add_class::<NLayout>()?;
Expand Down
10 changes: 7 additions & 3 deletions crates/transpiler/src/passes/sabre/dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use qiskit_circuit::operations::Operation;
use qiskit_circuit::packed_instruction::PackedOperation;
use qiskit_circuit::{Qubit, VirtualQubit};

use crate::TranspilerError;

/// The type of a node in the Sabre interactions graph.
#[derive(Clone, Debug)]
pub enum InteractionKind {
Expand Down Expand Up @@ -54,16 +56,15 @@ impl InteractionKind {
return Ok(Self::Synchronize);
}
match qargs {
&[] | &[_] => Ok(Self::Synchronize),
// We're assuming that if the instruction has classical wires (like a `PyInstruction` or
// something), then it's still going to need routing, even though we can't see inside
// the operation to actually _know_ what it is.
&[left, right] => Ok(Self::TwoQ([
VirtualQubit::new(left.0),
VirtualQubit::new(right.0),
])),
// TODO: multi-q gates _should_ be an error, but for the initial patch, we're
// maintaining the historical behaviour of Sabre which is to ignore multi-q gates.
_ => Ok(Self::Synchronize),
_ => Err(SabreDAGError::MultiQ),
}
}
}
Expand All @@ -87,12 +88,15 @@ impl SabreNode {

#[derive(Error, Debug)]
pub enum SabreDAGError {
#[error("encountered a non-directive multi-qubit operation")]
MultiQ,
#[error("Python error: {0}")]
Python(#[from] PyErr),
}
impl From<SabreDAGError> for PyErr {
fn from(err: SabreDAGError) -> PyErr {
match err {
SabreDAGError::MultiQ => TranspilerError::new_err(err.to_string()),
SabreDAGError::Python(err) => err,
}
}
Expand Down
75 changes: 48 additions & 27 deletions crates/transpiler/src/passes/sabre/heuristic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,43 +79,60 @@ impl BasicHeuristic {
}
}

/// Define the characteristics of the lookahead heuristic. This is a sum of the physical distances
/// of every gate in the lookahead set, which is gates immediately after the front layer.
/// Define the characteristics of the lookahead heuristic.
#[pyclass(module = "qiskit._accelerate.sabre", frozen, from_py_object)]
#[derive(Clone, Copy, PartialEq, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub struct LookaheadHeuristic {
/// The relative weight of this heuristic. Typically this is defined relative to the
/// :class:`.BasicHeuristic`, which generally has its weight set to 1.0.
pub weight: f64,
/// Number of gates to consider in the heuristic.
pub size: usize,
/// Dynamic scaling of the heuristic weight depending on the lookahead set.
weights: Vec<f64>,
/// Dynamic scaling of the heuristic weight depending on the size of the layer.
pub scale: SetScaling,
}
impl_intopyobject_for_copy_pyclass!(LookaheadHeuristic);
impl LookaheadHeuristic {
/// Construct a new lookahead heuristic.
///
/// Fails if `weights` has $2^{16}$ or more elements (these are layers - 65,536 is too many!).
pub fn new(weights: Vec<f64>, scale: SetScaling) -> Option<Self> {
let _: u16 = weights.len().try_into().ok()?;
Some(Self { weights, scale })
}

pub fn num_layers(&self) -> u16 {
self.weights
.len()
.try_into()
.expect("constructor enforces sufficiently few layers")
}

#[inline]
pub fn weights(&self) -> &[f64] {
&self.weights
}
}
#[pymethods]
impl LookaheadHeuristic {
#[new]
pub fn new(weight: f64, size: usize, scale: SetScaling) -> Self {
Self {
weight,
size,
scale,
}
pub fn py_new(weights: Vec<f64>, scale: SetScaling) -> PyResult<Self> {
Self::new(weights, scale)
.ok_or_else(|| PyValueError::new_err("must have fewer than 65,536 layers"))
}

pub fn __getnewargs__(&self, py: Python) -> PyResult<Py<PyAny>> {
(self.weight, self.size, self.scale).into_py_any(py)
(self.weights.as_slice().into_pyobject(py)?, self.scale).into_py_any(py)
}

pub fn __eq__(&self, py: Python, other: Py<PyAny>) -> bool {
other.extract::<Self>(py).is_ok_and(|other| self == &other)
}

pub fn __repr__(&self, py: Python) -> PyResult<Py<PyAny>> {
let fmt = "LookaheadHeuristic(weight={!r}, size={!r}, scale={!r})";
let fmt = "LookaheadHeuristic(weights={!r}, scale={!r})";
PyString::new(py, fmt)
.call_method1("format", (self.weight, self.size, self.scale))?
.call_method1(
"format",
(self.weights.as_slice().into_pyobject(py)?, self.scale),
)?
.into_py_any(py)
}
}
Expand Down Expand Up @@ -159,6 +176,14 @@ impl DecayHeuristic {

/// A complete description of the heuristic that Sabre will use. See the individual elements for a
/// greater description.
///
/// .. note::
///
/// This is an internal Qiskit object, not a formal part of the API. You can use this for
/// fine-grained control over the Sabre heuristic, including for research, but beware that the
/// available options and configuration of it may change without warning in minor versions of
/// Qiskit. If you are doing research using this, be sure to pin the version of Qiskit in your
/// requirements.
#[pyclass(module = "qiskit._accelerate.sabre", frozen, eq, skip_from_py_object)]
#[derive(Clone, Debug, PartialEq)]
pub struct Heuristic {
Expand Down Expand Up @@ -205,7 +230,7 @@ impl Heuristic {
pub fn __getnewargs__(&self, py: Python) -> PyResult<Py<PyAny>> {
(
self.basic,
self.lookahead,
self.lookahead.clone(),
self.decay,
self.attempt_limit,
self.best_epsilon,
Expand All @@ -223,15 +248,11 @@ impl Heuristic {
}
}

/// Set the weight and extended-set size of the ``lookahead`` heuristic. The weight here
/// should typically be less than that of ``basic``.
pub fn with_lookahead(&self, weight: f64, size: usize, scale: SetScaling) -> Self {
/// Set the layer weights of the ``lookahead`` heuristic. The weight here should typically be
/// less than that of ``basic``. The number of weights dictates the number of layers.
pub fn with_lookahead(&self, weights: Vec<f64>, scale: SetScaling) -> Self {
Self {
lookahead: Some(LookaheadHeuristic {
weight,
size,
scale,
}),
lookahead: Some(LookaheadHeuristic { weights, scale }),
..self.clone()
}
}
Expand All @@ -256,7 +277,7 @@ impl Heuristic {
"format",
(
self.basic,
self.lookahead,
self.lookahead.clone(),
self.decay,
self.attempt_limit,
self.best_epsilon,
Expand Down
Loading
Loading