Skip to content

Commit

Permalink
Port standard instructions to Rust. (#13486)
Browse files Browse the repository at this point in the history
* Extend PackedOperation for standard instructions.

* WIP

* Compiling.

* Add missing module registration.

* Implement Operation::directive

* Add StandardInstructionType enum.

* Expose only StandardInstructionType to Python.

This way, we can also use this enum to tag the Python classes.

* Fix unit access.

* Remove no longer needed enum variant parens.

* Remove unused stuff.

* Fix up packed_instruction docs, add From<StandardInstruction>.

* Fix up imports.

* Improve comments in circuit_instruction.

* Fix typo.

* More doc fixes.

* Reorganize PackedInstruction mem layout to support 32 bit archs.

* Update layout description, cleanup.

* Revert "Update layout description, cleanup."

This reverts commit b6d8f92.

* Revert "Reorganize PackedInstruction mem layout to support 32 bit archs."

This reverts commit 6049498.

* Use bitfield-struct crate for PackedOperation.

Trying out a neat crate for Rust bitfields. The caveats are:

* Custom enums used in the bitfield must specify const funcs for bit
  conversion, and bytemuck's cast functions aren't const.
* The bitfield crate implements Clone for you, but does not seem to have
  a way to disable this. We can't rely on their clone, since for pointer
  types we need to allocate a new Box. To get around this,
  PackedOperation is a wrapper around an internal bitfield struct
  (rather than being a bitfield struct itself).

(Note: I'm not yet happy with this. Specifically, I think the
abstraction may be cleaner if OpBitField is defined entirely in terms of
raw native types and any reinterpretation / transmutation is done from
PackedOperation. Consider this a first pass.)

* Pivot.

* Compiling.

* Remove unused bytemuck stuff.

* Run format.

* Improve static assertion error messages.

* Clean up.

* Implement delay unit for ImmediateValue.

* Finish implementing PointerBits for 32 bit.

I can't easily test this on my own machine, but I think it'll work.

* More cleanup and documentation updates.

* Ensure subclasses of Measure are not treated as standard.

* Drop 32-bit support.

* Make StandardInstruction::Barrier hold a u32.

* Remove static assertion in favor of comment.

* Add notes on bitfield macro behavior to structs.

* Make immediate a union.

This is really what it should be here, since selecting one of its
members is inherently unsafe and something the caller should be
responsible for doing correctly.

* Rewrite to localize bit packing by kind.

* Rename 'standard_gate label.

* Add backup handling for PyInstruction compare.

This shouldn't happen, but if it does, we can handle it gracefully
like we do for StandardGate and PyGate.

* Use StandardInstruction in BarrierBeforeFinalMeasurements.

* Rename OperationRef::StandardGate.

* Replace Barrier uses.

* Remove _Pointer suffixes.

* Merge fixes.

* Avoid coercing int to float in Delay duration.

* Update Cargo.lock.

* Fix lint.

* Make impl_packable_pointer operation_type an expr.

* Move PackedOperation Drop to root mod.

Also uses codegen for From<Box<T>> explicitly for all T,
rather than a blanket impl on the private PackablePointer
trait to ensure proper docs are generated.

* Remove StandardGate pad.

* Add #[inline] in a few places.

Probably not necessary in most of these cases.
  • Loading branch information
kevinhartman authored Feb 5, 2025
1 parent 6274843 commit 73409fb
Show file tree
Hide file tree
Showing 25 changed files with 818 additions and 357 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ license = "Apache-2.0"
# Each crate can add on specific features freely as it inherits.
[workspace.dependencies]
bytemuck = "1.21"
bitfield-struct = "0.9.3"
indexmap.version = "2.7.1"
hashbrown.version = "0.14.5"
num-bigint = "0.4"
Expand Down
53 changes: 13 additions & 40 deletions crates/accelerate/src/barrier_before_final_measurement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use rustworkx_core::petgraph::stable_graph::NodeIndex;

use qiskit_circuit::circuit_instruction::ExtraInstructionAttributes;
use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType};
use qiskit_circuit::imports::BARRIER;
use qiskit_circuit::operations::{Operation, PyInstruction};
use qiskit_circuit::operations::{Operation, StandardInstruction};
use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation};
use qiskit_circuit::Qubit;

Expand Down Expand Up @@ -65,45 +64,19 @@ pub fn barrier_before_final_measurements(
res
})
.collect();
let new_barrier = BARRIER
.get_bound(py)
.call1((dag.num_qubits(), label.as_deref()))?;

let new_barrier_py_inst = PyInstruction {
qubits: dag.num_qubits() as u32,
clbits: 0,
params: 0,
op_name: "barrier".to_string(),
control_flow: false,
#[cfg(feature = "cache_pygates")]
instruction: new_barrier.clone().unbind(),
#[cfg(not(feature = "cache_pygates"))]
instruction: new_barrier.unbind(),
};
let qargs: Vec<Qubit> = (0..dag.num_qubits() as u32).map(Qubit).collect();
#[cfg(feature = "cache_pygates")]
{
dag.apply_operation_back(
py,
PackedOperation::from_instruction(Box::new(new_barrier_py_inst)),
qargs.as_slice(),
&[],
None,
ExtraInstructionAttributes::new(label, None, None, None),
Some(new_barrier.unbind()),
)?;
}
#[cfg(not(feature = "cache_pygates"))]
{
dag.apply_operation_back(
py,
PackedOperation::from_instruction(Box::new(new_barrier_py_inst)),
qargs.as_slice(),
&[],
None,
ExtraInstructionAttributes::new(label, None, None, None),
)?;
}
dag.apply_operation_back(
py,
PackedOperation::from_standard_instruction(StandardInstruction::Barrier(
dag.num_qubits() as u32
)),
qargs.as_slice(),
&[],
None,
ExtraInstructionAttributes::new(label, None, None, None),
#[cfg(feature = "cache_pygates")]
None,
)?;
for inst in final_packed_ops {
dag.push_back(py, inst)?;
}
Expand Down
23 changes: 7 additions & 16 deletions crates/accelerate/src/circuit_library/multi_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use qiskit_circuit::packed_instruction::PackedOperation;
use smallvec::{smallvec, SmallVec};

use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::operations::{Param, PyInstruction};
use qiskit_circuit::{imports, Clbit, Qubit};
use qiskit_circuit::operations::{Param, StandardInstruction};
use qiskit_circuit::{Clbit, Qubit};

use itertools::izip;

Expand Down Expand Up @@ -193,7 +193,7 @@ pub fn n_local(

// This struct can be used to yield barrier if insert_barriers is true, otherwise
// it returns an empty iterator. For conveniently injecting barriers in-between operations.
let maybe_barrier = MaybeBarrier::new(py, num_qubits, insert_barriers)?;
let maybe_barrier = MaybeBarrier::new(num_qubits, insert_barriers)?;

let packed_insts = (0..reps).flat_map(|layer| {
rotation_layer(
Expand Down Expand Up @@ -280,23 +280,14 @@ struct MaybeBarrier {
}

impl MaybeBarrier {
fn new(py: Python, num_qubits: u32, insert_barriers: bool) -> PyResult<Self> {
fn new(num_qubits: u32, insert_barriers: bool) -> PyResult<Self> {
if !insert_barriers {
Ok(Self { barrier: None })
} else {
let barrier_cls = imports::BARRIER.get_bound(py);
let py_barrier = barrier_cls.call1((num_qubits,))?;
let py_inst = PyInstruction {
qubits: num_qubits,
clbits: 0,
params: 0,
op_name: "barrier".to_string(),
control_flow: false,
instruction: py_barrier.into(),
};

let inst = (
py_inst.into(),
PackedOperation::from_standard_instruction(StandardInstruction::Barrier(
num_qubits,
)),
smallvec![],
(0..num_qubits).map(Qubit).collect(),
vec![] as Vec<Clbit>,
Expand Down
35 changes: 11 additions & 24 deletions crates/accelerate/src/circuit_library/pauli_evolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
use pyo3::prelude::*;
use pyo3::types::{PyList, PyString, PyTuple};
use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::operations::{multiply_param, radd_param, Param, PyInstruction, StandardGate};
use qiskit_circuit::operations;
use qiskit_circuit::operations::{multiply_param, radd_param, Param, StandardGate};
use qiskit_circuit::packed_instruction::PackedOperation;
use qiskit_circuit::{imports, Clbit, Qubit};
use qiskit_circuit::{Clbit, Qubit};
use smallvec::{smallvec, SmallVec};

// custom types for a more readable code
Expand Down Expand Up @@ -248,7 +249,14 @@ pub fn py_pauli_evolution(
indices.push(tuple.get_item(1)?.extract::<Vec<u32>>()?)
}

let barrier = get_barrier(py, num_qubits as u32);
let barrier = (
PackedOperation::from_standard_instruction(operations::StandardInstruction::Barrier(
num_qubits as u32,
)),
smallvec![],
(0..num_qubits as u32).map(Qubit).collect(),
vec![],
);

let evos = paulis.iter().enumerate().zip(indices).zip(times).flat_map(
|(((i, pauli), qubits), time)| {
Expand Down Expand Up @@ -328,24 +336,3 @@ fn cx_fountain(
)
}))
}

fn get_barrier(py: Python, num_qubits: u32) -> Instruction {
let barrier_cls = imports::BARRIER.get_bound(py);
let barrier = barrier_cls
.call1((num_qubits,))
.expect("Could not create Barrier Python-side");
let barrier_inst = PyInstruction {
qubits: num_qubits,
clbits: 0,
params: 0,
op_name: "barrier".to_string(),
control_flow: false,
instruction: barrier.into(),
};
(
barrier_inst.into(),
smallvec![],
(0..num_qubits).map(Qubit).collect(),
vec![],
)
}
35 changes: 11 additions & 24 deletions crates/accelerate/src/circuit_library/pauli_feature_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use pyo3::prelude::*;
use pyo3::types::PySequence;
use pyo3::types::PyString;
use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::imports;
use qiskit_circuit::operations::PyInstruction;
use qiskit_circuit::operations::{add_param, multiply_param, multiply_params, Param, StandardGate};
use qiskit_circuit::operations::{
add_param, multiply_param, multiply_params, Param, StandardGate, StandardInstruction,
};
use qiskit_circuit::packed_instruction::PackedOperation;
use qiskit_circuit::{Clbit, Qubit};
use smallvec::{smallvec, SmallVec};
Expand Down Expand Up @@ -78,7 +78,14 @@ pub fn pauli_feature_map(

// construct a Barrier object Python side to (possibly) add to the circuit
let packed_barrier = if insert_barriers {
Some(_get_barrier(py, feature_dimension)?)
Some((
PackedOperation::from_standard_instruction(StandardInstruction::Barrier(
feature_dimension,
)),
smallvec![],
(0..feature_dimension).map(Qubit).collect(),
vec![] as Vec<Clbit>,
))
} else {
None
};
Expand Down Expand Up @@ -244,23 +251,3 @@ fn _get_paulis(
},
)
}

/// Get a barrier object from Python space.
fn _get_barrier(py: Python, feature_dimension: u32) -> PyResult<Instruction> {
let barrier_cls = imports::BARRIER.get_bound(py);
let barrier = barrier_cls.call1((feature_dimension,))?;
let barrier_inst = PyInstruction {
qubits: feature_dimension,
clbits: 0,
params: 0,
op_name: "barrier".to_string(),
control_flow: false,
instruction: barrier.into(),
};
Ok((
barrier_inst.into(),
smallvec![],
(0..feature_dimension).map(Qubit).collect(),
vec![] as Vec<Clbit>,
))
}
33 changes: 24 additions & 9 deletions crates/accelerate/src/commutation_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,30 +56,45 @@ static SUPPORTED_OP: Lazy<HashSet<&str>> = Lazy::new(|| {
// E.g. RX is generated by X and 2-pi periodic, while CRX is generated by CX and 4-pi periodic.
static SUPPORTED_ROTATIONS: Lazy<HashMap<&str, (u8, Option<OperationRef>)>> = Lazy::new(|| {
HashMap::from([
("rx", (2, Some(OperationRef::Standard(StandardGate::XGate)))),
("ry", (2, Some(OperationRef::Standard(StandardGate::YGate)))),
("rz", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
("p", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
("u1", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
(
"rx",
(2, Some(OperationRef::StandardGate(StandardGate::XGate))),
),
(
"ry",
(2, Some(OperationRef::StandardGate(StandardGate::YGate))),
),
(
"rz",
(2, Some(OperationRef::StandardGate(StandardGate::ZGate))),
),
(
"p",
(2, Some(OperationRef::StandardGate(StandardGate::ZGate))),
),
(
"u1",
(2, Some(OperationRef::StandardGate(StandardGate::ZGate))),
),
("rxx", (2, None)), // None means the gate is in the commutation dictionary
("ryy", (2, None)),
("rzx", (2, None)),
("rzz", (2, None)),
(
"crx",
(4, Some(OperationRef::Standard(StandardGate::CXGate))),
(4, Some(OperationRef::StandardGate(StandardGate::CXGate))),
),
(
"cry",
(4, Some(OperationRef::Standard(StandardGate::CYGate))),
(4, Some(OperationRef::StandardGate(StandardGate::CYGate))),
),
(
"crz",
(4, Some(OperationRef::Standard(StandardGate::CZGate))),
(4, Some(OperationRef::StandardGate(StandardGate::CZGate))),
),
(
"cp",
(2, Some(OperationRef::Standard(StandardGate::CZGate))),
(2, Some(OperationRef::StandardGate(StandardGate::CZGate))),
),
])
});
Expand Down
6 changes: 3 additions & 3 deletions crates/accelerate/src/gate_direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ fn py_fix_direction_target(
];

// Take this path so Target can check for exact match of the parameterized gate's angle
if let OperationRef::Standard(std_gate) = inst.op.view() {
if let OperationRef::StandardGate(std_gate) = inst.op.view() {
match std_gate {
StandardGate::RXXGate
| StandardGate::RYYGate
Expand Down Expand Up @@ -309,7 +309,7 @@ where

// If the op has a pre-defined replacement - replace if the other direction is supported otherwise error
// If no pre-defined replacement for the op - if the other direction is supported error saying no pre-defined rule otherwise error saying op is not supported
if let OperationRef::Standard(std_gate) = packed_inst.op.view() {
if let OperationRef::StandardGate(std_gate) = packed_inst.op.view() {
match std_gate {
StandardGate::CXGate
| StandardGate::ECRGate
Expand Down Expand Up @@ -463,7 +463,7 @@ fn apply_operation_back(
) -> PyResult<()> {
dag.apply_operation_back(
py,
PackedOperation::from_standard(gate),
PackedOperation::from_standard_gate(gate),
qargs,
&[],
param,
Expand Down
2 changes: 1 addition & 1 deletion crates/accelerate/src/remove_identity_equiv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn remove_identity_equiv(

for (op_node, inst) in dag.op_nodes(false) {
match inst.op.view() {
OperationRef::Standard(gate) => {
OperationRef::StandardGate(gate) => {
let (dim, trace) = match gate {
StandardGate::RXGate | StandardGate::RYGate | StandardGate::RZGate => {
if let Param::Float(theta) = inst.params_view()[0] {
Expand Down
2 changes: 1 addition & 1 deletion crates/accelerate/src/synthesis/multi_controlled/mcmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub fn mcmt_v_chain(
.filter(|index| control_state & (1 << index) == 0)
.map(|index| {
Ok((
PackedOperation::from_standard(StandardGate::XGate),
PackedOperation::from_standard_gate(StandardGate::XGate),
smallvec![] as SmallVec<[Param; 3]>,
vec![Qubit::new(index)],
vec![] as Vec<Clbit>,
Expand Down
5 changes: 4 additions & 1 deletion crates/accelerate/src/target_transpiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,10 @@ impl Target {
};
let out_inst = match inst {
TargetOperation::Normal(op) => match op.operation.view() {
OperationRef::Standard(standard) => standard
OperationRef::StandardGate(standard) => standard
.create_py_op(py, Some(&op.params), &ExtraInstructionAttributes::default())?
.into_any(),
OperationRef::StandardInstruction(standard) => standard
.create_py_op(py, Some(&op.params), &ExtraInstructionAttributes::default())?
.into_any(),
OperationRef::Gate(gate) => gate.gate.clone_ref(py),
Expand Down
Loading

0 comments on commit 73409fb

Please sign in to comment.