Skip to content

Commit 7deafbf

Browse files
authored
Combine Python types in PackedInstructions (Qiskit#15277)
* Combine Python types in PackedInstructions This commit migrates the 3 python variants in PackedOperation to be a single variant. The 3 types PyGate, PyInstruction, and PyOperation are very similar and are only separate for typing reasons. This commit moves to only having a single struct and adding the typing information as an enum that wraps that struct representation. This is then propogated to the PackedInstruction type and move us from 3 separate fields in the struct to just a single one. This frees up more space for other operation types in the future. OperationRef keeps the distinct variants for Gate, Instruction, and Operation. This simplifies the required changes as most places interact with OperationRef instead of the owned types. The other motivation for this is as an early step towards fixing Qiskit#14240. This commit simplifies and consolidates the places that explicitly wrap python usage in the circuit crate. This will make it easier to conditionally compile things when we start to do that. The other option considered was also moving to a single struct and setting a `py_op_type` field inside the struct that was an enum to differentiate the Python type. However, doing this inside the struct would require significant changes to the usage of Python types in a circuit. Instead of matching on the type off of OperationRef it would need to be a double mapping first on OperationRef then on `py_op_type` field. Using the outer enum prevents this and makes it a bit easier to match in places. * Add missing alignment on PyOperationTypes As we insert PyOperationTypes in the PackedInstruction we need to ensure the alignment so that there are free bits to put the discriminant of the PackedOperation in the u64. This was overlooked in earlier versions of this PR branch and would have caused issues on other platforms potentially. * Fix typo in rebase
1 parent 95c22e0 commit 7deafbf

23 files changed

Lines changed: 243 additions & 360 deletions

crates/accelerate/src/twirling.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
use std::f64::consts::PI;
1414

15-
use crate::QiskitError;
1615
use hashbrown::HashMap;
1716
use ndarray::ArrayView2;
1817
use ndarray::linalg::kron;
@@ -29,6 +28,9 @@ use qiskit_circuit::instruction::Instruction;
2928
use qiskit_circuit::operations::StandardGate::{I, X, Y, Z};
3029
use qiskit_circuit::operations::{Operation, OperationRef, Param, StandardGate};
3130
use qiskit_circuit::packed_instruction::PackedInstruction;
31+
32+
use crate::QiskitError;
33+
3234
use qiskit_circuit::{BlocksMode, NoBlocks, VarsMode};
3335
use qiskit_transpiler::passes::run_optimize_1q_gates_decomposition;
3436
use qiskit_transpiler::target::Target;

crates/circuit/src/circuit_data.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ use crate::imports::{ANNOTATED_OPERATION, QUANTUM_CIRCUIT};
2727
use crate::interner::{Interned, InternedMap, Interner};
2828
use crate::object_registry::{ObjectRegistry, ObjectRegistryError};
2929
use crate::operations::{
30-
ControlFlow, ControlFlowView, Operation, OperationRef, Param, PythonOperation, StandardGate,
30+
ControlFlow, ControlFlowView, Operation, OperationRef, Param, PyOperationTypes,
31+
PythonOperation, StandardGate,
3132
};
3233
use crate::packed_instruction::{PackedInstruction, PackedOperation};
3334
use crate::parameter::parameter_expression::{ParameterError, ParameterExpression};
@@ -799,13 +800,16 @@ impl CircuitData {
799800
let memo = PyDict::new(py);
800801
for inst in &self.data {
801802
let new_op = match inst.op.view() {
803+
OperationRef::Gate(gate) => {
804+
PyOperationTypes::Gate(gate.py_deepcopy(py, Some(&memo))?).into()
805+
}
802806
OperationRef::ControlFlow(cf) => cf.clone().into(),
803-
OperationRef::Gate(gate) => gate.py_deepcopy(py, Some(&memo))?.into(),
804807
OperationRef::Instruction(instruction) => {
805-
instruction.py_deepcopy(py, Some(&memo))?.into()
808+
PyOperationTypes::Instruction(instruction.py_deepcopy(py, Some(&memo))?)
809+
.into()
806810
}
807811
OperationRef::Operation(operation) => {
808-
operation.py_deepcopy(py, Some(&memo))?.into()
812+
PyOperationTypes::Operation(operation.py_deepcopy(py, Some(&memo))?).into()
809813
}
810814
OperationRef::StandardGate(gate) => gate.into(),
811815
OperationRef::StandardInstruction(instruction) => instruction.into(),
@@ -825,10 +829,14 @@ impl CircuitData {
825829
} else if copy_instructions {
826830
for inst in &self.data {
827831
let new_op = match inst.op.view() {
832+
OperationRef::Gate(gate) => PyOperationTypes::Gate(gate.py_copy(py)?).into(),
833+
OperationRef::Instruction(instruction) => {
834+
PyOperationTypes::Instruction(instruction.py_copy(py)?).into()
835+
}
836+
OperationRef::Operation(operation) => {
837+
PyOperationTypes::Operation(operation.py_copy(py)?).into()
838+
}
828839
OperationRef::ControlFlow(cf) => cf.clone().into(),
829-
OperationRef::Gate(gate) => gate.py_copy(py)?.into(),
830-
OperationRef::Instruction(instruction) => instruction.py_copy(py)?.into(),
831-
OperationRef::Operation(operation) => operation.py_copy(py)?.into(),
832840
OperationRef::StandardGate(gate) => gate.into(),
833841
OperationRef::StandardInstruction(instruction) => instruction.into(),
834842
OperationRef::Unitary(unitary) => unitary.clone().into(),

crates/circuit/src/circuit_instruction.rs

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::imports::{CONTROLLED_GATE, GATE, INSTRUCTION, OPERATION, WARNINGS_WAR
2929
use crate::instruction::{Instruction, Parameters, create_py_op};
3030
use crate::operations::{
3131
ArrayType, BoxDuration, ControlFlow, ControlFlowInstruction, ControlFlowType, Operation,
32-
OperationRef, Param, PauliProductMeasurement, PyGate, PyInstruction, PyOperation, StandardGate,
32+
OperationRef, Param, PauliProductMeasurement, PyInstruction, PyOperationTypes, StandardGate,
3333
StandardInstruction, StandardInstructionType, UnitaryGate,
3434
};
3535
use crate::packed_instruction::PackedOperation;
@@ -256,7 +256,7 @@ impl CircuitInstruction {
256256
match self.operation.view() {
257257
OperationRef::StandardGate(standard) => Ok(standard.num_ctrl_qubits() != 0),
258258
OperationRef::Gate(gate) => gate
259-
.gate
259+
.instruction
260260
.bind(py)
261261
.is_instance(CONTROLLED_GATE.get_bound(py)),
262262
_ => Ok(false),
@@ -868,13 +868,15 @@ impl<'a, 'py, T: CircuitBlock> FromPyObject<'a, 'py> for OperationFromPython<T>
868868

869869
if ob_type.is_subclass(GATE.get_bound(py))? {
870870
let params = get_params()?;
871-
let operation = PackedOperation::from_gate(Box::new(PyGate {
872-
qubits: ob.getattr(intern!(py, "num_qubits"))?.extract()?,
873-
clbits: 0,
874-
params: params.len()? as u32,
875-
op_name: ob.getattr(intern!(py, "name"))?.extract()?,
876-
gate: ob.to_owned().unbind(),
877-
}));
871+
let operation = PackedOperation::from_py_operation(Box::new(PyOperationTypes::Gate(
872+
PyInstruction {
873+
qubits: ob.getattr(intern!(py, "num_qubits"))?.extract()?,
874+
clbits: 0,
875+
params: params.len()? as u32,
876+
op_name: ob.getattr(intern!(py, "name"))?.extract()?,
877+
instruction: ob.to_owned().unbind(),
878+
},
879+
)));
878880
let params = extract_params(operation.view(), &params)?;
879881
return Ok(OperationFromPython {
880882
operation,
@@ -884,13 +886,15 @@ impl<'a, 'py, T: CircuitBlock> FromPyObject<'a, 'py> for OperationFromPython<T>
884886
}
885887
if ob_type.is_subclass(INSTRUCTION.get_bound(py))? {
886888
let params = get_params()?;
887-
let operation = PackedOperation::from_instruction(Box::new(PyInstruction {
888-
qubits: ob.getattr(intern!(py, "num_qubits"))?.extract()?,
889-
clbits: ob.getattr(intern!(py, "num_clbits"))?.extract()?,
890-
params: params.len()? as u32,
891-
op_name: ob.getattr(intern!(py, "name"))?.extract()?,
892-
instruction: ob.to_owned().unbind(),
893-
}));
889+
let operation = PackedOperation::from_py_operation(Box::new(
890+
PyOperationTypes::Instruction(PyInstruction {
891+
qubits: ob.getattr(intern!(py, "num_qubits"))?.extract()?,
892+
clbits: ob.getattr(intern!(py, "num_clbits"))?.extract()?,
893+
params: params.len()? as u32,
894+
op_name: ob.getattr(intern!(py, "name"))?.extract()?,
895+
instruction: ob.to_owned().unbind(),
896+
}),
897+
));
894898
let params = extract_params(operation.view(), &params)?;
895899
return Ok(OperationFromPython {
896900
operation,
@@ -900,13 +904,15 @@ impl<'a, 'py, T: CircuitBlock> FromPyObject<'a, 'py> for OperationFromPython<T>
900904
}
901905
if ob_type.is_subclass(OPERATION.get_bound(py))? {
902906
let params = get_params()?;
903-
let operation = PackedOperation::from_operation(Box::new(PyOperation {
904-
qubits: ob.getattr(intern!(py, "num_qubits"))?.extract()?,
905-
clbits: ob.getattr(intern!(py, "num_clbits"))?.extract()?,
906-
params: params.len()? as u32,
907-
op_name: ob.getattr(intern!(py, "name"))?.extract()?,
908-
operation: ob.to_owned().unbind(),
909-
}));
907+
let operation = PackedOperation::from_py_operation(Box::new(
908+
PyOperationTypes::Operation(PyInstruction {
909+
qubits: ob.getattr(intern!(py, "num_qubits"))?.extract()?,
910+
clbits: ob.getattr(intern!(py, "num_clbits"))?.extract()?,
911+
params: params.len()? as u32,
912+
op_name: ob.getattr(intern!(py, "name"))?.extract()?,
913+
instruction: ob.to_owned().unbind(),
914+
}),
915+
));
910916
let params = extract_params(operation.view(), &params)?;
911917
return Ok(OperationFromPython {
912918
operation,

crates/circuit/src/converters.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::bit::{ShareableClbit, ShareableQubit};
1818
use crate::circuit_data::{CircuitData, CircuitDataError, CircuitVar};
1919
use crate::dag_circuit::DAGIdentifierInfo;
2020
use crate::dag_circuit::{DAGCircuit, NodeType};
21-
use crate::operations::{OperationRef, PythonOperation};
21+
use crate::operations::{OperationRef, PyOperationTypes, PythonOperation};
2222
use crate::{Clbit, Qubit};
2323

2424
/// An extractable representation of a QuantumCircuit reserved only for
@@ -134,13 +134,20 @@ pub fn dag_to_circuit(
134134
let op = match instr.op.view() {
135135
OperationRef::ControlFlow(cf) => cf.clone().into(),
136136
OperationRef::Gate(gate) => {
137-
Python::attach(|py| gate.py_deepcopy(py, None))?.into()
137+
PyOperationTypes::Gate(Python::attach(|py| gate.py_deepcopy(py, None))?)
138+
.into()
138139
}
139140
OperationRef::Instruction(instruction) => {
140-
Python::attach(|py| instruction.py_deepcopy(py, None))?.into()
141+
PyOperationTypes::Instruction(Python::attach(|py| {
142+
instruction.py_deepcopy(py, None)
143+
})?)
144+
.into()
141145
}
142146
OperationRef::Operation(operation) => {
143-
Python::attach(|py| operation.py_deepcopy(py, None))?.into()
147+
PyOperationTypes::Operation(Python::attach(|py| {
148+
operation.py_deepcopy(py, None)
149+
})?)
150+
.into()
144151
}
145152
OperationRef::StandardGate(gate) => gate.into(),
146153
OperationRef::StandardInstruction(instruction) => instruction.into(),

crates/circuit/src/dag_circuit.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ use crate::interner::{Interned, InternedMap, Interner};
3333
use crate::object_registry::ObjectRegistry;
3434
use crate::operations::{
3535
ArrayType, BoxDuration, Condition, ControlFlow, ControlFlowInstruction, ControlFlowView,
36-
Operation, OperationRef, Param, PythonOperation, StandardGate, StandardInstruction,
37-
SwitchTarget,
36+
Operation, OperationRef, Param, PyOperationTypes, PythonOperation, StandardGate,
37+
StandardInstruction, SwitchTarget,
3838
};
3939
use crate::packed_instruction::{PackedInstruction, PackedOperation};
4040
use crate::parameter::parameter_expression::ParameterExpression;
@@ -2545,21 +2545,26 @@ impl DAGCircuit {
25452545
&& check_args())
25462546
}
25472547
[OperationRef::Gate(op1), OperationRef::Gate(op2)] => {
2548-
Ok(op1.gate.bind(py).eq(&op2.gate)? && check_args())
2548+
Ok(op1.instruction.bind(py).eq(&op2.instruction)?
2549+
&& check_args())
25492550
}
25502551
[OperationRef::Operation(op1), OperationRef::Operation(op2)] => {
2551-
Ok(op1.operation.bind(py).eq(&op2.operation)? && check_args())
2552+
Ok(op1.instruction.bind(py).eq(&op2.instruction)?
2553+
&& check_args())
25522554
}
25532555
// Handle the edge case where we end up with a Python object and a standard
25542556
// gate/instruction.
25552557
// This typically only happens if we have a ControlledGate in Python
25562558
// and we have mutable state set.
25572559
[OperationRef::StandardGate(_), OperationRef::Gate(op2)] => {
2558-
Ok(slf.unpack_py_op(py, inst1)?.bind(py).eq(&op2.gate)?
2560+
Ok(slf.unpack_py_op(py, inst1)?.bind(py).eq(&op2.instruction)?
25592561
&& check_args())
25602562
}
25612563
[OperationRef::Gate(op1), OperationRef::StandardGate(_)] => {
2562-
Ok(other.unpack_py_op(py, inst2)?.bind(py).eq(&op1.gate)?
2564+
Ok(other
2565+
.unpack_py_op(py, inst2)?
2566+
.bind(py)
2567+
.eq(&op1.instruction)?
25632568
&& check_args())
25642569
}
25652570
[
@@ -2815,13 +2820,13 @@ impl DAGCircuit {
28152820
) => Ok(left == right),
28162821
(OperationRef::Unitary(left), OperationRef::Unitary(right)) => Ok(left == right),
28172822
(OperationRef::Gate(left), OperationRef::Gate(right)) => {
2818-
Python::attach(|py| left.gate.bind(py).eq(&right.gate))
2823+
Python::attach(|py| left.instruction.bind(py).eq(&right.instruction))
28192824
}
28202825
(OperationRef::Instruction(left), OperationRef::Instruction(right)) => {
28212826
Python::attach(|py| left.instruction.bind(py).eq(&right.instruction))
28222827
}
28232828
(OperationRef::Operation(left), OperationRef::Operation(right)) => {
2824-
Python::attach(|py| left.operation.bind(py).eq(&right.operation))
2829+
Python::attach(|py| left.instruction.bind(py).eq(&right.instruction))
28252830
}
28262831
_ => Ok(false),
28272832
}
@@ -7858,13 +7863,20 @@ impl DAGCircuit {
78587863
match instr.op.view() {
78597864
OperationRef::ControlFlow(cf) => cf.clone().into(),
78607865
OperationRef::Gate(gate) => {
7861-
Python::attach(|py| gate.py_deepcopy(py, None))?.into()
7866+
PyOperationTypes::Gate(Python::attach(|py| gate.py_deepcopy(py, None))?)
7867+
.into()
78627868
}
78637869
OperationRef::Instruction(instruction) => {
7864-
Python::attach(|py| instruction.py_deepcopy(py, None))?.into()
7870+
PyOperationTypes::Instruction(Python::attach(|py| {
7871+
instruction.py_deepcopy(py, None)
7872+
})?)
7873+
.into()
78657874
}
78667875
OperationRef::Operation(operation) => {
7867-
Python::attach(|py| operation.py_deepcopy(py, None))?.into()
7876+
PyOperationTypes::Operation(Python::attach(|py| {
7877+
operation.py_deepcopy(py, None)
7878+
})?)
7879+
.into()
78687880
}
78697881
OperationRef::StandardGate(gate) => gate.into(),
78707882
OperationRef::StandardInstruction(instruction) => instruction.into(),

crates/circuit/src/dag_node.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::sync::OnceLock;
1717
use crate::TupleLikeArg;
1818
use crate::circuit_data::CircuitData;
1919
use crate::circuit_instruction::{CircuitInstruction, OperationFromPython, extract_params};
20-
use crate::operations::{Operation, OperationRef, Param, PythonOperation};
20+
use crate::operations::{Operation, OperationRef, Param, PyOperationTypes, PythonOperation};
2121

2222
use ahash::AHasher;
2323
use approx::relative_eq;
@@ -255,9 +255,15 @@ impl DAGOpNode {
255255
if deepcopy {
256256
instruction.operation = match instruction.operation.view() {
257257
OperationRef::ControlFlow(cf) => cf.clone().into(),
258-
OperationRef::Gate(gate) => gate.py_deepcopy(py, None)?.into(),
259-
OperationRef::Instruction(instruction) => instruction.py_deepcopy(py, None)?.into(),
260-
OperationRef::Operation(operation) => operation.py_deepcopy(py, None)?.into(),
258+
OperationRef::Gate(gate) => {
259+
PyOperationTypes::Gate(gate.py_deepcopy(py, None)?).into()
260+
}
261+
OperationRef::Instruction(instruction) => {
262+
PyOperationTypes::Instruction(instruction.py_deepcopy(py, None)?).into()
263+
}
264+
OperationRef::Operation(operation) => {
265+
PyOperationTypes::Operation(operation.py_deepcopy(py, None)?).into()
266+
}
261267
OperationRef::StandardGate(gate) => gate.into(),
262268
OperationRef::StandardInstruction(instruction) => instruction.into(),
263269
OperationRef::Unitary(unitary) => unitary.clone().into(),
@@ -301,12 +307,16 @@ impl DAGOpNode {
301307
Ok(CircuitInstruction {
302308
operation: if deepcopy {
303309
match self.instruction.operation.view() {
304-
OperationRef::ControlFlow(cf) => cf.clone().into(),
305-
OperationRef::Gate(gate) => gate.py_deepcopy(py, None)?.into(),
310+
OperationRef::Gate(gate) => {
311+
PyOperationTypes::Gate(gate.py_deepcopy(py, None)?).into()
312+
}
306313
OperationRef::Instruction(instruction) => {
307-
instruction.py_deepcopy(py, None)?.into()
314+
PyOperationTypes::Instruction(instruction.py_deepcopy(py, None)?).into()
315+
}
316+
OperationRef::Operation(operation) => {
317+
PyOperationTypes::Operation(operation.py_deepcopy(py, None)?).into()
308318
}
309-
OperationRef::Operation(operation) => operation.py_deepcopy(py, None)?.into(),
319+
OperationRef::ControlFlow(cf) => cf.clone().into(),
310320
OperationRef::StandardGate(gate) => gate.into(),
311321
OperationRef::StandardInstruction(instruction) => instruction.into(),
312322
OperationRef::Unitary(unitary) => unitary.clone().into(),

crates/circuit/src/instruction.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ pub fn create_py_op(
186186
OperationRef::StandardInstruction(instruction) => {
187187
instruction.create_py_op(py, params.map(|p| p.unwrap_params()), label)
188188
}
189-
OperationRef::Gate(gate) => Ok(gate.gate.clone_ref(py)),
189+
OperationRef::Gate(gate) => Ok(gate.instruction.clone_ref(py)),
190190
OperationRef::Instruction(instruction) => Ok(instruction.instruction.clone_ref(py)),
191-
OperationRef::Operation(operation) => Ok(operation.operation.clone_ref(py)),
191+
OperationRef::Operation(operation) => Ok(operation.instruction.clone_ref(py)),
192192
OperationRef::Unitary(unitary) => unitary.create_py_op(py, label),
193193
}
194194
}

0 commit comments

Comments
 (0)