diff --git a/crates/accelerate/src/circuit_duration.rs b/crates/accelerate/src/circuit_duration.rs index 388415874108..69332ec8ec86 100644 --- a/crates/accelerate/src/circuit_duration.rs +++ b/crates/accelerate/src/circuit_duration.rs @@ -13,7 +13,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, Wire}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit, Wire}; use qiskit_circuit::operations::{DelayUnit, Operation, OperationRef, Param, StandardInstruction}; use qiskit_transpiler::target::Target; @@ -25,7 +25,12 @@ use rustworkx_core::petgraph::stable_graph::StableDiGraph; use rustworkx_core::petgraph::visit::{EdgeRef, IntoEdgeReferences}; /// Estimate the duration of a scheduled circuit in seconds -#[pyfunction] +#[pyfunction(name = "compute_estimated_duration")] +fn py_compute_estimated_duration(dag: &PyDAGCircuit, target: &Target) -> PyResult { + compute_estimated_duration(dag.try_read()?, target) +} + +/// Estimate the duration of a scheduled circuit in seconds pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> PyResult { let dt = target.dt; @@ -98,6 +103,6 @@ pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> P } pub fn compute_duration(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(compute_estimated_duration))?; + m.add_wrapped(wrap_pyfunction!(py_compute_estimated_duration))?; Ok(()) } diff --git a/crates/accelerate/src/twirling.rs b/crates/accelerate/src/twirling.rs index b212bdba1217..170fcf789c1a 100644 --- a/crates/accelerate/src/twirling.rs +++ b/crates/accelerate/src/twirling.rs @@ -299,7 +299,7 @@ fn generate_twirled_circuit( } } if let Some(optimizer_target) = optimizer_target { - let mut dag = DAGCircuit::from_circuit_data(&out_circ, false, None, None, None, None)?; + let mut dag = DAGCircuit::from_circuit_data(&out_circ, false, None, None)?; let state = Optimize1qGatesDecompositionState::new( optimizer_target.num_qubits.unwrap_or(0) as usize, ); diff --git a/crates/cext/src/circuit.rs b/crates/cext/src/circuit.rs index 22429250dd9c..6066bc2bbe91 100644 --- a/crates/cext/src/circuit.rs +++ b/crates/cext/src/circuit.rs @@ -2542,7 +2542,7 @@ pub unsafe extern "C" fn qk_circuit_to_dag(circuit: *const CircuitData) -> *mut // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { const_ptr_as_ref(circuit) }; - let dag = DAGCircuit::from_circuit_data(circuit, true, None, None, None, None) + let dag = DAGCircuit::from_circuit_data(circuit, true, None, None) .expect("Error occurred while converting CircuitData to DAGCircuit"); Box::into_raw(Box::new(dag)) diff --git a/crates/cext/src/control_flow.rs b/crates/cext/src/control_flow.rs index e57e60905ac8..39e549e5d302 100644 --- a/crates/cext/src/control_flow.rs +++ b/crates/cext/src/control_flow.rs @@ -1619,7 +1619,7 @@ pub unsafe extern "C" fn qk_control_flow_switch_case_labels_clear(labels: *mut C if !labels.labels.is_null() && labels.num_labels > 0 { drop(unsafe { // SAFETY: Per document unsafe, label is a valid CSwitchCaseLabels - Box::from_raw(std::slice::from_raw_parts_mut( + Box::from_raw(std::ptr::slice_from_raw_parts_mut( labels.labels as *mut u64, labels.num_labels, )) diff --git a/crates/cext/src/dag.rs b/crates/cext/src/dag.rs index fc86cff4cb97..bd9a11cc294e 100644 --- a/crates/cext/src/dag.rs +++ b/crates/cext/src/dag.rs @@ -18,7 +18,7 @@ use smallvec::smallvec; use crate::exit_codes::ExitCode; use qiskit_circuit::bit::{ClassicalRegister, QuantumRegister}; use qiskit_circuit::circuit_data::CircuitData; -use qiskit_circuit::dag_circuit::{DAGCircuit, DAGError, NodeIndex, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, DAGError, NodeIndex, NodeType, PyDAGCircuit}; use qiskit_circuit::instruction::Parameters; use qiskit_circuit::operations::{ ArrayType, Operation, OperationRef, Param, StandardGate, StandardInstruction, UnitaryGate, @@ -1867,7 +1867,7 @@ pub unsafe extern "C" fn qk_dag_to_python(dag: *mut DAGCircuit) -> *mut ::pyo3:: let py = unsafe { ::pyo3::Python::assume_attached() }; // SAFETY: per documentation, `dag` points to owned and valid data. let dag = unsafe { Box::from_raw(dag) }; - match ::pyo3::Bound::new(py, *dag) { + match ::pyo3::Bound::new(py, PyDAGCircuit::from(*dag)) { Ok(ob) => ob.into_ptr(), Err(e) => { e.restore(py); @@ -1904,7 +1904,13 @@ pub unsafe extern "C" fn qk_dag_borrow_from_python( ) -> *mut DAGCircuit { // SAFETY: per documentation, we are attached to a Python interpreter, and `ob` points to a // valid PyObject. - unsafe { crate::py::borrow_mut(::pyo3::Python::assume_attached(), ob) } + unsafe { + crate::py::borrow_map_mut::( + ::pyo3::Python::assume_attached(), + ob, + |_py, dag| dag.try_write(), + ) + } } /// @ingroup QkDag @@ -1938,6 +1944,6 @@ pub unsafe extern "C" fn qk_dag_convert_from_python( // SAFETY: per documentation, we are attached to a Python interpreter, `object` is a valid // pointer to a PyObject, and `address` points to enough space to write a pointer. unsafe { - crate::py::convert_mut::(::pyo3::Python::assume_attached(), object, address) + crate::py::convert_mut::(::pyo3::Python::assume_attached(), object, address) } } diff --git a/crates/cext/src/transpiler/passes/basis_translator.rs b/crates/cext/src/transpiler/passes/basis_translator.rs index bb4d3e986291..673ffa184ed1 100644 --- a/crates/cext/src/transpiler/passes/basis_translator.rs +++ b/crates/cext/src/transpiler/passes/basis_translator.rs @@ -68,7 +68,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_basis_translator( // SAFETY: Per documentation, the pointer is non-null and aligned. let target = unsafe { const_ptr_as_ref(target) }; - let dag = DAGCircuit::from_circuit_data(circ_from_ptr, false, None, None, None, None) + let dag = DAGCircuit::from_circuit_data(circ_from_ptr, false, None, None) .expect("Circuit to DAG conversion failed"); let mut equiv_lib = generate_standard_equivalence_library(); diff --git a/crates/cext/src/transpiler/passes/commutative_cancellation.rs b/crates/cext/src/transpiler/passes/commutative_cancellation.rs index 7dc17043ea50..99e9b724c215 100644 --- a/crates/cext/src/transpiler/passes/commutative_cancellation.rs +++ b/crates/cext/src/transpiler/passes/commutative_cancellation.rs @@ -76,7 +76,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_commutative_cancellation( "Invalid value provided for approximation degree, only NAN or values between 0.0 and 1.0 inclusive are valid" ); } - let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(_) => panic!("Internal circuit -> DAG conversion failed"), }; diff --git a/crates/cext/src/transpiler/passes/consolidate_blocks.rs b/crates/cext/src/transpiler/passes/consolidate_blocks.rs index 919b0d2898f6..d4ced681b77c 100644 --- a/crates/cext/src/transpiler/passes/consolidate_blocks.rs +++ b/crates/cext/src/transpiler/passes/consolidate_blocks.rs @@ -52,7 +52,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_consolidate_blocks( } else { approximation_degree }; - let mut circ_as_dag = DAGCircuit::from_circuit_data(circuit, true, None, None, None, None) + let mut circ_as_dag = DAGCircuit::from_circuit_data(circuit, true, None, None) .expect("Error while converting from CircuitData to DAGCircuit."); // Call the pass diff --git a/crates/cext/src/transpiler/passes/convert_to_pauli_rotations.rs b/crates/cext/src/transpiler/passes/convert_to_pauli_rotations.rs index bd70d020f0f0..7c0a47ed312b 100644 --- a/crates/cext/src/transpiler/passes/convert_to_pauli_rotations.rs +++ b/crates/cext/src/transpiler/passes/convert_to_pauli_rotations.rs @@ -10,7 +10,10 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use qiskit_circuit::{circuit_data::CircuitData, dag_circuit::DAGCircuit}; +use qiskit_circuit::{ + circuit_data::CircuitData, + dag_circuit::{DAGCircuit, PyDAGCircuit}, +}; use qiskit_transpiler::passes::py_convert_to_pauli_rotations; use crate::pointers::mut_ptr_as_ref; @@ -35,11 +38,16 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_convert_to_pauli_rotation ) { // SAFETY: The user guarantees the pointer is safe to read. let circuit = unsafe { mut_ptr_as_ref(circuit) }; - let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(_) => panic!("Internal Circuit -> DAG conversion failed"), }; - let out = py_convert_to_pauli_rotations(&dag).expect("Failed running PBC conversion."); + let as_py_dag: PyDAGCircuit = dag.into(); + let out = py_convert_to_pauli_rotations(&as_py_dag).expect("Failed running PBC conversion."); // If a DAG is returned, the circuit has been modified. Else just leave it as is. - *circuit = CircuitData::from_dag_ref(&out).expect("Internal DAG -> Circuit conversion failed"); + *circuit = CircuitData::from_dag_ref( + out.try_read() + .expect("Nothing else should be reading the dag"), + ) + .expect("Internal DAG -> Circuit conversion failed"); } diff --git a/crates/cext/src/transpiler/passes/elide_permutations.rs b/crates/cext/src/transpiler/passes/elide_permutations.rs index 65b6c162f54f..dd4e163390dd 100644 --- a/crates/cext/src/transpiler/passes/elide_permutations.rs +++ b/crates/cext/src/transpiler/passes/elide_permutations.rs @@ -41,7 +41,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_elide_permutations( ) -> *mut TranspileLayout { // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; - let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(_e) => panic!("Internal circuit to DAG conversion failed."), }; diff --git a/crates/cext/src/transpiler/passes/gate_direction.rs b/crates/cext/src/transpiler/passes/gate_direction.rs index a602f9127aac..72654a8ea4fa 100644 --- a/crates/cext/src/transpiler/passes/gate_direction.rs +++ b/crates/cext/src/transpiler/passes/gate_direction.rs @@ -38,7 +38,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_check_gate_direction( let circuit = unsafe { const_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + let dag = DAGCircuit::from_circuit_data(circuit, false, None, None) .expect("Circuit to DAG conversion failed"); check_direction_target(&dag, target).expect("Unexpected error occurred in CheckGateDirection") @@ -64,7 +64,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_gate_direction( let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None) .expect("Circuit to DAG conversion failed"); fix_direction_target(&mut dag, target).expect("Unexpected error occurred in GateDirection"); diff --git a/crates/cext/src/transpiler/passes/inverse_cancellation.rs b/crates/cext/src/transpiler/passes/inverse_cancellation.rs index 343644fd0143..bb39c791a9cc 100644 --- a/crates/cext/src/transpiler/passes/inverse_cancellation.rs +++ b/crates/cext/src/transpiler/passes/inverse_cancellation.rs @@ -67,7 +67,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_inverse_cancellation( ) { // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; - let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(_) => panic!("Internal Circuit -> DAG conversion failed"), }; diff --git a/crates/cext/src/transpiler/passes/litinski_transformation.rs b/crates/cext/src/transpiler/passes/litinski_transformation.rs index f21bfcc2ca5e..8f2629a7ad08 100644 --- a/crates/cext/src/transpiler/passes/litinski_transformation.rs +++ b/crates/cext/src/transpiler/passes/litinski_transformation.rs @@ -42,7 +42,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_litinski_transformation( ) { // SAFETY: The user guarantees the pointer is safe to read. let circuit = unsafe { mut_ptr_as_ref(circuit) }; - let dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + let dag = DAGCircuit::from_circuit_data(circuit, false, None, None) .expect("Internal Circuit -> DAG conversion failed"); let maybe_out = diff --git a/crates/cext/src/transpiler/passes/optimize_1q_sequences.rs b/crates/cext/src/transpiler/passes/optimize_1q_sequences.rs index 97628b05f8ce..71bb7b31b9b2 100644 --- a/crates/cext/src/transpiler/passes/optimize_1q_sequences.rs +++ b/crates/cext/src/transpiler/passes/optimize_1q_sequences.rs @@ -91,7 +91,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_optimize_1q_sequences( let circuit = unsafe { mut_ptr_as_ref(circuit) }; // Convert the circuit to a DAG. - let mut circuit_as_dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + let mut circuit_as_dag = DAGCircuit::from_circuit_data(circuit, false, None, None) .expect("Error while converting the circuit to a dag."); let state = Optimize1qGatesDecompositionState::new( diff --git a/crates/cext/src/transpiler/passes/remove_diagonal_gates_before_measure.rs b/crates/cext/src/transpiler/passes/remove_diagonal_gates_before_measure.rs index 1d5a0629c564..581b720fb515 100644 --- a/crates/cext/src/transpiler/passes/remove_diagonal_gates_before_measure.rs +++ b/crates/cext/src/transpiler/passes/remove_diagonal_gates_before_measure.rs @@ -31,7 +31,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_remove_diagonal_gates_bef ) { // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; - let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None) .expect("Circuit to DAG conversion failed"); run_remove_diagonal_before_measure(&mut dag); *circuit = CircuitData::from_dag_ref(&dag).expect("DAG to Circuit conversion failed"); diff --git a/crates/cext/src/transpiler/passes/remove_identity_equiv.rs b/crates/cext/src/transpiler/passes/remove_identity_equiv.rs index 6c3a721469e7..c850eff991fd 100644 --- a/crates/cext/src/transpiler/passes/remove_identity_equiv.rs +++ b/crates/cext/src/transpiler/passes/remove_identity_equiv.rs @@ -51,7 +51,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_remove_identity_equivalen // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(e) => panic!("{}", e), }; diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index d6ca3b4353fe..5ccb3610bc4c 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -114,7 +114,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; let options = unsafe { const_ptr_as_ref(options) }; - let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None) .unwrap_or_else(|_| panic!("Internal circuit to DAG conversion failed.")); let heuristic = heuristic::Heuristic::new( Some(heuristic::BasicHeuristic::new( diff --git a/crates/cext/src/transpiler/passes/split_2q_unitaries.rs b/crates/cext/src/transpiler/passes/split_2q_unitaries.rs index d9396ed3ef84..c2b1ae0ac652 100644 --- a/crates/cext/src/transpiler/passes/split_2q_unitaries.rs +++ b/crates/cext/src/transpiler/passes/split_2q_unitaries.rs @@ -42,7 +42,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_split_2q_unitaries( ) -> *mut TranspileLayout { // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; - let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(_e) => panic!("Internal circuit -> DAG conversion failed."), }; diff --git a/crates/cext/src/transpiler/passes/two_qubit_peephole.rs b/crates/cext/src/transpiler/passes/two_qubit_peephole.rs index 52a94162c662..cf9938eac137 100644 --- a/crates/cext/src/transpiler/passes/two_qubit_peephole.rs +++ b/crates/cext/src/transpiler/passes/two_qubit_peephole.rs @@ -84,7 +84,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_2q_peephole_optimization( // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(e) => panic!("{}", e), }; diff --git a/crates/cext/src/transpiler/passes/unitary_synthesis.rs b/crates/cext/src/transpiler/passes/unitary_synthesis.rs index 44901bcf6cc0..bab9cf4c4e9e 100644 --- a/crates/cext/src/transpiler/passes/unitary_synthesis.rs +++ b/crates/cext/src/transpiler/passes/unitary_synthesis.rs @@ -81,7 +81,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_unitary_synthesis( // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(e) => panic!("{}", e), }; diff --git a/crates/cext/src/transpiler/passes/vf2.rs b/crates/cext/src/transpiler/passes/vf2.rs index 87f776f1e056..8913b7d272a0 100644 --- a/crates/cext/src/transpiler/passes/vf2.rs +++ b/crates/cext/src/transpiler/passes/vf2.rs @@ -353,7 +353,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_vf2_layout_average( // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { const_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(e) => panic!("{}", e), }; @@ -443,7 +443,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_vf2_layout_exact( // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { const_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None) { Ok(dag) => dag, Err(e) => panic!("{}", e), }; diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index f358b49f0995..b4ca6cb68e88 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -3493,7 +3493,7 @@ mod test { let other = qc.clone(); check(&qc, &other); let roundtrip = py_dag_to_circuit( - &DAGCircuit::from_circuit_data(&qc, false, None, None, None, None)?, + &DAGCircuit::from_circuit_data(&qc, false, None, None)?.into(), false, )?; check(&qc, &roundtrip); diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index e23e81ae9206..24dda599450a 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -593,8 +593,7 @@ impl CircuitBlock for CircuitData { } impl CircuitBlock for DAGCircuit { fn extract_py_block(ob: Bound) -> PyResult { - Self::from_circuit_data(&ob.borrow().inner, false, None, None, None, None) - .map_err(Into::into) + Self::from_circuit_data(&ob.borrow().inner, false, None, None).map_err(Into::into) } } impl CircuitBlock for NoBlocks { diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index 615246a10530..6d31deed5986 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -16,7 +16,7 @@ use pyo3::prelude::*; use crate::bit::{ShareableClbit, ShareableQubit}; use crate::circuit_data::{CircuitData, CircuitDataError, PyCircuitData}; -use crate::dag_circuit::DAGCircuit; +use crate::dag_circuit::PyDAGCircuit; use crate::{Clbit, Qubit}; /// An extractable representation of a QuantumCircuit reserved only for @@ -52,7 +52,7 @@ pub fn circuit_to_dag( copy_operations: bool, qubit_order: Option>, clbit_order: Option>, -) -> PyResult { +) -> PyResult { // Convert ShareableQubit/ShareableClbit to internal indices let qubit_indices = qubit_order .as_ref() @@ -96,7 +96,7 @@ pub fn circuit_to_dag( }) .transpose()?; - DAGCircuit::from_circuit( + PyDAGCircuit::from_circuit( quantum_circuit, copy_operations, qubit_indices, @@ -105,20 +105,20 @@ pub fn circuit_to_dag( .map_err(Into::into) } -/// Convert a :class:`.DAGCircuit` to a :class:`.PyCircuitData`. +/// Convert a :class:`.DAGCircuit` to a :class:`.CircuitData`. /// /// `copy_operations` refers to Python-space operations; if set true, we'll attach to a Python /// interpreter to ensure we can copy any objects. If we're not running in a Python context, pass /// `false` to that argument (or better, in Rust space, use `CircuitData::from_dag_ref`). #[pyfunction(name = "dag_to_circuit", signature = (dag, copy_operations = true))] pub fn py_dag_to_circuit( - dag: &DAGCircuit, + dag: &PyDAGCircuit, copy_operations: bool, ) -> Result { if copy_operations { - Python::attach(|py| CircuitData::from_dag_ref_deepcopy(py, dag)) + Python::attach(|py| CircuitData::from_dag_ref_deepcopy(py, dag.try_read()?)) } else { - CircuitData::from_dag_ref(dag) + CircuitData::from_dag_ref(dag.try_read()?) } .map(PyCircuitData::from) } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index e405550839e2..81ecab68bb85 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -327,17 +327,8 @@ impl Wire { /// There are 3 types of nodes in the graph: inputs, outputs, and operations. /// The nodes are connected by directed edges that correspond to qubits and /// bits. -#[pyclass(module = "qiskit._accelerate.circuit", from_py_object)] #[derive(Clone, Debug)] pub struct DAGCircuit { - /// Circuit name. Generally, this corresponds to the name - /// of the QuantumCircuit from which the DAG was generated. - #[pyo3(get, set)] - pub name: Option, - /// Circuit metadata - #[pyo3(get, set)] - pub metadata: Option>, - dag: StableDiGraph, qregs: RegisterData, @@ -484,19 +475,45 @@ impl PyBitLocations { } } +/// Quantum circuit as a directed acyclic graph. +/// +/// There are 3 types of nodes in the graph: inputs, outputs, and operations. +/// The nodes are connected by directed edges that correspond to qubits and +/// bits. +#[pyclass( + name = "DAGCircuit", + module = "qiskit._accelerate.circuit", + from_py_object +)] +#[derive(Clone, Debug)] +pub struct PyDAGCircuit { + /// Circuit name. Generally, this corresponds to the name + /// of the QuantumCircuit from which the DAG was generated. + #[pyo3(get, set)] + pub name: Option, + /// Circuit metadata + #[pyo3(get, set)] + pub metadata: Option>, + /// Inner circuit + inner: DAGCircuit, +} + #[pymethods] -impl DAGCircuit { +impl PyDAGCircuit { #[new] - pub fn py_new(py: Python) -> PyResult { - let mut out = Self::new(); - out.metadata = Some(PyDict::new(py).unbind().into()); - Ok(out) + pub fn py_new(py: Python) -> Self { + let out = DAGCircuit::new(); + PyDAGCircuit { + name: None, + metadata: Some(PyDict::new(py).unbind().into()), + inner: out, + } } /// Returns the dict containing the QuantumRegisters in the circuit #[getter] fn get_qregs(&self, py: Python) -> &Py { - self.qregs.cached(py) + self.inner.qregs.cached(py) } /// Returns a dict mapping Qubit instances to tuple comprised of 0) the @@ -505,13 +522,13 @@ impl DAGCircuit { /// within that register. #[getter("_qubit_indices")] pub fn get_qubit_locations(&self, py: Python) -> &Py { - self.qubit_locations.cached(py) + self.inner.qubit_locations.cached(py) } /// Returns the dict containing the ClassicalRegisters in the circuit #[getter] fn get_cregs(&self, py: Python) -> &Py { - self.cregs.cached(py) + self.inner.cregs.cached(py) } /// Returns a dict mapping Clbit instances to tuple comprised of 0) the @@ -520,7 +537,7 @@ impl DAGCircuit { /// within that register. #[getter("_clbit_indices")] pub fn get_clbit_locations(&self, py: Python) -> &Py { - self.clbit_locations.cached(py) + self.inner.clbit_locations.cached(py) } /// Returns the total duration of the circuit, set by a scheduling transpiler pass. Its unit is @@ -548,7 +565,7 @@ impl DAGCircuit { /// To be removed with get_duration. #[getter("_duration")] fn get_internal_duration(&self, py: Python) -> PyResult>> { - Ok(self.duration.as_ref().map(|x| x.clone_ref(py))) + Ok(self.inner.duration.as_ref().map(|x| x.clone_ref(py))) } /// Sets the total duration of the circuit, set by a scheduling transpiler pass. Its unit is @@ -577,7 +594,7 @@ impl DAGCircuit { /// To be removed with set_duration. #[setter("_duration")] fn set_internal_duration(&mut self, duration: Option>) { - self.duration = duration + self.inner.duration = duration } /// Returns the unit that duration is specified in. @@ -604,7 +621,7 @@ impl DAGCircuit { /// To be removed with get_unit. #[getter("_unit")] fn get_internal_unit(&self) -> PyResult { - Ok(self.unit.clone()) + Ok(self.inner.unit.clone()) } /// Sets the unit that duration is specified in. @@ -632,48 +649,52 @@ impl DAGCircuit { /// To be removed with set_unit. #[setter("_unit")] fn set_internal_unit(&mut self, unit: String) { - self.unit = unit + self.inner.unit = unit } #[getter] fn input_map(&self, py: Python) -> PyResult> { let out_dict = PyDict::new(py); for (qubit, indices) in self + .inner .qubit_io_map .iter() .enumerate() .map(|(idx, indices)| (Qubit::new(idx), indices)) { out_dict.set_item( - self.qubits.get(qubit).unwrap(), - self.get_node(py, indices[0])?, + self.inner.qubits.get(qubit).unwrap(), + self.inner.get_node(py, indices[0])?, )?; } for (clbit, indices) in self + .inner .clbit_io_map .iter() .enumerate() .map(|(idx, indices)| (Clbit::new(idx), indices)) { out_dict.set_item( - self.clbits.get(clbit).unwrap(), - self.get_node(py, indices[0])?, + self.inner.clbits.get(clbit).unwrap(), + self.inner.get_node(py, indices[0])?, )?; } for (var, indices) in self + .inner .var_io_map .iter() .enumerate() .map(|(idx, indices)| (Var::new(idx), indices)) { out_dict.set_item( - self.vars_stretches + self.inner + .vars_stretches .vars() .get(var) .unwrap() .clone() .into_pyobject(py)?, - self.get_node(py, indices[0])?, + self.inner.get_node(py, indices[0])?, )?; } Ok(out_dict.unbind()) @@ -683,41 +704,45 @@ impl DAGCircuit { fn output_map(&self, py: Python) -> PyResult> { let out_dict = PyDict::new(py); for (qubit, indices) in self + .inner .qubit_io_map .iter() .enumerate() .map(|(idx, indices)| (Qubit::new(idx), indices)) { out_dict.set_item( - self.qubits.get(qubit).unwrap(), - self.get_node(py, indices[1])?, + self.inner.qubits.get(qubit).unwrap(), + self.inner.get_node(py, indices[1])?, )?; } for (clbit, indices) in self + .inner .clbit_io_map .iter() .enumerate() .map(|(idx, indices)| (Clbit::new(idx), indices)) { out_dict.set_item( - self.clbits.get(clbit).unwrap(), - self.get_node(py, indices[1])?, + self.inner.clbits.get(clbit).unwrap(), + self.inner.get_node(py, indices[1])?, )?; } for (var, indices) in self + .inner .var_io_map .iter() .enumerate() .map(|(idx, indices)| (Var::new(idx), indices)) { out_dict.set_item( - self.vars_stretches + self.inner + .vars_stretches .vars() .get(var) .unwrap() .clone() .into_pyobject(py)?, - self.get_node(py, indices[1])?, + self.inner.get_node(py, indices[1])?, )?; } Ok(out_dict.unbind()) @@ -727,12 +752,13 @@ impl DAGCircuit { let out_dict = PyDict::new(py); out_dict.set_item("name", self.name.clone())?; out_dict.set_item("metadata", self.metadata.as_ref().map(|x| x.clone_ref(py)))?; - out_dict.set_item("qregs", self.qregs.cached(py))?; - out_dict.set_item("cregs", self.cregs.cached(py))?; - out_dict.set_item("global_phase", self.global_phase.clone())?; + out_dict.set_item("qregs", self.inner.qregs.cached(py))?; + out_dict.set_item("cregs", self.inner.cregs.cached(py))?; + out_dict.set_item("global_phase", self.inner.global_phase.clone())?; out_dict.set_item( "qubit_io_map", - self.qubit_io_map + self.inner + .qubit_io_map .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) @@ -740,7 +766,8 @@ impl DAGCircuit { )?; out_dict.set_item( "clbit_io_map", - self.clbit_io_map + self.inner + .clbit_io_map .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) @@ -748,37 +775,38 @@ impl DAGCircuit { )?; out_dict.set_item( "var_io_map", - self.var_io_map + self.inner + .var_io_map .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) .into_py_dict(py)?, )?; - out_dict.set_item("op_name", self.op_names.clone())?; - out_dict.set_item("qubits", self.qubits.objects())?; - out_dict.set_item("clbits", self.clbits.objects())?; + out_dict.set_item("op_name", self.inner.op_names.clone())?; + out_dict.set_item("qubits", self.inner.qubits.objects())?; + out_dict.set_item("clbits", self.inner.clbits.objects())?; - let vars_stretches_state = self.vars_stretches.to_pickle(py); + let vars_stretches_state = self.inner.vars_stretches.to_pickle(py); out_dict.set_item("identifier_info", vars_stretches_state.0)?; out_dict.set_item("vars", vars_stretches_state.1)?; out_dict.set_item("stretches", vars_stretches_state.2)?; - let mut nodes: Vec> = Vec::with_capacity(self.dag.node_count()); - for node_idx in self.dag.node_indices() { - let node_data = self.get_node(py, node_idx)?; + let mut nodes: Vec> = Vec::with_capacity(self.inner.dag.node_count()); + for node_idx in self.inner.dag.node_indices() { + let node_data = self.inner.get_node(py, node_idx)?; nodes.push((node_idx.index(), node_data).into_py_any(py)?); } out_dict.set_item("nodes", nodes)?; out_dict.set_item( "nodes_removed", - self.dag.node_count() != self.dag.node_bound(), + self.inner.dag.node_count() != self.inner.dag.node_bound(), )?; - let mut edges: Vec> = Vec::with_capacity(self.dag.edge_bound()); + let mut edges: Vec> = Vec::with_capacity(self.inner.dag.edge_bound()); // edges are saved with none (deleted edges) instead of their index to save space - for i in 0..self.dag.edge_bound() { + for i in 0..self.inner.dag.edge_bound() { let idx = EdgeIndex::new(i); - let edge = match self.dag.edge_weight(idx) { + let edge = match self.inner.dag.edge_weight(idx) { Some(edge_w) => { - let endpoints = self.dag.edge_endpoints(idx).unwrap(); + let endpoints = self.inner.dag.edge_endpoints(idx).unwrap(); ( endpoints.0.index(), endpoints.1.index(), @@ -798,29 +826,29 @@ impl DAGCircuit { let dict_state = state.cast_bound::(py)?; self.name = dict_state.get_item("name")?.unwrap().extract()?; self.metadata = dict_state.get_item("metadata")?.unwrap().extract()?; - self.qregs = RegisterData::from_mapping( + self.inner.qregs = RegisterData::from_mapping( dict_state .get_item("qregs")? .unwrap() .extract::>()?, ); - self.cregs = RegisterData::from_mapping( + self.inner.cregs = RegisterData::from_mapping( dict_state .get_item("cregs")? .unwrap() .extract::>()?, ); - self.global_phase = dict_state.get_item("global_phase")?.unwrap().extract()?; - self.op_names = dict_state.get_item("op_name")?.unwrap().extract()?; + self.inner.global_phase = dict_state.get_item("global_phase")?.unwrap().extract()?; + self.inner.op_names = dict_state.get_item("op_name")?.unwrap().extract()?; let binding = dict_state.get_item("qubits")?.unwrap(); let qubits_raw = binding.extract::>()?; for bit in qubits_raw.into_iter() { - self.qubits.add(bit)?; + self.inner.qubits.add(bit)?; } let binding = dict_state.get_item("clbits")?.unwrap(); let clbits_raw = binding.extract::>()?; for bit in clbits_raw.into_iter() { - self.clbits.add(bit)?; + self.inner.clbits.add(bit)?; } let binding = dict_state.get_item("identifier_info")?.unwrap(); @@ -830,31 +858,34 @@ impl DAGCircuit { let binding = dict_state.get_item("stretches")?.unwrap(); let stretches_raw = binding.extract::>()?; - self.vars_stretches = + self.inner.vars_stretches = VarStretchContainer::from_pickle(py, (identifier_info, vars_raw, stretches_raw))?; let binding = dict_state.get_item("qubit_io_map")?.unwrap(); let qubit_index_map_raw = binding.cast::().unwrap(); - self.qubit_io_map = Vec::with_capacity(qubit_index_map_raw.len()); + self.inner.qubit_io_map = Vec::with_capacity(qubit_index_map_raw.len()); for (_k, v) in qubit_index_map_raw.iter() { let indices: [usize; 2] = v.extract()?; - self.qubit_io_map + self.inner + .qubit_io_map .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); } let binding = dict_state.get_item("clbit_io_map")?.unwrap(); let clbit_index_map_raw = binding.cast::().unwrap(); - self.clbit_io_map = Vec::with_capacity(clbit_index_map_raw.len()); + self.inner.clbit_io_map = Vec::with_capacity(clbit_index_map_raw.len()); for (_k, v) in clbit_index_map_raw.iter() { let indices: [usize; 2] = v.extract()?; - self.clbit_io_map + self.inner + .clbit_io_map .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); } let binding = dict_state.get_item("var_io_map")?.unwrap(); let var_index_map_raw = binding.cast::().unwrap(); - self.var_io_map = Vec::with_capacity(var_index_map_raw.len()); + self.inner.var_io_map = Vec::with_capacity(var_index_map_raw.len()); for (_k, v) in var_index_map_raw.iter() { let indices: [usize; 2] = v.extract()?; - self.var_io_map + self.inner + .var_io_map .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); } // Rebuild Graph preserving index holes: @@ -863,12 +894,12 @@ impl DAGCircuit { let binding = dict_state.get_item("edges")?.unwrap(); let edges_lst = binding.cast::()?; let node_removed: bool = dict_state.get_item("nodes_removed")?.unwrap().extract()?; - self.dag = StableDiGraph::default(); + self.inner.dag = StableDiGraph::default(); if !node_removed { for item in nodes_lst.iter() { let node_w = item.cast::().unwrap().get_item(1).unwrap(); - let weight = self.pack_into(py, &node_w)?; - self.dag.add_node(weight); + let weight = self.inner.pack_into(py, &node_w)?; + self.inner.dag.add_node(weight); } } else if nodes_lst.len() == 1 { // graph has only one node, handle logic here to save one if in the loop later @@ -878,12 +909,12 @@ impl DAGCircuit { let node_w = item.get_item(1).unwrap(); for _i in 0..node_idx { - self.dag.add_node(NodeType::QubitIn(Qubit(u32::MAX))); + self.inner.dag.add_node(NodeType::QubitIn(Qubit(u32::MAX))); } - let weight = self.pack_into(py, &node_w)?; - self.dag.add_node(weight); + let weight = self.inner.pack_into(py, &node_w)?; + self.inner.dag.add_node(weight); for i in 0..node_idx { - self.dag.remove_node(NodeIndex::new(i)); + self.inner.dag.remove_node(NodeIndex::new(i)); } } else { let binding = nodes_lst.get_item(nodes_lst.len() - 1).unwrap(); @@ -898,57 +929,63 @@ impl DAGCircuit { let item = item.cast::().unwrap(); let next_index: usize = item.get_item(0).unwrap().extract().unwrap(); let weight: Py = item.get_item(1).unwrap().extract().unwrap(); - while next_index > self.dag.node_bound() { + while next_index > self.inner.dag.node_bound() { // node does not exist - let tmp_node = self.dag.add_node(NodeType::QubitIn(Qubit(u32::MAX))); + let tmp_node = self.inner.dag.add_node(NodeType::QubitIn(Qubit(u32::MAX))); tmp_nodes.push(tmp_node); } // add node to the graph, and update the next available node index - let weight = self.pack_into(py, weight.bind(py))?; - self.dag.add_node(weight); + let weight = self.inner.pack_into(py, weight.bind(py))?; + self.inner.dag.add_node(weight); } // Remove any temporary nodes we added for tmp_node in tmp_nodes { - self.dag.remove_node(tmp_node); + self.inner.dag.remove_node(tmp_node); } } // to ensure O(1) on edge deletion, use a temporary node to store missing edges - let tmp_node = self.dag.add_node(NodeType::QubitIn(Qubit(u32::MAX))); + let tmp_node = self.inner.dag.add_node(NodeType::QubitIn(Qubit(u32::MAX))); for item in edges_lst { if item.is_none() { // add a temporary edge that will be deleted later to re-create the hole - self.dag + self.inner + .dag .add_edge(tmp_node, tmp_node, Wire::Qubit(Qubit(u32::MAX))); } else { let triple = item.cast::().unwrap(); let edge_p: usize = triple.get_item(0).unwrap().extract().unwrap(); let edge_c: usize = triple.get_item(1).unwrap().extract().unwrap(); let edge_w = Wire::from_pickle(&triple.get_item(2).unwrap())?; - self.dag + self.inner + .dag .add_edge(NodeIndex::new(edge_p), NodeIndex::new(edge_c), edge_w); } } - self.dag.remove_node(tmp_node); - self.qubit_locations = BitLocator::with_capacity(self.qubits.len()); - for (index, qubit) in self.qubits.objects().iter().enumerate() { + self.inner.dag.remove_node(tmp_node); + self.inner.qubit_locations = BitLocator::with_capacity(self.inner.qubits.len()); + for (index, qubit) in self.inner.qubits.objects().iter().enumerate() { let registers = self + .inner .qregs .registers() .iter() .filter_map(|x| x.index_of(qubit).map(|y| (x.clone(), y))); - self.qubit_locations + self.inner + .qubit_locations .insert(qubit.clone(), BitLocations::new(index as u32, registers)); } - self.clbit_locations = BitLocator::with_capacity(self.clbits.len()); - for (index, clbit) in self.clbits.objects().iter().enumerate() { + self.inner.clbit_locations = BitLocator::with_capacity(self.inner.clbits.len()); + for (index, clbit) in self.inner.clbits.objects().iter().enumerate() { let registers = self + .inner .cregs .registers() .iter() .filter_map(|x| x.index_of(clbit).map(|y| (x.clone(), y))); - self.clbit_locations + self.inner + .clbit_locations .insert(clbit.clone(), BitLocations::new(index as u32, registers)); } Ok(()) @@ -970,11 +1007,12 @@ impl DAGCircuit { .metadata .map(|dict| deepcopy.call1((dict, memo)).map(|ob| ob.unbind())) .transpose()?; - out.duration = out + out.inner.duration = out + .inner .duration .map(|dict| deepcopy.call1((dict, memo)).map(|ob| ob.unbind())) .transpose()?; - for node in out.dag.node_weights_mut() { + for node in out.inner.dag.node_weights_mut() { if let NodeType::Operation(inst) = node { inst.py_deepcopy_inplace(py, memo)?; }; @@ -993,7 +1031,7 @@ impl DAGCircuit { /// list(:class:`.Qubit`): The current sequence of registered qubits. #[getter(qubits)] pub fn py_qubits(&self, py: Python<'_>) -> Py { - self.qubits.cached(py).clone_ref(py) + self.inner.qubits.cached(py).clone_ref(py) } /// Returns the current sequence of registered :class:`.Clbit` @@ -1008,20 +1046,20 @@ impl DAGCircuit { /// list(:class:`.Clbit`): The current sequence of registered clbits. #[getter(clbits)] pub fn py_clbits(&self, py: Python<'_>) -> Py { - self.clbits.cached(py).clone_ref(py) + self.inner.clbits.cached(py).clone_ref(py) } /// Return a list of the wires in order. #[getter] fn get_wires(&self, py: Python<'_>) -> PyResult> { - let wires: Bound = PyList::new(py, self.qubits.objects().iter())?; + let wires: Bound = PyList::new(py, self.inner.qubits.objects().iter())?; - for clbit in self.clbits.objects().iter() { + for clbit in self.inner.clbits.objects().iter() { wires.append(clbit)? } let out_list = PyList::new(py, wires)?; - for var in self.vars_stretches.vars().objects() { + for var in self.inner.vars_stretches.vars().objects() { out_list.append(var.clone().into_py_any(py)?)?; } Ok(out_list.unbind()) @@ -1030,13 +1068,13 @@ impl DAGCircuit { /// Returns the number of nodes in the dag. #[getter] fn get_node_counter(&self) -> usize { - self.dag.node_count() + self.inner.dag.node_count() } /// Return the global phase of the circuit. #[getter] pub fn get_global_phase(&self) -> Param { - self.global_phase.clone() + self.inner.global_phase.clone() } /// Set the global phase of the circuit. @@ -1045,13 +1083,13 @@ impl DAGCircuit { /// angle (float, :class:`.ParameterExpression`): The phase angle. #[setter(global_phase)] pub fn set_global_phase(&mut self, angle: Param) -> Result<(), DAGError> { - self.set_global_phase_param(angle).map(|_| ()) + self.inner.set_global_phase_param(angle).map(|_| ()) } /// Remove all operation nodes with the given name. fn remove_all_ops_named(&mut self, opname: &str) { let mut to_remove = Vec::new(); - for (id, weight) in self.dag.node_references() { + for (id, weight) in self.inner.dag.node_references() { if let NodeType::Operation(packed) = &weight { if opname == packed.op.name() { to_remove.push(id); @@ -1059,7 +1097,7 @@ impl DAGCircuit { } } for node in to_remove { - self.remove_op_node(node); + self.inner.remove_op_node(node); } } @@ -1069,12 +1107,12 @@ impl DAGCircuit { let Ok(bit) = bit.extract::() else { return Err(DAGCircuitError::new_err("not a Qubit instance.")); }; - if self.qubits.find(&bit).is_some() { + if self.inner.qubits.find(&bit).is_some() { return Err(DAGCircuitError::new_err(format!( "duplicate qubits {bit:?}" ))); } - self.add_qubit_unchecked(bit)?; + self.inner.add_qubit_unchecked(bit)?; } Ok(()) } @@ -1085,44 +1123,24 @@ impl DAGCircuit { let Ok(bit) = bit.extract::() else { return Err(DAGCircuitError::new_err("not a Clbit instance.")); }; - if self.clbits.find(&bit).is_some() { + if self.inner.clbits.find(&bit).is_some() { return Err(DAGCircuitError::new_err(format!( "duplicate clbits {bit:?}" ))); } - self.add_clbit_unchecked(bit)?; + self.inner.add_clbit_unchecked(bit)?; } Ok(()) } /// Add all wires in a quantum register. pub fn add_qreg(&mut self, qreg: QuantumRegister) -> Result<(), DAGError> { - self.qregs.add_register(qreg.clone(), true)?; - - for (index, bit) in qreg.bits().enumerate() { - if self.qubits.find(&bit).is_none() { - self.add_qubit_unchecked(bit.clone())?; - } - let locations: &mut BitLocations = - self.qubit_locations.get_mut(&bit).unwrap(); - locations.add_register(qreg.clone(), index); - } - Ok(()) + self.inner.add_qreg(qreg) } /// Add all wires in a classical register. pub fn add_creg(&mut self, creg: ClassicalRegister) -> Result<(), DAGError> { - self.cregs.add_register(creg.clone(), true)?; - - for (index, bit) in creg.bits().enumerate() { - if self.clbits.find(&bit).is_none() { - self.add_clbit_unchecked(bit.clone())?; - } - let locations: &mut BitLocations = - self.clbit_locations.get_mut(&bit).unwrap(); - locations.add_register(creg.clone(), index); - } - Ok(()) + self.inner.add_creg(creg) } /// Finds locations in the circuit, by mapping the Qubit and Clbit to positional index @@ -1148,7 +1166,8 @@ impl DAGCircuit { bit: &Bound<'py, PyAny>, ) -> PyResult> { if let Ok(qubit) = bit.extract::() { - self.qubit_locations + self.inner + .qubit_locations .get(&qubit) .map(|location| location.clone().into_pyobject(py)) .transpose()? @@ -1158,7 +1177,8 @@ impl DAGCircuit { )) }) } else if let Ok(clbit) = bit.extract::() { - self.clbit_locations + self.inner + .clbit_locations .get(&clbit) .map(|location| location.clone().into_pyobject(py)) .transpose()? @@ -1191,7 +1211,7 @@ impl DAGCircuit { /// or is not idle. #[pyo3(name = "remove_clbits", signature = (*clbits))] fn py_remove_clbits(&mut self, clbits: Vec) -> PyResult<()> { - let bit_iter = match self.clbits.map_objects(clbits.iter().cloned()) { + let bit_iter = match self.inner.clbits.map_objects(clbits.iter().cloned()) { Ok(bit_iter) => bit_iter, Err(_) => { return Err(DAGCircuitError::new_err(format!( @@ -1199,7 +1219,7 @@ impl DAGCircuit { ))); } }; - self.remove_clbits(bit_iter).map_err(Into::into) + self.inner.remove_clbits(bit_iter).map_err(Into::into) } /// Remove classical registers from the circuit, leaving underlying bits @@ -1210,7 +1230,7 @@ impl DAGCircuit { /// the circuit. #[pyo3(name = "remove_cregs", signature = (*cregs))] fn py_remove_cregs(&mut self, cregs: Vec) -> PyResult<()> { - self.remove_cregs(cregs).map_err(Into::into) + self.inner.remove_cregs(cregs).map_err(Into::into) } /// Remove quantum bits from the circuit. All bits MUST be idle. @@ -1229,7 +1249,7 @@ impl DAGCircuit { /// or is not idle. #[pyo3(name = "remove_qubits", signature = (*qubits))] pub fn py_remove_qubits(&mut self, qubits: Vec) -> PyResult<()> { - let bit_iter = match self.qubits.map_objects(qubits.iter().cloned()) { + let bit_iter = match self.inner.qubits.map_objects(qubits.iter().cloned()) { Ok(bit_iter) => bit_iter, Err(_) => { return Err(DAGCircuitError::new_err(format!( @@ -1237,7 +1257,7 @@ impl DAGCircuit { ))); } }; - self.remove_qubits(bit_iter).map_err(Into::into) + self.inner.remove_qubits(bit_iter).map_err(Into::into) } /// Remove quantum registers from the circuit, leaving underlying bits @@ -1248,10 +1268,9 @@ impl DAGCircuit { /// the circuit. #[pyo3(name = "remove_qregs", signature = (*qregs))] fn py_remove_qregs(&mut self, qregs: Vec) -> PyResult<()> { - // let self_bound_cregs = self.cregs.bind(py); let mut valid_regs: Vec = Vec::new(); for qregs in qregs.into_iter() { - if let Some(reg) = self.qregs.get(qregs.name()) { + if let Some(reg) = self.inner.qregs.get(qregs.name()) { if reg != &qregs { return Err(DAGCircuitError::new_err(format!( "creg not in circuit: {reg:?}" @@ -1268,12 +1287,12 @@ impl DAGCircuit { // Use an iterator that will remove the registers from the circuit as it iterates. let valid_names = valid_regs.iter().map(|reg| { for (index, bit) in reg.bits().enumerate() { - let bit_position = self.qubit_locations.get_mut(&bit).unwrap(); + let bit_position = self.inner.qubit_locations.get_mut(&bit).unwrap(); bit_position.remove_register(reg, index); } reg.name().to_string() }); - self.qregs.remove_registers(valid_names); + self.inner.qregs.remove_registers(valid_names); Ok(()) } @@ -1293,6 +1312,7 @@ impl DAGCircuit { let resources = condition_resources(condition)?; for reg in resources.cregs.bind(py) { if !self + .inner .cregs .contains_key(reg.getattr(intern!(py, "name"))?.to_string().as_str()) { @@ -1304,7 +1324,7 @@ impl DAGCircuit { for bit in resources.clbits.bind(py) { let bit: ShareableClbit = bit.extract()?; - if self.clbits.find(&bit).is_none() { + if self.inner.clbits.find(&bit).is_none() { return Err(DAGCircuitError::new_err(format!( "invalid clbits in condition for {name}" ))); @@ -1331,7 +1351,13 @@ impl DAGCircuit { // You likely want `copy_empty_like_with_same_capacity`. #[pyo3(name = "copy_empty_like", signature = (*, vars_mode=VarsMode::Alike))] pub fn py_copy_empty_like(&self, vars_mode: VarsMode) -> Self { - self.copy_empty_like_with_capacity(0, 0, vars_mode, BlocksMode::Drop) + PyDAGCircuit { + name: self.name.clone(), + metadata: self.metadata.as_ref().cloned(), + inner: self + .inner + .copy_empty_like_with_capacity(0, 0, vars_mode, BlocksMode::Drop), + } } /// Put ``self`` into the canonical physical form, with the given number of qubits. @@ -1353,18 +1379,18 @@ impl DAGCircuit { pub fn py_make_physical(&mut self, num_qubits: Option) -> PyResult<()> { let num_qubits = match num_qubits { Some(num_qubits) => { - if (num_qubits as usize) < self.num_qubits() { + if (num_qubits as usize) < self.inner.num_qubits() { return Err(PyValueError::new_err(format!( "cannot have fewer physical qubits ({}) than virtual ({})", num_qubits, - self.num_qubits() + self.inner.num_qubits() ))); } num_qubits as usize } - None => self.num_qubits(), + None => self.inner.num_qubits(), }; - self.make_physical(num_qubits); + self.inner.make_physical(num_qubits); Ok(()) } @@ -1375,12 +1401,12 @@ impl DAGCircuit { node: &Bound, check: bool, ) -> PyResult<()> { - if let NodeType::Operation(inst) = self.pack_into(py, node)? { + if let NodeType::Operation(inst) = self.inner.pack_into(py, node)? { if check { - self.check_op_addition(&inst)?; + self.inner.check_op_addition(&inst)?; } - self.push_back(inst)?; + self.inner.push_back(inst)?; Ok(()) } else { Err(PyTypeError::new_err("Invalid node type input")) @@ -1420,13 +1446,15 @@ impl DAGCircuit { .map(|c| c.value.extract::>()) .transpose()?; let node = { - let qubits_id = self.qargs_interner.insert_owned( - self.qubits + let qubits_id = self.inner.qargs_interner.insert_owned( + self.inner + .qubits .map_objects(qargs.into_iter().flatten())? .collect(), ); - let clbits_id = self.cargs_interner.insert_owned( - self.clbits + let clbits_id = self.inner.cargs_interner.insert_owned( + self.inner + .clbits .map_objects(cargs.into_iter().flatten())? .collect(), ); @@ -1434,19 +1462,19 @@ impl DAGCircuit { op: py_op.operation, qubits: qubits_id, clbits: clbits_id, - params: self.take_parameter_blocks(py_op.params), + params: self.inner.take_parameter_blocks(py_op.params), label: py_op.label, #[cfg(feature = "cache_pygates")] py_op: op.unbind().into(), }; if check { - self.check_op_addition(&instr)?; + self.inner.check_op_addition(&instr)?; } - self.push_back(instr)? + self.inner.push_back(instr)? }; - self.get_node(py, node) + self.inner.get_node(py, node) } /// Apply an operation to the input of the circuit. @@ -1482,13 +1510,15 @@ impl DAGCircuit { .map(|c| c.value.extract::>()) .transpose()?; let node = { - let qubits_id = self.qargs_interner.insert_owned( - self.qubits + let qubits_id = self.inner.qargs_interner.insert_owned( + self.inner + .qubits .map_objects(qargs.into_iter().flatten())? .collect(), ); - let clbits_id = self.cargs_interner.insert_owned( - self.clbits + let clbits_id = self.inner.cargs_interner.insert_owned( + self.inner + .clbits .map_objects(cargs.into_iter().flatten())? .collect(), ); @@ -1496,19 +1526,19 @@ impl DAGCircuit { op: py_op.operation, qubits: qubits_id, clbits: clbits_id, - params: self.take_parameter_blocks(py_op.params), + params: self.inner.take_parameter_blocks(py_op.params), label: py_op.label, #[cfg(feature = "cache_pygates")] py_op: op.unbind().into(), }; if check { - self.check_op_addition(&instr)?; + self.inner.check_op_addition(&instr)?; } - self.push_front(instr)? + self.inner.push_front(instr)? }; - self.get_node(py, node) + self.inner.get_node(py, node) } /// Compose the ``other`` circuit onto the output of this circuit. @@ -1547,7 +1577,7 @@ impl DAGCircuit { fn py_compose( &mut self, py: Python, - other: &DAGCircuit, + other: &PyDAGCircuit, qubits: Option>, clbits: Option>, front: bool, @@ -1570,7 +1600,7 @@ impl DAGCircuit { Ok(Qubit::new(index)) } else { let shareable_qubit = q.extract::()?; - self.qubits.find(&shareable_qubit).ok_or_else(|| { + self.inner.qubits.find(&shareable_qubit).ok_or_else(|| { DAGCircuitError::new_err(format!( "Qubit {:?} not found in DAG", shareable_qubit @@ -1592,7 +1622,7 @@ impl DAGCircuit { Ok(Clbit::new(index)) } else { let shareable_clbit = c.extract::()?; - self.clbits.find(&shareable_clbit).ok_or_else(|| { + self.inner.clbits.find(&shareable_clbit).ok_or_else(|| { DAGCircuitError::new_err(format!( "Clbit {:?} not found in DAG", shareable_clbit @@ -1609,15 +1639,16 @@ impl DAGCircuit { // lead to duplication if a Python user composes with a DAG that already uses blocks // registered in 'self'. let block_map = other + .inner .blocks() .items() - .map(|(index, block)| (index, self.add_block(block.clone()))) + .map(|(index, block)| (index, self.inner.add_block(block.clone()))) .collect(); // Compose if inplace { - self.compose( - other, + self.inner.compose( + &other.inner, qubits.as_deref(), clbits.as_deref(), block_map, @@ -1626,8 +1657,8 @@ impl DAGCircuit { Ok(None) } else { let mut dag = self.clone(); - dag.compose( - other, + dag.inner.compose( + &other.inner, qubits.as_deref(), clbits.as_deref(), block_map, @@ -1660,79 +1691,12 @@ impl DAGCircuit { /// DAGCircuitError: If the DAG is invalid #[pyo3(signature=(ignore=None))] fn idle_wires(&self, py: Python, ignore: Option<&Bound>) -> PyResult> { - let mut result: Vec> = Vec::new(); - let wires = (0..self.qubit_io_map.len()) - .map(|idx| Wire::Qubit(Qubit::new(idx))) - .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit::new(idx)))) - .chain((0..self.var_io_map.len()).map(|idx| Wire::Var(Var::new(idx)))); - match ignore { - Some(ignore) => { - // Convert the list to a Rust set. - let ignore_set = ignore - .into_iter() - .map(|s| s.extract()) - .collect::>>()?; - for wire in wires { - let nodes_found = self - .nodes_on_wire(wire) - .filter(|node| matches!(self.dag[*node], NodeType::Operation(_))) - .any(|node| { - let weight = self.dag.node_weight(node).unwrap(); - if let NodeType::Operation(packed) = weight { - !ignore_set.contains(packed.op.name()) - } else { - false - } - }); - - if !nodes_found { - result.push(match wire { - Wire::Qubit(qubit) => { - self.qubits.get(qubit).unwrap().into_py_any(py)? - } - Wire::Clbit(clbit) => { - self.clbits.get(clbit).unwrap().into_py_any(py)? - } - Wire::Var(var) => self - .vars_stretches - .vars() - .get(var) - .unwrap() - .clone() - .into_py_any(py)?, - }); - } - } - } - None => { - for wire in wires { - if self.is_wire_idle(wire) { - result.push(match wire { - Wire::Qubit(qubit) => { - self.qubits.get(qubit).unwrap().into_py_any(py)? - } - Wire::Clbit(clbit) => { - self.clbits.get(clbit).unwrap().into_py_any(py)? - } - Wire::Var(var) => self - .vars_stretches - .vars() - .get(var) - .unwrap() - .clone() - .into_py_any(py)?, - }); - } - } - } - } - Ok(PyTuple::new(py, result)?.into_any().try_iter()?.unbind()) + idle_wires(&self.inner, py, ignore) } /// Return `true` if there are no operation nodes in the graph. fn is_empty(&self) -> bool { - self.dag.node_count() - == 2 * (self.qubits.len() + self.clbits.len() + self.vars_stretches.vars().len()) + self.inner.is_empty() } /// Return the number of operations. If there is control flow present, this count may only @@ -1753,43 +1717,7 @@ impl DAGCircuit { /// ``recurse=True``, or any control flow is present in a non-recursive call. #[pyo3(signature= (*, recurse=false))] pub fn size(&self, recurse: bool) -> Result { - let mut length = self.num_ops(); - if !self.has_control_flow() { - return Ok(length); - } - if !recurse { - return Err(DAGError::General( - concat!( - "Size with control flow is ambiguous.", - " You may use `recurse=True` to get a result", - " but see this method's documentation for the meaning of this." - ) - .into(), - )); - } - - // Handle recursively. - for control_flow in self - .op_nodes(false) - .filter_map(|(_, node)| self.try_view_control_flow(node)) - { - match control_flow { - ControlFlowView::ForLoop { - collection, body, .. - } => { - // TODO: is this the intended logic? - length += collection.len() * body.size(true)?; - } - _ => { - for block in control_flow.blocks() { - length += block.size(true)?; - } - } - } - // We don't count a control-flow node itself! - length -= 1; - } - Ok(length) + self.inner.size(recurse) } /// Return the circuit depth. If there is control flow present, this count may only be an @@ -1813,58 +1741,7 @@ impl DAGCircuit { /// flow is present in a non-recursive call. #[pyo3(signature= (*, recurse=false))] pub fn depth(&self, recurse: bool) -> Result { - if self.qubits.is_empty() && self.clbits.is_empty() && self.num_vars() == 0 { - return Ok(0); - } - if !self.has_control_flow() { - let weight_fn = |_| -> Result { Ok(1) }; - return match rustworkx_core::dag_algo::longest_path_length(&self.dag, weight_fn) - .unwrap() - { - Some(res) => Ok(res - 1), - None => panic!("not a DAG"), - }; - } - if !recurse { - return Err(DAGError::General( - concat!( - "Depth with control flow is ambiguous.", - " You may use `recurse=True` to get a result", - " but see this method's documentation for the meaning of this." - ) - .into(), - )); - } - // Handle recursively. - let mut node_lookup: HashMap = HashMap::new(); - for (node_index, control_flow) in self - .op_nodes(false) - .filter_map(|(index, node)| self.try_view_control_flow(node).map(|cf| (index, cf))) - { - let weight = if let ControlFlowView::ForLoop { collection, .. } = control_flow { - collection.len() - } else { - 1 - }; - if weight == 0 { - node_lookup.insert(node_index, 0); - } else { - let blocks = control_flow.blocks(); - let mut block_weights: Vec = Vec::with_capacity(blocks.len()); - for block in blocks { - block_weights.push(block.depth(true)?); - node_lookup.insert(node_index, weight * block_weights.iter().max().unwrap()); - } - } - } - - let weight_fn = |edge: EdgeReference<'_, Wire>| -> Result { - Ok(*node_lookup.get(&edge.target()).unwrap_or(&1)) - }; - match rustworkx_core::dag_algo::longest_path_length(&self.dag, weight_fn).unwrap() { - Some(res) => Ok(res - 1), - None => panic!("not a DAG"), - } + self.inner.depth(recurse) } /// Return the total number of qubits + clbits used by the circuit. @@ -1874,7 +1751,7 @@ impl DAGCircuit { /// with the new function DAGCircuit.num_qubits replacing the former /// semantic of DAGCircuit.width(). pub fn width(&self) -> usize { - self.qubits.len() + self.clbits.len() + self.num_vars() + self.inner.width() } /// Return the total number of qubits used by the circuit. @@ -1882,44 +1759,31 @@ impl DAGCircuit { /// DAGCircuit.width() now returns qubits + clbits for /// consistency with Circuit.width() [qiskit-terra #2564]. pub fn num_qubits(&self) -> usize { - self.qubits.len() + self.inner.num_qubits() } /// Return the total number of classical bits used by the circuit. pub fn num_clbits(&self) -> usize { - self.clbits.len() + self.inner.num_clbits() } /// Return the number of basic blocks in this circuit. pub fn num_blocks(&self) -> usize { - self.blocks.len() + self.inner.num_blocks() } /// Get the number of op nodes in the DAG. #[inline] pub fn num_ops(&self) -> usize { - self.dag.node_count() - 2 * self.width() + self.inner.num_ops() } /// Compute how many components the circuit can decompose into. fn num_tensor_factors(&self) -> usize { - // This function was forked from rustworkx's - // number_weekly_connected_components() function as of 0.15.0: - // https://github.com/Qiskit/rustworkx/blob/0.15.0/src/connectivity/mod.rs#L215-L235 - - let mut weak_components = self.dag.node_count(); - let mut vertex_sets = UnionFind::new(self.dag.node_bound()); - for edge in self.dag.edge_references() { - let (a, b) = (edge.source(), edge.target()); - // union the two vertices of the edge - if vertex_sets.union(a.index(), b.index()) { - weak_components -= 1 - }; - } - weak_components + self.inner.num_tensor_factors() } - fn __eq__(&self, py: Python, other: &DAGCircuit) -> PyResult { + fn __eq__(&self, py: Python, other: &PyDAGCircuit) -> PyResult { fn eq_inner( py: Python, slf: &DAGCircuit, @@ -2274,16 +2138,8 @@ impl DAGCircuit { ), ])?; } - let body_a = DAGCircuit::from_circuit( - QuantumCircuitData { - data: body_a_circuit, - name: body_a.name.clone(), - metadata: body_a - .metadata - .as_ref() - .map(|m| m.bind(py).clone()), - transpile_layout: None, - }, + let body_a = DAGCircuit::from_circuit_data( + &body_a_circuit, false, None, None, @@ -2303,16 +2159,8 @@ impl DAGCircuit { ), ])?; } - let body_b = DAGCircuit::from_circuit( - QuantumCircuitData { - data: body_b_circuit, - name: body_b.name.clone(), - metadata: body_b - .metadata - .as_ref() - .map(|m| m.bind(py).clone()), - transpile_layout: None, - }, + let body_b = DAGCircuit::from_circuit_data( + &body_b_circuit, false, None, None, @@ -2622,10 +2470,10 @@ impl DAGCircuit { .collect(); eq_inner( py, - self, + &self.inner, &slf_qubit_map, &slf_clbit_map, - other, + &other.inner, &slf_qubit_map, &slf_clbit_map, ) @@ -2660,23 +2508,24 @@ impl DAGCircuit { /// :class:`.QuantumCircuit`'s equality check). This implements a semantic /// data-flow equality check, which is less sensitive to the order operations were /// defined. This is typically what a user cares about with respect to equality. - fn structurally_equal(&self, other: &DAGCircuit) -> PyResult { - if self.qubits != other.qubits { + fn structurally_equal(&self, other: &PyDAGCircuit) -> PyResult { + if self.inner.qubits != other.inner.qubits { return Ok(false); } - if self.clbits != other.clbits { + if self.inner.clbits != other.inner.clbits { return Ok(false); } if !self + .inner .vars_stretches - .structurally_equal(&other.vars_stretches) + .structurally_equal(&other.inner.vars_stretches) { return Ok(false); } - if self.qregs != other.qregs { + if self.inner.qregs != other.inner.qregs { return Ok(false); } - if self.cregs != other.cregs { + if self.inner.cregs != other.inner.cregs { return Ok(false); } // This is a stricter check than `Param::eq`, since we don't allow equality between explicit @@ -2689,7 +2538,7 @@ impl DAGCircuit { _ => Ok(false), } }; - if !param_eq(&self.global_phase, &other.global_phase)? { + if !param_eq(&self.inner.global_phase, &other.inner.global_phase)? { return Ok(false); } // This is stricter than `PackedInstruction::py_op_eq` because it doesn't allow equality @@ -2704,13 +2553,13 @@ impl DAGCircuit { { return Ok(false); } - if self.qargs_interner.get(from_self.qubits) - != other.qargs_interner.get(from_other.qubits) + if self.inner.qargs_interner.get(from_self.qubits) + != other.inner.qargs_interner.get(from_other.qubits) { return Ok(false); } - if self.cargs_interner.get(from_self.clbits) - != other.cargs_interner.get(from_other.clbits) + if self.inner.cargs_interner.get(from_self.clbits) + != other.inner.cargs_interner.get(from_other.clbits) { return Ok(false); } @@ -2751,12 +2600,17 @@ impl DAGCircuit { // Now the meat of the actual comparison. This is deliberately non-topological and // index-sensitive - that's exactly what we're testing for. - if self.dag.node_count() != other.dag.node_count() - || self.dag.edge_count() != other.dag.edge_count() + if self.inner.dag.node_count() != other.inner.dag.node_count() + || self.inner.dag.edge_count() != other.inner.dag.edge_count() { return Ok(false); } - for (self_index, other_index) in self.dag.node_indices().zip(other.dag.node_indices()) { + for (self_index, other_index) in self + .inner + .dag + .node_indices() + .zip(other.inner.dag.node_indices()) + { // The `NodeIndex` values should be the same for both; for the example of two DAGs that // have undergone a deterministic compilation pipeline, this means that they've seen the // same sequence of additions, removals and substitutions (or at least a comparable @@ -2764,13 +2618,19 @@ impl DAGCircuit { if self_index != other_index { return Ok(false); } - if !self.dag[self_index].equal_with(&other.dag[other_index], inst_eq)? { + if !self.inner.dag[self_index].equal_with(&other.inner.dag[other_index], inst_eq)? { return Ok(false); } for pair in self + .inner .dag .edges_directed(self_index, Direction::Incoming) - .zip_longest(other.dag.edges_directed(other_index, Direction::Incoming)) + .zip_longest( + other + .inner + .dag + .edges_directed(other_index, Direction::Incoming), + ) { match pair { EitherOrBoth::Both(left, right) if edgeref_eq(left, right) => (), @@ -2778,9 +2638,15 @@ impl DAGCircuit { } } for pair in self + .inner .dag .edges_directed(self_index, Direction::Outgoing) - .zip_longest(other.dag.edges_directed(other_index, Direction::Outgoing)) + .zip_longest( + other + .inner + .dag + .edges_directed(other_index, Direction::Outgoing), + ) { match pair { EitherOrBoth::Both(left, right) if edgeref_eq(left, right) => (), @@ -2809,14 +2675,16 @@ impl DAGCircuit { reverse: bool, ) -> PyResult> { let nodes: PyResult> = if let Some(key) = key { - self.topological_key_sort(py, &key, reverse)? - .map(|node| self.get_node(py, node)) + self.inner + .topological_key_sort(py, &key, reverse)? + .map(|node| self.inner.get_node(py, node)) .collect() } else { // Good path, using interner IDs. - self.topological_nodes(reverse) + self.inner + .topological_nodes(reverse) .into_iter() - .map(|n| self.get_node(py, n)) + .map(|n| self.inner.get_node(py, n)) .collect() }; @@ -2847,16 +2715,18 @@ impl DAGCircuit { reverse: bool, ) -> PyResult> { let nodes: PyResult> = if let Some(key) = key { - self.topological_key_sort(py, &key, reverse)? - .filter_map(|node| match self.dag.node_weight(node) { - Some(NodeType::Operation(_)) => Some(self.get_node(py, node)), + self.inner + .topological_key_sort(py, &key, reverse)? + .filter_map(|node| match self.inner.dag.node_weight(node) { + Some(NodeType::Operation(_)) => Some(self.inner.get_node(py, node)), _ => None, }) .collect() } else { // Good path, using interner IDs. - self.topological_op_nodes(reverse) - .map(|n| self.get_node(py, n)) + self.inner + .topological_op_nodes(reverse) + .map(|n| self.inner.get_node(py, n)) .collect() }; @@ -2923,12 +2793,18 @@ impl DAGCircuit { for (bit, index) in wire_pos_map.iter() { if bit.cast::().is_ok() { qubit_pos_map.insert( - self.qubits.find(&bit.extract::()?).unwrap(), + self.inner + .qubits + .find(&bit.extract::()?) + .unwrap(), index.extract()?, ); } else if bit.cast::().is_ok() { clbit_pos_map.insert( - self.clbits.find(&bit.extract::()?).unwrap(), + self.inner + .clbits + .find(&bit.extract::()?) + .unwrap(), index.extract()?, ); } else { @@ -2940,8 +2816,8 @@ impl DAGCircuit { let block_ids: Vec<_> = node_block.iter().map(|n| n.node.unwrap()).collect(); let py_op = op.extract::>()?; - let params = self.take_parameter_blocks(py_op.params); - let new_node = self.replace_block( + let params = self.inner.take_parameter_blocks(py_op.params); + let new_node = self.inner.replace_block( &block_ids, py_op.operation, params, @@ -2950,7 +2826,7 @@ impl DAGCircuit { &qubit_pos_map, &clbit_pos_map, )?; - self.get_node(py, new_node) + self.inner.get_node(py, new_node) } /// Replace one node with dag. @@ -2978,7 +2854,7 @@ impl DAGCircuit { &mut self, py: Python, node: &Bound, - input_dag: &DAGCircuit, + input_dag: &PyDAGCircuit, wires: Option>, propagate_condition: Option, ) -> PyResult> { @@ -3000,7 +2876,7 @@ impl DAGCircuit { Err(_) => return Err(DAGCircuitError::new_err("expected node DAGOpNode")), }; - let node = match &self.dag[node_index] { + let node = match &self.inner.dag[node_index] { NodeType::Operation(op) => op.clone(), _ => return Err(DAGCircuitError::new_err("expected node")), }; @@ -3011,9 +2887,10 @@ impl DAGCircuit { // if we had some special tracking to "deduplicate" Python instances that we've seen // before. let block_map = input_dag + .inner .blocks() .items() - .map(|(index, block)| (index, self.add_block(block.clone()))) + .map(|(index, block)| (index, self.inner.add_block(block.clone()))) .collect(); type WireMapsTuple = ( @@ -3024,19 +2901,22 @@ impl DAGCircuit { let build_wire_map = |wires: &Bound| -> PyResult { let qargs_list: Vec<&ShareableQubit> = self + .inner .qubits - .map_indices(self.qargs_interner.get(node.qubits)) + .map_indices(self.inner.qargs_interner.get(node.qubits)) .collect(); let mut cargs_list: Vec<&ShareableClbit> = self + .inner .clbits - .map_indices(self.cargs_interner.get(node.clbits)) + .map_indices(self.inner.cargs_interner.get(node.clbits)) .collect(); let cargs_set: HashSet<&ShareableClbit> = HashSet::from_iter(cargs_list.iter().cloned()); - if self.may_have_additional_wires(&node) { - let (add_cargs, _add_vars) = Python::attach(|py| self.additional_wires(py, &node))?; + if self.inner.may_have_additional_wires(&node) { + let (add_cargs, _add_vars) = + Python::attach(|py| self.inner.additional_wires(py, &node))?; for wire in add_cargs { - let clbit = self.clbits.get(wire).unwrap(); + let clbit = self.inner.clbits.get(wire).unwrap(); if !cargs_set.contains(clbit) { cargs_list.push(clbit); } @@ -3061,10 +2941,11 @@ impl DAGCircuit { unreachable!() } let input_qubit: Qubit = input_dag + .inner .qubits .find(&wire.extract::()?) .unwrap(); - let self_qubit: Qubit = self.qubits.find(qargs_list[index]).unwrap(); + let self_qubit: Qubit = self.inner.qubits.find(qargs_list[index]).unwrap(); qubit_wire_map.insert(input_qubit, self_qubit); } else if wire.cast::().is_ok() { if index < qargs_len { @@ -3072,10 +2953,14 @@ impl DAGCircuit { } clbit_wire_map.insert( input_dag + .inner .clbits .find(&wire.extract::()?) .unwrap(), - self.clbits.find(cargs_list[index - qargs_len]).unwrap(), + self.inner + .clbits + .find(cargs_list[index - qargs_len]) + .unwrap(), ); } else { return Err(DAGCircuitError::new_err( @@ -3100,20 +2985,24 @@ impl DAGCircuit { if source_wire.cast::().is_ok() { qubit_wire_map.insert( input_dag + .inner .qubits .find(&source_wire.extract::()?) .unwrap(), - self.qubits + self.inner + .qubits .find(&target_wire.extract::()?) .unwrap(), ); } else if source_wire.cast::().is_ok() { clbit_wire_map.insert( input_dag + .inner .clbits .find(&source_wire.extract::()?) .unwrap(), - self.clbits + self.inner + .clbits .find(&target_wire.extract::()?) .unwrap(), ); @@ -3144,14 +3033,19 @@ impl DAGCircuit { } }; - let input_dag_var_set: HashSet<&expr::Var> = - input_dag.vars_stretches.vars().objects().iter().collect(); + let input_dag_var_set: HashSet<&expr::Var> = input_dag + .inner + .vars_stretches + .vars() + .objects() + .iter() + .collect(); - let node_vars = if self.may_have_additional_wires(&node) { - let (_additional_clbits, additional_vars) = self.additional_wires(py, &node)?; + let node_vars = if self.inner.may_have_additional_wires(&node) { + let (_additional_clbits, additional_vars) = self.inner.additional_wires(py, &node)?; let var_set: HashSet<&expr::Var> = additional_vars .into_iter() - .map(|v| self.vars_stretches.vars().get(v).unwrap()) + .map(|v| self.inner.vars_stretches.vars().get(v).unwrap()) .collect(); if input_dag_var_set.difference(&var_set).count() > 0 { return Err(DAGCircuitError::new_err(format!( @@ -3165,31 +3059,39 @@ impl DAGCircuit { }; for contracted_var in node_vars.difference(&input_dag_var_set) { let pred = self + .inner .dag .edges_directed(node_index, Incoming) .find(|edge| { if let Wire::Var(var) = edge.weight() { - *contracted_var == self.vars_stretches.vars().get(*var).unwrap() + *contracted_var == self.inner.vars_stretches.vars().get(*var).unwrap() } else { false } }) .unwrap(); let succ = self + .inner .dag .edges_directed(node_index, Outgoing) .find(|edge| { if let Wire::Var(var) = edge.weight() { - *contracted_var == self.vars_stretches.vars().get(*var).unwrap() + *contracted_var == self.inner.vars_stretches.vars().get(*var).unwrap() } else { false } }) .unwrap(); - self.dag.add_edge( + self.inner.dag.add_edge( pred.source(), succ.target(), - Wire::Var(self.vars_stretches.vars().find(contracted_var).unwrap()), + Wire::Var( + self.inner + .vars_stretches + .vars() + .find(contracted_var) + .unwrap(), + ), ); } @@ -3204,9 +3106,9 @@ impl DAGCircuit { // It doesn't make sense to try and propagate a condition from a control-flow op; a // replacement for the control-flow op should implement the operation completely. - let node_map = self.substitute_node_with_dag( + let node_map = self.inner.substitute_node_with_dag( node_index, - input_dag, + &input_dag.inner, Some(&qubit_wire_map), Some(&clbit_wire_map), Some(&var_map), @@ -3215,7 +3117,7 @@ impl DAGCircuit { let out_dict = PyDict::new(py); for (old_index, new_index) in node_map { - out_dict.set_item(old_index.index(), self.get_node(py, new_index)?)?; + out_dict.set_item(old_index.index(), self.inner.get_node(py, new_index)?)?; } Ok(out_dict.unbind()) } @@ -3272,7 +3174,7 @@ impl DAGCircuit { }; let py = op.py(); let node_index = node.as_ref().node.unwrap(); - self.substitute_node_with_py_op(node_index, op)?; + self.inner.substitute_node_with_py_op(node_index, op)?; if inplace { let temp = op.extract::>()?; node.instruction.operation = temp.operation; @@ -3284,7 +3186,7 @@ impl DAGCircuit { } node.into_py_any(py) } else { - self.get_node(py, node_index) + self.inner.get_node(py, node_index) } } @@ -3309,11 +3211,13 @@ impl DAGCircuit { remove_idle_qubits: bool, vars_mode: VarsMode, ) -> PyResult> { - let connected_components = rustworkx_core::connectivity::connected_components(&self.dag); + let connected_components = + rustworkx_core::connectivity::connected_components(&self.inner.dag); let dags = PyList::empty(py); for comp_nodes in connected_components.iter() { - let mut new_dag = self.copy_empty_like(vars_mode, BlocksMode::Drop); + let mut new_py_dag = self.py_copy_empty_like(vars_mode); + let new_dag: &mut DAGCircuit = &mut new_py_dag.inner; new_dag.global_phase = Param::Float(0.); // A map from nodes in the this DAGCircuit to nodes in the new dag. Used for adding edges @@ -3324,7 +3228,7 @@ impl DAGCircuit { // Adding the nodes to the new dag let mut non_classical = false; for node in comp_nodes { - match self.dag.node_weight(*node) { + match self.inner.dag.node_weight(*node) { Some(w) => match w { NodeType::ClbitIn(b) => { let clbit_in = new_dag.clbit_io_map[b.index()][0]; @@ -3353,8 +3257,9 @@ impl DAGCircuit { node_map.insert(*node, var_out); } NodeType::Operation(pi) => { - let pi = block_map - .map_instruction(pi, |b| new_dag.add_block(self.blocks[b].clone())); + let pi = block_map.map_instruction(pi, |b| { + new_dag.add_block(self.inner.blocks[b].clone()) + }); new_dag.track_instruction(&pi); let new_node = new_dag.dag.add_node(NodeType::Operation(pi)); node_map.insert(*node, new_node); @@ -3369,7 +3274,7 @@ impl DAGCircuit { } let node_filter = |node: NodeIndex| -> bool { node_map.contains_key(&node) }; - let filtered = NodeFiltered(&self.dag, node_filter); + let filtered = NodeFiltered(&self.inner.dag, node_filter); // Remove the edges added by copy_empty_like (as idle wires) to avoid duplication new_dag.dag.clear_edges(); @@ -3411,8 +3316,7 @@ impl DAGCircuit { } } if remove_idle_qubits { - let idle_wires: Vec> = new_dag - .idle_wires(py, None)? + let idle_wires: Vec> = idle_wires(new_dag, py, None)? .into_bound(py) .map(|q| q.unwrap()) .filter(|e| e.cast::().is_ok()) @@ -3420,6 +3324,7 @@ impl DAGCircuit { let qubits = PyTuple::new(py, idle_wires)?; let bit_iter = match self + .inner .qubits .map_objects(qubits.iter().map(|x| x.extract().unwrap())) { @@ -3433,7 +3338,7 @@ impl DAGCircuit { new_dag.remove_qubits(bit_iter)?; // TODO: this does not really work, some issue with remove_qubits itself } - dags.append(pyo3::Py::new(py, new_dag)?)?; + dags.append(pyo3::Py::new(py, new_py_dag)?)?; } Ok(dags.unbind()) @@ -3452,9 +3357,13 @@ impl DAGCircuit { let node2 = node2.node.unwrap(); // Check that both nodes correspond to operations - if !matches!(self.dag.node_weight(node1).unwrap(), NodeType::Operation(_)) - || !matches!(self.dag.node_weight(node2).unwrap(), NodeType::Operation(_)) - { + if !matches!( + self.inner.dag.node_weight(node1).unwrap(), + NodeType::Operation(_) + ) || !matches!( + self.inner.dag.node_weight(node2).unwrap(), + NodeType::Operation(_) + ) { return Err(DAGCircuitError::new_err( "Nodes to swap are not both DAGOpNodes", )); @@ -3463,6 +3372,7 @@ impl DAGCircuit { // Gather all wires connecting node1 and node2. // This functionality was extracted from rustworkx's 'get_edge_data' let wires: Vec = self + .inner .dag .edges(node1) .filter(|edge| edge.target() == node2) @@ -3481,7 +3391,7 @@ impl DAGCircuit { // - Outgoing -> child -> outputs (child_edge_id, child_target_node_id) // This functionality was inspired in rustworkx's 'find_predecessors_by_edge' and 'find_successors_by_edge'. let directed_edge_for_wire = |node: NodeIndex, direction: Direction, wire: Wire| { - for edge in self.dag.edges_directed(node, direction) { + for edge in self.inner.dag.edges_directed(node, direction) { if wire == *edge.weight() { match direction { Incoming => return Some((edge.id(), edge.source())), @@ -3510,12 +3420,12 @@ impl DAGCircuit { for (wire, (node1_to_node2, _), (parent_to_node1, parent), (node2_to_child, child)) in relevant_edges { - self.dag.remove_edge(parent_to_node1); - self.dag.add_edge(parent, node2, wire); - self.dag.remove_edge(node1_to_node2); - self.dag.add_edge(node2, node1, wire); - self.dag.remove_edge(node2_to_child); - self.dag.add_edge(node1, child, wire); + self.inner.dag.remove_edge(parent_to_node1); + self.inner.dag.add_edge(parent, node2, wire); + self.inner.dag.remove_edge(node1_to_node2); + self.inner.dag.add_edge(node2, node1, wire); + self.inner.dag.remove_edge(node2_to_child); + self.inner.dag.add_edge(node1, child, wire); } Ok(()) } @@ -3528,7 +3438,7 @@ impl DAGCircuit { /// Returns: /// node: the node. fn node(&self, py: Python, node_id: isize) -> PyResult> { - self.get_node(py, NodeIndex::new(node_id as usize)) + self.inner.get_node(py, NodeIndex::new(node_id as usize)) } /// Iterator for node values. @@ -3537,9 +3447,10 @@ impl DAGCircuit { /// node: the node. fn nodes(&self, py: Python) -> PyResult> { let result: PyResult> = self + .inner .dag .node_references() - .map(|(node, weight)| self.unpack_into(py, node, weight)) + .map(|(node, weight)| self.inner.unpack_into(py, node, weight)) .collect(); let tup = PyTuple::new(py, result?)?; Ok(tup.into_any().try_iter().unwrap().unbind()) @@ -3565,7 +3476,7 @@ impl DAGCircuit { }; let actual_nodes: Vec<_> = match nodes { - None => self.dag.node_indices().collect(), + None => self.inner.dag.node_indices().collect(), Some(nodes) => { let mut out = Vec::new(); if let Ok(node) = get_node_index(&nodes) { @@ -3581,18 +3492,25 @@ impl DAGCircuit { let mut edges = Vec::new(); for node in actual_nodes { - for edge in self.dag.edges_directed(node, Outgoing) { + for edge in self.inner.dag.edges_directed(node, Outgoing) { edges.push(( - self.get_node(py, edge.source())?, - self.get_node(py, edge.target())?, + self.inner.get_node(py, edge.source())?, + self.inner.get_node(py, edge.target())?, match edge.weight() { - Wire::Qubit(qubit) => { - self.qubits.get(*qubit).unwrap().into_bound_py_any(py)? - } - Wire::Clbit(clbit) => { - self.clbits.get(*clbit).unwrap().into_bound_py_any(py)? - } + Wire::Qubit(qubit) => self + .inner + .qubits + .get(*qubit) + .unwrap() + .into_bound_py_any(py)?, + Wire::Clbit(clbit) => self + .inner + .clbits + .get(*clbit) + .unwrap() + .into_bound_py_any(py)?, Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -3633,7 +3551,7 @@ impl DAGCircuit { } else { true }; - for (node, weight) in self.dag.node_references() { + for (node, weight) in self.inner.dag.node_references() { if let NodeType::Operation(packed) = &weight { if !include_directives && packed.op.directive() { continue; @@ -3645,10 +3563,10 @@ impl DAGCircuit { if !(filter_is_nonstandard && packed.op.try_standard_gate().is_some()) && packed.op.py_op_is_instance(op_type)? { - nodes.push(self.unpack_into(py, node, weight)?); + nodes.push(self.inner.unpack_into(py, node, weight)?); } } else { - nodes.push(self.unpack_into(py, node, weight)?); + nodes.push(self.inner.unpack_into(py, node, weight)?); } } } @@ -3660,10 +3578,11 @@ impl DAGCircuit { /// Returns: /// list[DAGOpNode]: The list of dag nodes containing control flow ops. fn control_flow_op_nodes(&self, py: Python) -> PyResult>> { - if !self.has_control_flow() { + if !self.inner.has_control_flow() { return Ok(vec![]); } - self.dag + self.inner + .dag .node_references() .filter_map(|(node_index, node_type)| match node_type { NodeType::Operation(node) => { @@ -3673,7 +3592,7 @@ impl DAGCircuit { ControlFlow::BreakLoop | ControlFlow::ContinueLoop ) }) { - Some(self.unpack_into(py, node_index, node_type)) + Some(self.inner.unpack_into(py, node_index, node_type)) } else { None } @@ -3688,7 +3607,8 @@ impl DAGCircuit { /// Returns: /// list[DAGOpNode]: the list of DAGOpNodes that represent gates. fn gate_nodes(&self, py: Python) -> PyResult>> { - self.dag + self.inner + .dag .node_references() .filter_map(|(node, weight)| match weight { NodeType::Operation(packed) => match packed.op.view() { @@ -3696,7 +3616,9 @@ impl DAGCircuit { kind: PyOpKind::Gate, .. }) - | OperationRef::StandardGate(_) => Some(self.unpack_into(py, node, weight)), + | OperationRef::StandardGate(_) => { + Some(self.inner.unpack_into(py, node, weight)) + } _ => None, }, _ => None, @@ -3712,10 +3634,10 @@ impl DAGCircuit { names_set.insert(name_obj.extract::()?); } let mut result: Vec> = Vec::new(); - for (id, weight) in self.dag.node_references() { + for (id, weight) in self.inner.dag.node_references() { if let NodeType::Operation(packed) = weight { if names_set.contains(packed.op.name()) { - result.push(self.unpack_into(py, id, weight)?); + result.push(self.inner.unpack_into(py, id, weight)?); } } } @@ -3725,23 +3647,24 @@ impl DAGCircuit { /// Get list of 2 qubit operations. Ignore directives like snapshot and barrier. #[pyo3(name = "two_qubit_ops")] pub fn py_two_qubit_ops(&self, py: Python) -> PyResult>> { - self.two_qubit_ops() - .map(|(index, _)| self.unpack_into(py, index, &self.dag[index])) + self.inner + .two_qubit_ops() + .map(|(index, _)| self.inner.unpack_into(py, index, &self.inner.dag[index])) .collect() } /// Get list of 3+ qubit operations. Ignore directives like snapshot and barrier. fn multi_qubit_ops(&self, py: Python) -> PyResult>> { let mut nodes = Vec::new(); - for (node, weight) in self.dag.node_references() { + for (node, weight) in self.inner.dag.node_references() { if let NodeType::Operation(packed) = weight { if packed.op.directive() { continue; } - let qargs = self.qargs_interner.get(packed.qubits); + let qargs = self.inner.qargs_interner.get(packed.qubits); if qargs.len() >= 3 { - nodes.push(self.unpack_into(py, node, weight)?); + nodes.push(self.inner.unpack_into(py, node, weight)?); } } } @@ -3751,12 +3674,12 @@ impl DAGCircuit { /// Returns the longest path in the dag as a list of DAGOpNodes, DAGInNodes, and DAGOutNodes. fn longest_path(&self, py: Python) -> PyResult>> { let weight_fn = |_| -> Result { Ok(1) }; - match rustworkx_core::dag_algo::longest_path(&self.dag, weight_fn).unwrap() { + match rustworkx_core::dag_algo::longest_path(&self.inner.dag, weight_fn).unwrap() { Some(res) => res.0, None => panic!("not a DAG"), } .into_iter() - .map(|node_index| self.get_node(py, node_index)) + .map(|node_index| self.inner.get_node(py, node_index)) .collect() } @@ -3765,8 +3688,9 @@ impl DAGCircuit { #[pyo3(name = "successors")] fn py_successors(&self, py: Python, node: &DAGNode) -> PyResult> { let successors: PyResult> = self + .inner .successors(node.node.unwrap()) - .map(|i| self.get_node(py, i)) + .map(|i| self.inner.get_node(py, i)) .collect(); Ok(PyTuple::new(py, successors?)? .into_any() @@ -3780,8 +3704,9 @@ impl DAGCircuit { #[pyo3(name = "predecessors")] fn py_predecessors(&self, py: Python, node: &DAGNode) -> PyResult> { let predecessors: PyResult> = self + .inner .predecessors(node.node.unwrap()) - .map(|i| self.get_node(py, i)) + .map(|i| self.inner.get_node(py, i)) .collect(); Ok(PyTuple::new(py, predecessors?)? .into_any() @@ -3793,11 +3718,12 @@ impl DAGCircuit { /// Returns iterator of "op" successors of a node in the dag. fn op_successors(&self, py: Python, node: &DAGNode) -> PyResult> { let predecessors: PyResult> = self + .inner .dag .neighbors_directed(node.node.unwrap(), Outgoing) .unique() - .filter_map(|i| match self.dag[i] { - NodeType::Operation(_) => Some(self.get_node(py, i)), + .filter_map(|i| match self.inner.dag[i] { + NodeType::Operation(_) => Some(self.inner.get_node(py, i)), _ => None, }) .collect(); @@ -3811,11 +3737,12 @@ impl DAGCircuit { /// Returns the iterator of "op" predecessors of a node in the dag. fn op_predecessors(&self, py: Python, node: &DAGNode) -> PyResult> { let predecessors: PyResult> = self + .inner .dag .neighbors_directed(node.node.unwrap(), Incoming) .unique() - .filter_map(|i| match self.dag[i] { - NodeType::Operation(_) => Some(self.get_node(py, i)), + .filter_map(|i| match self.inner.dag[i] { + NodeType::Operation(_) => Some(self.inner.get_node(py, i)), _ => None, }) .collect(); @@ -3828,14 +3755,16 @@ impl DAGCircuit { /// Checks if a second node is in the successors of node. fn is_successor(&self, node: &DAGNode, node_succ: &DAGNode) -> bool { - self.dag + self.inner + .dag .find_edge(node.node.unwrap(), node_succ.node.unwrap()) .is_some() } /// Checks if a second node is in the predecessors of node. fn is_predecessor(&self, node: &DAGNode, node_pred: &DAGNode) -> bool { - self.dag + self.inner + .dag .find_edge(node_pred.node.unwrap(), node.node.unwrap()) .is_some() } @@ -3845,8 +3774,9 @@ impl DAGCircuit { #[pyo3(name = "quantum_predecessors")] fn py_quantum_predecessors(&self, py: Python, node: &DAGNode) -> PyResult> { let predecessors: PyResult> = self + .inner .quantum_predecessors(node.node.unwrap()) - .map(|i| self.get_node(py, i)) + .map(|i| self.inner.get_node(py, i)) .collect(); Ok(PyTuple::new(py, predecessors?)? .into_any() @@ -3860,8 +3790,9 @@ impl DAGCircuit { #[pyo3(name = "quantum_successors")] fn py_quantum_successors(&self, py: Python, node: &DAGNode) -> PyResult> { let successors: PyResult> = self + .inner .quantum_successors(node.node.unwrap()) - .map(|i| self.get_node(py, i)) + .map(|i| self.inner.get_node(py, i)) .collect(); Ok(PyTuple::new(py, successors?)? .into_any() @@ -3873,13 +3804,15 @@ impl DAGCircuit { /// Returns iterator of the predecessors of a node that are /// connected by a classical edge as DAGOpNodes and DAGInNodes. fn classical_predecessors(&self, py: Python, node: &DAGNode) -> PyResult> { - let edges = self.dag.edges_directed(node.node.unwrap(), Incoming); + let edges = self.inner.dag.edges_directed(node.node.unwrap(), Incoming); let filtered = edges.filter_map(|e| match e.weight() { Wire::Qubit(_) => None, _ => Some(e.source()), }); - let predecessors: PyResult> = - filtered.unique().map(|i| self.get_node(py, i)).collect(); + let predecessors: PyResult> = filtered + .unique() + .map(|i| self.inner.get_node(py, i)) + .collect(); Ok(PyTuple::new(py, predecessors?)? .into_any() .try_iter() @@ -3895,8 +3828,9 @@ impl DAGCircuit { #[pyo3(name = "ancestors")] fn py_ancestors(&self, py: Python, node: &DAGNode) -> PyResult> { let ancestors: PyResult>> = self + .inner .ancestors(node.node.unwrap()) - .map(|node| self.get_node(py, node)) + .map(|node| self.inner.get_node(py, node)) .collect(); Ok(PySet::new(py, &ancestors?)?.unbind()) } @@ -3909,8 +3843,9 @@ impl DAGCircuit { #[pyo3(name = "descendants")] fn py_descendants(&self, py: Python, node: &DAGNode) -> PyResult> { let descendants: PyResult>> = self + .inner .descendants(node.node.unwrap()) - .map(|node| self.get_node(py, node)) + .map(|node| self.inner.get_node(py, node)) .collect(); Ok(PySet::new(py, &descendants?)?.unbind()) } @@ -3922,13 +3857,14 @@ impl DAGCircuit { type PyIteratorVec = Vec<(Py, Vec>)>; let successor_index: PyResult = self + .inner .bfs_successors(node.node.unwrap()) .map(|(node, nodes)| -> PyResult<(Py, Vec>)> { Ok(( - self.get_node(py, node)?, + self.inner.get_node(py, node)?, nodes .iter() - .map(|sub_node| self.get_node(py, *sub_node)) + .map(|sub_node| self.inner.get_node(py, *sub_node)) .collect::>>()?, )) }) @@ -3942,13 +3878,15 @@ impl DAGCircuit { /// Returns iterator of the successors of a node that are /// connected by a classical edge as DAGOpNodes and DAGOutNodes. fn classical_successors(&self, py: Python, node: &DAGNode) -> PyResult> { - let edges = self.dag.edges_directed(node.node.unwrap(), Outgoing); + let edges = self.inner.dag.edges_directed(node.node.unwrap(), Outgoing); let filtered = edges.filter_map(|e| match e.weight() { Wire::Qubit(_) => None, _ => Some(e.target()), }); - let predecessors: PyResult> = - filtered.unique().map(|i| self.get_node(py, i)).collect(); + let predecessors: PyResult> = filtered + .unique() + .map(|i| self.inner.get_node(py, i)) + .collect(); Ok(PyTuple::new(py, predecessors?)? .into_any() .try_iter() @@ -3966,82 +3904,96 @@ impl DAGCircuit { Err(_) => return Err(DAGCircuitError::new_err("Node not an DAGOpNode")), }; let index = node.as_ref().node.unwrap(); - if self.dag.node_weight(index).is_none() { + if self.inner.dag.node_weight(index).is_none() { return Err(DAGCircuitError::new_err("Node not in DAG")); } - self.remove_op_node(index); + self.inner.remove_op_node(index); Ok(()) } /// Remove all of the ancestor operation nodes of node. fn remove_ancestors_of(&mut self, node: &DAGNode) { - let ancestors: Vec<_> = core_ancestors(&self.dag, node.node.unwrap()) + let ancestors: Vec<_> = core_ancestors(&self.inner.dag, node.node.unwrap()) .filter(|next| { next != &node.node.unwrap() - && matches!(self.dag.node_weight(*next), Some(NodeType::Operation(_))) + && matches!( + self.inner.dag.node_weight(*next), + Some(NodeType::Operation(_)) + ) }) .collect(); for a in ancestors { - self.dag.remove_node(a); + self.inner.dag.remove_node(a); } } /// Remove all of the descendant operation nodes of node. fn remove_descendants_of(&mut self, node: &DAGNode) { - let descendants: Vec<_> = core_descendants(&self.dag, node.node.unwrap()) + let descendants: Vec<_> = core_descendants(&self.inner.dag, node.node.unwrap()) .filter(|next| { next != &node.node.unwrap() - && matches!(self.dag.node_weight(*next), Some(NodeType::Operation(_))) + && matches!( + self.inner.dag.node_weight(*next), + Some(NodeType::Operation(_)) + ) }) .collect(); for d in descendants { - self.dag.remove_node(d); + self.inner.dag.remove_node(d); } } /// Remove all of the non-ancestors operation nodes of node. fn remove_nonancestors_of(&mut self, node: &DAGNode) { - let ancestors: HashSet<_> = core_ancestors(&self.dag, node.node.unwrap()) + let ancestors: HashSet<_> = core_ancestors(&self.inner.dag, node.node.unwrap()) .filter(|next| { next != &node.node.unwrap() - && matches!(self.dag.node_weight(*next), Some(NodeType::Operation(_))) + && matches!( + self.inner.dag.node_weight(*next), + Some(NodeType::Operation(_)) + ) }) .collect(); let non_ancestors: Vec<_> = self + .inner .dag .node_indices() .filter(|node_id| !ancestors.contains(node_id)) .collect(); for na in non_ancestors { - self.dag.remove_node(na); + self.inner.dag.remove_node(na); } } /// Remove all of the non-descendants operation nodes of node. fn remove_nondescendants_of(&mut self, node: &DAGNode) { - let descendants: HashSet<_> = core_descendants(&self.dag, node.node.unwrap()) + let descendants: HashSet<_> = core_descendants(&self.inner.dag, node.node.unwrap()) .filter(|next| { next != &node.node.unwrap() - && matches!(self.dag.node_weight(*next), Some(NodeType::Operation(_))) + && matches!( + self.inner.dag.node_weight(*next), + Some(NodeType::Operation(_)) + ) }) .collect(); let non_descendants: Vec<_> = self + .inner .dag .node_indices() .filter(|node_id| !descendants.contains(node_id)) .collect(); for nd in non_descendants { - self.dag.remove_node(nd); + self.inner.dag.remove_node(nd); } } /// Return a list of op nodes in the first layer of this dag. #[pyo3(name = "front_layer")] fn py_front_layer(&self, py: Python) -> PyResult> { - let native_front_layer = self.front_layer(); + let native_front_layer = self.inner.front_layer(); let front_layer_list = PyList::empty(py); for node in native_front_layer { - front_layer_list.append(self.get_node(py, node)?)?; + front_layer_list.append(self.inner.get_node(py, node)?)?; } Ok(front_layer_list.into()) } @@ -4065,7 +4017,7 @@ impl DAGCircuit { #[pyo3(signature = (*, vars_mode=VarsMode::Captures))] fn layers(&self, py: Python, vars_mode: VarsMode) -> PyResult> { let layer_list = PyList::empty(py); - let mut graph_layers = self.multigraph_layers(); + let mut graph_layers = self.inner.multigraph_layers(); if graph_layers.next().is_none() { return Ok(PyIterator::from_object(&layer_list)?.into()); } @@ -4080,7 +4032,12 @@ impl DAGCircuit { // Get the op nodes from the layer, removing any input and output nodes. let mut op_nodes: Vec<(&PackedInstruction, &NodeIndex)> = graph_layer .iter() - .filter_map(|node| self.dag.node_weight(*node).map(|dag_node| (dag_node, node))) + .filter_map(|node| { + self.inner + .dag + .node_weight(*node) + .map(|dag_node| (dag_node, node)) + }) .filter_map(|(node, index)| match node { NodeType::Operation(oper) => Some((oper, index)), _ => None, @@ -4092,22 +4049,25 @@ impl DAGCircuit { return Ok(PyIterator::from_object(&layer_list)?.into()); } - let mut new_layer = self.copy_empty_like(vars_mode, BlocksMode::Drop); + let mut new_layer = self.py_copy_empty_like(vars_mode); let mut block_map = BlockMapper::new(); let data: Vec<_> = op_nodes .iter() .map(|(inst, _)| { - block_map.map_instruction(inst, |b| new_layer.add_block(self.blocks[b].clone())) + block_map.map_instruction(inst, |b| { + new_layer.inner.add_block(self.inner.blocks[b].clone()) + }) }) .collect(); - new_layer.extend(data)?; + new_layer.inner.extend(data)?; - let support_iter = new_layer.op_nodes(false).map(|(_, instruction)| { + let support_iter = new_layer.inner.op_nodes(false).map(|(_, instruction)| { PyTuple::new( py, new_layer + .inner .qubits - .map_indices(new_layer.qargs_interner.get(instruction.qubits)), + .map_indices(new_layer.inner.qargs_interner.get(instruction.qubits)), ) .unwrap() }); @@ -4129,28 +4089,29 @@ impl DAGCircuit { #[pyo3(signature = (*, vars_mode=VarsMode::Captures))] fn serial_layers(&self, py: Python, vars_mode: VarsMode) -> PyResult> { let layer_list = PyList::empty(py); - for next_node in self.topological_op_nodes(false) { - let retrieved_node: &PackedInstruction = match self.dag.node_weight(next_node) { + for next_node in self.inner.topological_op_nodes(false) { + let retrieved_node: &PackedInstruction = match self.inner.dag.node_weight(next_node) { Some(NodeType::Operation(node)) => node, _ => unreachable!("A non-operation node was obtained from topological_op_nodes."), }; - let mut new_layer = self.copy_empty_like(vars_mode, BlocksMode::Drop); + let mut new_layer = self.py_copy_empty_like(vars_mode); let mut block_map = BlockMapper::new(); // Save the support of the operation we add to the layer let support_list = PyList::empty(py); let qubits = PyTuple::new( py, - self.qargs_interner + self.inner + .qargs_interner .get(retrieved_node.qubits) .iter() - .map(|qubit| self.qubits.get(*qubit)), + .map(|qubit| self.inner.qubits.get(*qubit)), )? .unbind(); let inst = block_map.map_instruction(retrieved_node, |b| { - new_layer.add_block(self.blocks[b].clone()) + new_layer.inner.add_block(self.inner.blocks[b].clone()) }); - new_layer.push_back(inst)?; + new_layer.inner.push_back(inst)?; if !retrieved_node.op.directive() { support_list.append(qubits)?; @@ -4170,12 +4131,15 @@ impl DAGCircuit { /// Yield layers of the multigraph. #[pyo3(name = "multigraph_layers")] fn py_multigraph_layers(&self, py: Python) -> PyResult> { - let graph_layers = self.multigraph_layers().map(|layer| -> Vec> { - layer - .into_iter() - .filter_map(|index| self.get_node(py, index).ok()) - .collect() - }); + let graph_layers = self + .inner + .multigraph_layers() + .map(|layer| -> Vec> { + layer + .into_iter() + .filter_map(|index| self.inner.get_node(py, index).ok()) + .collect() + }); let list: Bound = PyList::new(py, graph_layers.collect::>>>())?; Ok(PyIterator::from_object(&list)?.unbind()) } @@ -4199,11 +4163,11 @@ impl DAGCircuit { let out_set = PySet::empty(py)?; - for run in self.collect_runs(name_list_set) { + for run in self.inner.collect_runs(name_list_set) { let run_tuple = PyTuple::new( py, run.into_iter() - .map(|node_index| self.get_node(py, node_index).unwrap()), + .map(|node_index| self.inner.get_node(py, node_index).unwrap()), )?; out_set.add(run_tuple)?; } @@ -4213,14 +4177,14 @@ impl DAGCircuit { /// Return a set of non-conditional runs of 1q "op" nodes. #[pyo3(name = "collect_1q_runs")] fn py_collect_1q_runs(&self, py: Python) -> PyResult> { - match self.collect_1q_runs() { + match self.inner.collect_1q_runs() { Some(runs) => { let runs_iter = runs.map(|node_indices| { PyList::new( py, node_indices .into_iter() - .map(|node_index| self.get_node(py, node_index).unwrap()), + .map(|node_index| self.inner.get_node(py, node_index).unwrap()), ) .unwrap() .unbind() @@ -4240,14 +4204,14 @@ impl DAGCircuit { /// Return a set of non-conditional runs of 2q "op" nodes. #[pyo3(name = "collect_2q_runs")] fn py_collect_2q_runs(&self, py: Python) -> PyResult> { - match self.collect_2q_runs() { + match self.inner.collect_2q_runs() { Some(runs) => { let runs_iter = runs.into_iter().map(|node_indices| { PyList::new( py, node_indices .into_iter() - .map(|node_index| self.get_node(py, node_index).unwrap()), + .map(|node_index| self.inner.get_node(py, node_index).unwrap()), ) .unwrap() .unbind() @@ -4284,13 +4248,13 @@ impl DAGCircuit { ) -> PyResult> { let wire = if wire.cast::().is_ok() { let wire = wire.extract::()?; - self.qubits.find(&wire).map(Wire::Qubit) + self.inner.qubits.find(&wire).map(Wire::Qubit) } else if wire.cast::().is_ok() { let wire = wire.extract::()?; - self.clbits.find(&wire).map(Wire::Clbit) + self.inner.clbits.find(&wire).map(Wire::Clbit) } else { let wire = wire.extract::()?; - self.vars_stretches.vars().find(&wire).map(Wire::Var) + self.inner.vars_stretches.vars().find(&wire).map(Wire::Var) } .ok_or_else(|| { DAGCircuitError::new_err(format!( @@ -4299,9 +4263,10 @@ impl DAGCircuit { })?; let nodes = self + .inner .nodes_on_wire(wire) - .filter(|node| !only_ops || matches!(self.dag[*node], NodeType::Operation(_))) - .map(|n| self.get_node(py, n)) + .filter(|node| !only_ops || matches!(self.inner.dag[*node], NodeType::Operation(_))) + .map(|n| self.inner.get_node(py, n)) .collect::>>()?; Ok(PyTuple::new(py, nodes)?.into_any().try_iter()?.unbind()) } @@ -4318,26 +4283,26 @@ impl DAGCircuit { /// Mapping[str, int]: a mapping of operation names to the number of times it appears. #[pyo3(name = "count_ops", signature = (*, recurse=true))] fn py_count_ops(&self, py: Python, recurse: bool) -> PyResult> { - self.count_ops(recurse)?.into_py_any(py) + self.inner.count_ops(recurse)?.into_py_any(py) } /// Count the occurrences of operation names on the longest path. /// /// Returns a dictionary of counts keyed on the operation name. fn count_ops_longest_path(&self) -> PyResult> { - if self.dag.node_count() == 0 { + if self.inner.dag.node_count() == 0 { return Ok(HashMap::new()); } let weight_fn = |_| -> Result { Ok(1) }; let longest_path = - match rustworkx_core::dag_algo::longest_path(&self.dag, weight_fn).unwrap() { + match rustworkx_core::dag_algo::longest_path(&self.inner.dag, weight_fn).unwrap() { Some(res) => res.0, None => panic!("not a DAG"), }; // Allocate for worst case where all operations are unique let mut op_counts: HashMap<&str, usize> = HashMap::with_capacity(longest_path.len() - 2); for node_index in &longest_path[1..longest_path.len() - 1] { - if let NodeType::Operation(ref packed) = self.dag[*node_index] { + if let NodeType::Operation(ref packed) = self.inner.dag[*node_index] { let name = packed.op.name(); op_counts .entry(name) @@ -4366,12 +4331,13 @@ impl DAGCircuit { fn quantum_causal_cone(&self, py: Python, qubit: &Bound) -> PyResult> { // Retrieve the output node from the qubit let qubit_nat: ShareableQubit = qubit.extract()?; - let output_qubit = self.qubits.find(&qubit_nat).ok_or_else(|| { + let output_qubit = self.inner.qubits.find(&qubit_nat).ok_or_else(|| { DAGCircuitError::new_err(format!( "The given qubit {qubit:?} is not present in the circuit" )) })?; let output_node_index = self + .inner .qubit_io_map .get(output_qubit.index()) .map(|x| x[1]) @@ -4382,7 +4348,8 @@ impl DAGCircuit { })?; let mut qubits_in_cone: HashSet<&Qubit> = HashSet::from([&output_qubit]); - let mut queue: VecDeque = self.quantum_predecessors(output_node_index).collect(); + let mut queue: VecDeque = + self.inner.quantum_predecessors(output_node_index).collect(); // The processed_non_directive_nodes stores the set of processed non-directive nodes. // This is an optimization to avoid considering the same non-directive node multiple @@ -4395,7 +4362,7 @@ impl DAGCircuit { while !queue.is_empty() { let cur_index = queue.pop_front().unwrap(); - if let NodeType::Operation(packed) = self.dag.node_weight(cur_index).unwrap() { + if let NodeType::Operation(packed) = self.inner.dag.node_weight(cur_index).unwrap() { if !packed.op.directive() { // If the operation is not a directive (in particular not a barrier nor a measure), // we do not do anything if it was already processed. Otherwise, we add its qubits @@ -4403,12 +4370,12 @@ impl DAGCircuit { if processed_non_directive_nodes.contains(&cur_index) { continue; } - qubits_in_cone.extend(self.qargs_interner.get(packed.qubits)); + qubits_in_cone.extend(self.inner.qargs_interner.get(packed.qubits)); processed_non_directive_nodes.insert(cur_index); - for pred_index in self.quantum_predecessors(cur_index) { + for pred_index in self.inner.quantum_predecessors(cur_index) { if let NodeType::Operation(_pred_packed) = - self.dag.node_weight(pred_index).unwrap() + self.inner.dag.node_weight(pred_index).unwrap() { queue.push_back(pred_index); } @@ -4417,11 +4384,12 @@ impl DAGCircuit { // Directives (such as barriers and measures) may be defined over all the qubits, // yet not all of these qubits should be considered in the causal cone. So we // only add those predecessors that have qubits in common with qubits_in_cone. - for pred_index in self.quantum_predecessors(cur_index) { + for pred_index in self.inner.quantum_predecessors(cur_index) { if let NodeType::Operation(pred_packed) = - self.dag.node_weight(pred_index).unwrap() + self.inner.dag.node_weight(pred_index).unwrap() { if self + .inner .qargs_interner .get(pred_packed.qubits) .iter() @@ -4436,7 +4404,7 @@ impl DAGCircuit { } let qubits_in_cone_vec: Vec<_> = qubits_in_cone.iter().map(|&&qubit| qubit).collect(); - let elements = self.qubits.map_indices(&qubits_in_cone_vec); + let elements = self.inner.qubits.map_indices(&qubits_in_cone_vec); Ok(PySet::new(py, elements)?.unbind()) } @@ -4519,7 +4487,14 @@ impl DAGCircuit { edge_attrs: Option>, ) -> PyResult { let mut buffer = Vec::::new(); - build_dot(py, self, &mut buffer, graph_attrs, node_attrs, edge_attrs)?; + build_dot( + py, + self.try_read()?, + &mut buffer, + graph_attrs, + node_attrs, + edge_attrs, + )?; Ok(String::from_utf8(buffer)?) } @@ -4529,7 +4504,7 @@ impl DAGCircuit { /// var: the variable to add. #[pyo3(name = "add_input_var")] fn py_add_input_var(&mut self, var: expr::Var) -> PyResult<()> { - self.add_var(var, VarType::Input)?; + self.inner.add_var(var, VarType::Input)?; Ok(()) } @@ -4539,7 +4514,7 @@ impl DAGCircuit { /// var: the variable to add. #[pyo3(name = "add_captured_var")] fn py_add_captured_var(&mut self, var: expr::Var) -> PyResult<()> { - self.add_var(var, VarType::Capture)?; + self.inner.add_var(var, VarType::Capture)?; Ok(()) } @@ -4549,7 +4524,8 @@ impl DAGCircuit { /// stretch: the stretch to add. #[pyo3(name = "add_captured_stretch")] fn py_add_captured_stretch(&mut self, stretch: expr::Stretch) -> PyResult<()> { - self.add_stretch(stretch, StretchType::Capture) + self.inner + .add_stretch(stretch, StretchType::Capture) .map_err(Into::into) } @@ -4559,7 +4535,7 @@ impl DAGCircuit { /// var: the variable to add. #[pyo3(name = "add_declared_var")] fn py_add_declared_var(&mut self, var: expr::Var) -> PyResult<()> { - self.add_var(var, VarType::Declare)?; + self.inner.add_var(var, VarType::Declare)?; Ok(()) } @@ -4569,7 +4545,8 @@ impl DAGCircuit { /// stretch: the stretch to add. #[pyo3(name = "add_declared_stretch")] fn py_add_declared_stretch(&mut self, stretch: expr::Stretch) -> PyResult<()> { - self.add_stretch(stretch, StretchType::Declare) + self.inner + .add_stretch(stretch, StretchType::Declare) .map_err(Into::into) } @@ -4582,37 +4559,37 @@ impl DAGCircuit { /// Number of input classical variables tracked by the circuit. #[getter] fn num_input_vars(&self) -> usize { - self.vars_stretches.num_vars(VarType::Input) + self.inner.num_input_vars() } /// Number of captured classical variables tracked by the circuit. #[getter] fn num_captured_vars(&self) -> usize { - self.vars_stretches.num_vars(VarType::Capture) + self.inner.num_captured_vars() } /// Number of declared local classical variables tracked by the circuit. #[getter] fn num_declared_vars(&self) -> usize { - self.vars_stretches.num_vars(VarType::Declare) + self.inner.num_declared_vars() } /// Total number of stretches tracked by the circuit. #[getter] pub fn num_stretches(&self) -> usize { - self.num_captured_stretches() + self.num_declared_stretches() + self.inner.num_stretches() } /// Number of captured stretches tracked by the circuit. #[getter] fn num_captured_stretches(&self) -> usize { - self.vars_stretches.num_stretches(StretchType::Capture) + self.inner.num_captured_stretches() } /// Number of declared local stretches tracked by the circuit. #[getter] fn num_declared_stretches(&self) -> usize { - self.vars_stretches.num_stretches(StretchType::Declare) + self.inner.num_declared_stretches() } /// Is this realtime variable in the DAG? @@ -4622,10 +4599,10 @@ impl DAGCircuit { #[pyo3(name = "has_var")] fn py_has_var(&self, var: &Bound) -> PyResult { if let Ok(name) = var.extract::() { - Ok(self.vars_stretches.has_var(&name)) + Ok(self.inner.vars_stretches.has_var(&name)) } else { let var = var.extract::()?; - Ok(self.vars_stretches.vars().contains(&var)) + Ok(self.inner.vars_stretches.vars().contains(&var)) } } @@ -4636,10 +4613,10 @@ impl DAGCircuit { #[pyo3(name = "has_stretch")] fn py_has_stretch(&self, stretch: &Bound) -> PyResult { if let Ok(name) = stretch.extract::() { - Ok(self.vars_stretches.has_stretch(&name)) + Ok(self.inner.vars_stretches.has_stretch(&name)) } else { let stretch = stretch.extract::()?; - Ok(self.vars_stretches.stretches().contains(&stretch)) + Ok(self.inner.vars_stretches.stretches().contains(&stretch)) } } @@ -4650,14 +4627,14 @@ impl DAGCircuit { #[pyo3(name = "has_identifier")] fn py_has_identifier(&self, var: &Bound) -> PyResult { if let Ok(name) = var.extract::() { - Ok(self.vars_stretches.has_identifier(&name)) + Ok(self.inner.vars_stretches.has_identifier(&name)) } else if let Ok(var) = var.extract::() { let expr::Var::Standalone { .. } = var else { return Ok(false); }; - Ok(self.vars_stretches.vars().contains(&var)) + Ok(self.inner.vars_stretches.vars().contains(&var)) } else if let Ok(stretch) = var.extract::() { - Ok(self.vars_stretches.stretches().contains(&stretch)) + Ok(self.inner.vars_stretches.stretches().contains(&stretch)) } else { Err(PyValueError::new_err( "identifier must be a name or expression kind Var or Stretch", @@ -4669,7 +4646,8 @@ impl DAGCircuit { fn iter_input_vars(&self, py: Python) -> PyResult> { let result = PySet::new( py, - self.input_vars() + self.inner + .input_vars() .map(|v| v.clone().into_py_any(py).unwrap()), )?; Ok(result.into_any().try_iter()?.unbind()) @@ -4679,7 +4657,8 @@ impl DAGCircuit { fn iter_captured_vars(&self, py: Python) -> PyResult> { let result = PySet::new( py, - self.captured_vars() + self.inner + .captured_vars() .map(|v| v.clone().into_py_any(py).unwrap()), )?; Ok(result.into_any().try_iter()?.unbind()) @@ -4689,7 +4668,8 @@ impl DAGCircuit { fn iter_captured_stretches(&self, py: Python) -> PyResult> { let result = PySet::new( py, - self.captured_stretches() + self.inner + .captured_stretches() .map(|v| v.clone().into_py_any(py).unwrap()), )?; Ok(result.into_any().try_iter()?.unbind()) @@ -4698,10 +4678,10 @@ impl DAGCircuit { /// Iterable over all captured identifiers tracked by the circuit. fn iter_captures(&self, py: Python) -> PyResult> { let out_set = PySet::empty(py)?; - for var in self.captured_vars() { + for var in self.inner.captured_vars() { out_set.add(var.clone())?; } - for stretch in self.captured_stretches() { + for stretch in self.inner.captured_stretches() { out_set.add(stretch.clone())?; } Ok(out_set.into_any().try_iter()?.unbind()) @@ -4711,7 +4691,8 @@ impl DAGCircuit { fn iter_declared_vars(&self, py: Python) -> PyResult> { let result = PySet::new( py, - self.declared_vars() + self.inner + .declared_vars() .map(|v| v.clone().into_py_any(py).unwrap()), )?; Ok(result.into_any().try_iter()?.unbind()) @@ -4721,7 +4702,8 @@ impl DAGCircuit { fn iter_declared_stretches(&self, py: Python) -> PyResult> { let result = PyList::new( py, - self.declared_stretches() + self.inner + .declared_stretches() .map(|v| v.clone().into_py_any(py).unwrap()), )?; Ok(result.into_any().try_iter()?.unbind()) @@ -4730,7 +4712,7 @@ impl DAGCircuit { /// Iterable over all the classical variables tracked by the circuit. fn iter_vars(&self, py: Python) -> PyResult> { let out_set = PySet::empty(py)?; - for var in self.vars_stretches.vars().objects() { + for var in self.inner.vars_stretches.vars().objects() { out_set.add(var.clone())?; } Ok(out_set.into_any().try_iter()?.unbind()) @@ -4739,23 +4721,25 @@ impl DAGCircuit { /// Iterable over all the stretches tracked by the circuit. fn iter_stretches(&self, py: Python) -> PyResult> { let out_set = PySet::empty(py)?; - for stretch in self.vars_stretches.stretches().objects() { + for stretch in self.inner.vars_stretches.stretches().objects() { out_set.add(stretch.clone())?; } Ok(out_set.into_any().try_iter()?.unbind()) } fn _has_edge(&self, source: usize, target: usize) -> bool { - self.dag + self.inner + .dag .contains_edge(NodeIndex::new(source), NodeIndex::new(target)) } fn _is_dag(&self) -> bool { - rustworkx_core::petgraph::algo::toposort(&self.dag, None).is_ok() + rustworkx_core::petgraph::algo::toposort(&self.inner.dag, None).is_ok() } fn _in_edges(&self, py: Python, node_index: usize) -> Vec> { - self.dag + self.inner + .dag .edges_directed(NodeIndex::new(node_index), Incoming) .map(|wire| { ( @@ -4763,12 +4747,13 @@ impl DAGCircuit { wire.target().index(), match wire.weight() { Wire::Qubit(qubit) => { - self.qubits.get(*qubit).into_bound_py_any(py).unwrap() + self.inner.qubits.get(*qubit).into_bound_py_any(py).unwrap() } Wire::Clbit(clbit) => { - self.clbits.get(*clbit).into_bound_py_any(py).unwrap() + self.inner.clbits.get(*clbit).into_bound_py_any(py).unwrap() } Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -4785,7 +4770,8 @@ impl DAGCircuit { } fn _out_edges(&self, py: Python, node_index: usize) -> Vec> { - self.dag + self.inner + .dag .edges_directed(NodeIndex::new(node_index), Outgoing) .map(|wire| { ( @@ -4793,12 +4779,13 @@ impl DAGCircuit { wire.target().index(), match wire.weight() { Wire::Qubit(qubit) => { - self.qubits.get(*qubit).into_bound_py_any(py).unwrap() + self.inner.qubits.get(*qubit).into_bound_py_any(py).unwrap() } Wire::Clbit(clbit) => { - self.clbits.get(*clbit).into_bound_py_any(py).unwrap() + self.inner.clbits.get(*clbit).into_bound_py_any(py).unwrap() } Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -4815,12 +4802,14 @@ impl DAGCircuit { } fn _in_wires(&self, py: Python, node_index: usize) -> Vec> { - self.dag + self.inner + .dag .edges_directed(NodeIndex::new(node_index), Incoming) .map(|wire| match wire.weight() { - Wire::Qubit(qubit) => self.qubits.get(*qubit).into_py_any(py).unwrap(), - Wire::Clbit(clbit) => self.clbits.get(*clbit).into_py_any(py).unwrap(), + Wire::Qubit(qubit) => self.inner.qubits.get(*qubit).into_py_any(py).unwrap(), + Wire::Clbit(clbit) => self.inner.clbits.get(*clbit).into_py_any(py).unwrap(), Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -4832,12 +4821,14 @@ impl DAGCircuit { } fn _out_wires(&self, py: Python, node_index: usize) -> Vec> { - self.dag + self.inner + .dag .edges_directed(NodeIndex::new(node_index), Outgoing) .map(|wire| match wire.weight() { - Wire::Qubit(qubit) => self.qubits.get(*qubit).into_py_any(py).unwrap(), - Wire::Clbit(clbit) => self.clbits.get(*clbit).into_py_any(py).unwrap(), + Wire::Qubit(qubit) => self.inner.qubits.get(*qubit).into_py_any(py).unwrap(), + Wire::Clbit(clbit) => self.inner.clbits.get(*clbit).into_py_any(py).unwrap(), Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -4856,14 +4847,16 @@ impl DAGCircuit { ) -> PyResult>> { let mut result = Vec::new(); for e in self + .inner .dag .edges_directed(NodeIndex::new(node_index), Outgoing) .unique_by(|e| e.id()) { let weight = match e.weight() { - Wire::Qubit(qubit) => self.qubits.get(*qubit).into_py_any(py)?, - Wire::Clbit(clbit) => self.clbits.get(*clbit).into_py_any(py)?, + Wire::Qubit(qubit) => self.inner.qubits.get(*qubit).into_py_any(py)?, + Wire::Clbit(clbit) => self.inner.clbits.get(*clbit).into_py_any(py)?, Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -4871,21 +4864,23 @@ impl DAGCircuit { .into_py_any(py)?, }; if edge_checker.call1((weight,))?.extract::()? { - result.push(self.get_node(py, e.target())?); + result.push(self.inner.get_node(py, e.target())?); } } Ok(result) } fn _edges(&self, py: Python) -> PyResult>> { - self.dag + self.inner + .dag .edge_indices() .map(|index| { - let wire = self.dag.edge_weight(index).unwrap(); + let wire = self.inner.dag.edge_weight(index).unwrap(); match wire { - Wire::Qubit(qubit) => self.qubits.get(*qubit).into_py_any(py), - Wire::Clbit(clbit) => self.clbits.get(*clbit).into_py_any(py), + Wire::Qubit(qubit) => self.inner.qubits.get(*qubit).into_py_any(py), + Wire::Clbit(clbit) => self.inner.clbits.get(*clbit).into_py_any(py), Wire::Var(var) => self + .inner .vars_stretches .vars() .get(*var) @@ -4925,6 +4920,36 @@ impl Default for DAGCircuit { } impl DAGCircuit { + /// Add all wires in a quantum register. + pub fn add_qreg(&mut self, qreg: QuantumRegister) -> Result<(), DAGError> { + self.qregs.add_register(qreg.clone(), true)?; + + for (index, bit) in qreg.bits().enumerate() { + if self.qubits.find(&bit).is_none() { + self.add_qubit_unchecked(bit.clone())?; + } + let locations: &mut BitLocations = + self.qubit_locations.get_mut(&bit).unwrap(); + locations.add_register(qreg.clone(), index); + } + Ok(()) + } + + /// Add all wires in a classical register. + pub fn add_creg(&mut self, creg: ClassicalRegister) -> Result<(), DAGError> { + self.cregs.add_register(creg.clone(), true)?; + + for (index, bit) in creg.bits().enumerate() { + if self.clbits.find(&bit).is_none() { + self.add_clbit_unchecked(bit.clone())?; + } + let locations: &mut BitLocations = + self.clbit_locations.get_mut(&bit).unwrap(); + locations.add_register(creg.clone(), index); + } + Ok(()) + } + /// Gives the DAG ownership of the provided basic block and returns a /// unique identifier that can be used to retrieve a reference to it /// later. @@ -4958,7 +4983,7 @@ impl DAGCircuit { .map(|params| { params .try_map_blocks_ref(|block| { - DAGCircuit::from_circuit_data(block, false, None, None, None, None) + DAGCircuit::from_circuit_data(block, false, None, None) .map(|dag| self.add_block(dag)) }) .map(Box::new) @@ -5000,6 +5025,7 @@ impl DAGCircuit { ControlFlowView::try_from_instruction(instr, &self.blocks) } + // TODO: Move Python auxiliary method /// Build a reference to the Python-space operation object (the `Gate`, etc) packed into an /// instruction. This may construct the reference if the `Instruction` is a standard /// gate or instruction with no already stored operation. @@ -5040,8 +5066,6 @@ impl DAGCircuit { pub fn new() -> Self { DAGCircuit { - name: None, - metadata: None, dag: StableDiGraph::default(), qregs: RegisterData::new(), cregs: RegisterData::new(), @@ -5189,11 +5213,9 @@ impl DAGCircuit { Some(num_edges), Some(num_stretches), ); - target_dag.name.clone_from(&self.name); target_dag.global_phase = self.global_phase.clone(); target_dag.duration.clone_from(&self.duration); target_dag.unit.clone_from(&self.unit); - target_dag.metadata.clone_from(&self.metadata); // We strongly expect the cargs to be copied over verbatim. We don't know about qargs, so // we leave that with its default capacity. target_dag.cargs_interner = self.cargs_interner.clone(); @@ -6183,6 +6205,7 @@ impl DAGCircuit { .filter(|node: &NodeIndex| matches!(&self.dag[*node], NodeType::Operation(_))) } + // TODO: Move Python auxiliary method fn topological_key_sort( &self, py: Python, @@ -6215,6 +6238,10 @@ impl DAGCircuit { .any(|x| self.op_names.contains_key(&x.to_string())) } + pub fn get_node(&self, py: Python, node: NodeIndex) -> PyResult> { + self.unpack_into(py, node, self.dag.node_weight(node).unwrap()) + } + /// Is the given [Wire] idle? /// /// # Panics @@ -6469,10 +6496,6 @@ impl DAGCircuit { Ok(clbit) } - pub fn get_node(&self, py: Python, node: NodeIndex) -> PyResult> { - self.unpack_into(py, node, self.dag.node_weight(node).unwrap()) - } - /// Remove an operation node n. /// /// Add edges from predecessors to successors. @@ -6536,6 +6559,7 @@ impl DAGCircuit { core_bfs_predecessors(&self.dag, node).filter(move |(_, others)| !others.is_empty()) } + // TODO: Move Python auxiliary method fn pack_into(&mut self, py: Python, b: &Bound) -> Result { Ok(if let Ok(in_node) = b.cast::() { let in_node = in_node.borrow(); @@ -6598,6 +6622,7 @@ impl DAGCircuit { }) } + // TODO: Move Python auxiliary method fn unpack_into(&self, py: Python, id: NodeIndex, weight: &NodeType) -> PyResult> { let dag_node = match weight { NodeType::QubitIn(qubit) => Py::new( @@ -7312,8 +7337,6 @@ impl DAGCircuit { num_ops; Self { - name: None, - metadata: None, dag: StableDiGraph::with_capacity(num_nodes, num_edges), qregs: RegisterData::new(), cregs: RegisterData::new(), @@ -7475,7 +7498,6 @@ impl DAGCircuit { /// Return the op name counts in the circuit /// /// Args: - /// py: The python token necessary for control flow recursion /// recurse: Whether to recurse into control flow ops or not pub fn count_ops(&self, recurse: bool) -> Result, DAGError> { if !recurse || !self.has_control_flow() { @@ -7547,31 +7569,10 @@ impl DAGCircuit { Ok(new_nodes) } - /// Alternative constructor to build an instance of [DAGCircuit] from a `QuantumCircuit`. - pub fn from_circuit( - qc: QuantumCircuitData, - copy_op: bool, - qubit_order: Option>, - clbit_order: Option>, - ) -> Result { - // Extract necessary attributes - let qc_data = qc.data; - Self::from_circuit_data( - &qc_data, - copy_op, - qc.name, - qc.metadata.map(|x| x.unbind()), - qubit_order, - clbit_order, - ) - } - /// Builds a [DAGCircuit] based on an instance of [CircuitData]. pub fn from_circuit_data( qc_data: &CircuitData, copy_op: bool, - name: Option, - metadata: Option>, qubit_order: Option>, clbit_order: Option>, ) -> Result { @@ -7591,9 +7592,6 @@ impl DAGCircuit { Some(num_stretches), ); - // Assign other necessary data - new_dag.name = name; - // Avoid manually acquiring the GIL. new_dag.global_phase = match qc_data.global_phase() { // The clone here implicitly requires the gil while ParameterExpression is defined in @@ -7603,8 +7601,6 @@ impl DAGCircuit { _ => unreachable!("Incorrect parameter assigned for global phase"), }; - new_dag.metadata = metadata; - // Add the qubits depending on order, and produce the qargs map. let qarg_map = if let Some(qubit_ordering) = qubit_order { @@ -7691,7 +7687,7 @@ impl DAGCircuit { new_dag.qubit_locations = qc_data.qubit_indices().clone(); new_dag.clbit_locations = qc_data.clbit_indices().clone(); new_dag.blocks = qc_data.blocks().try_map_without_references(|block| { - DAGCircuit::from_circuit_data(block, copy_op, None, None, None, None) + DAGCircuit::from_circuit_data(block, copy_op, None, None) })?; new_dag.try_extend(qc_data.iter().map(|instr| -> Result<_, DAGError> { Ok(PackedInstruction { @@ -8230,16 +8226,6 @@ impl DAGCircuit { DAGCircuitBuilder::new(self) } - // Returns an immutable reference to 'name', if it exists - pub fn get_name(&self) -> Option<&String> { - self.name.as_ref() - } - - // Returns an immutable reference to 'metadata' - pub fn get_metadata(&self) -> Option<&Py> { - self.metadata.as_ref() - } - /// Returns an iterator over the unique successors of the given node pub fn successors(&self, node: NodeIndex) -> impl Iterator { self.dag.neighbors_directed(node, Outgoing).unique() @@ -8290,6 +8276,237 @@ impl DAGCircuit { } Ok(builder.build()) } + + /// Set the global phase of the circuit. + /// + /// Args: + /// angle (float, :class:`.ParameterExpression`): The phase angle. + pub fn set_global_phase(&mut self, angle: Param) -> Result<(), DAGError> { + self.set_global_phase_param(angle).map(|_| ()) + } + + /// Return `true` if there are no operation nodes in the graph. + pub fn is_empty(&self) -> bool { + self.dag.node_count() + == 2 * (self.qubits.len() + self.clbits.len() + self.vars_stretches.vars().len()) + } + + /// Total number of classical variables tracked by the circuit. + fn num_vars(&self) -> usize { + self.num_input_vars() + self.num_captured_vars() + self.num_declared_vars() + } + + /// Number of input classical variables tracked by the circuit. + fn num_input_vars(&self) -> usize { + self.vars_stretches.num_vars(VarType::Input) + } + + /// Number of captured classical variables tracked by the circuit. + fn num_captured_vars(&self) -> usize { + self.vars_stretches.num_vars(VarType::Capture) + } + + /// Number of declared local classical variables tracked by the circuit. + fn num_declared_vars(&self) -> usize { + self.vars_stretches.num_vars(VarType::Declare) + } + + /// Total number of stretches tracked by the circuit. + pub fn num_stretches(&self) -> usize { + self.num_captured_stretches() + self.num_declared_stretches() + } + + /// Number of captured stretches tracked by the circuit. + fn num_captured_stretches(&self) -> usize { + self.vars_stretches.num_stretches(StretchType::Capture) + } + + /// Number of declared local stretches tracked by the circuit. + fn num_declared_stretches(&self) -> usize { + self.vars_stretches.num_stretches(StretchType::Declare) + } + + /// Return the total number of qubits + clbits used by the circuit. + /// This function formerly returned the number of qubits by the calculation + /// return len(self._wires) - self.num_clbits() + /// but was changed by issue #2564 to return number of qubits + clbits + /// with the new function DAGCircuit.num_qubits replacing the former + /// semantic of DAGCircuit.width(). + pub fn width(&self) -> usize { + self.qubits.len() + self.clbits.len() + self.num_vars() + } + + /// Return the total number of qubits used by the circuit. + /// num_qubits() replaces former use of width(). + /// DAGCircuit.width() now returns qubits + clbits for + /// consistency with Circuit.width() [qiskit-terra #2564]. + pub fn num_qubits(&self) -> usize { + self.qubits.len() + } + + /// Return the total number of classical bits used by the circuit. + pub fn num_clbits(&self) -> usize { + self.clbits.len() + } + + /// Return the number of basic blocks in this circuit. + pub fn num_blocks(&self) -> usize { + self.blocks.len() + } + + /// Compute how many components the circuit can decompose into. + fn num_tensor_factors(&self) -> usize { + // This function was forked from rustworkx's + // number_weekly_connected_components() function as of 0.15.0: + // https://github.com/Qiskit/rustworkx/blob/0.15.0/src/connectivity/mod.rs#L215-L235 + + let mut weak_components = self.dag.node_count(); + let mut vertex_sets = UnionFind::new(self.dag.node_bound()); + for edge in self.dag.edge_references() { + let (a, b) = (edge.source(), edge.target()); + // union the two vertices of the edge + if vertex_sets.union(a.index(), b.index()) { + weak_components -= 1 + }; + } + weak_components + } + + /// Get the number of op nodes in the DAG. + #[inline] + pub fn num_ops(&self) -> usize { + self.dag.node_count() - 2 * self.width() + } + + /// Return the number of operations. If there is control flow present, this count may only + /// be an estimate, as the complete control-flow path cannot be statically known. + /// + /// Args: + /// recurse: if ``True``, then recurse into control-flow operations. For loops with + /// known-length iterators are counted unrolled. If-else blocks sum both of the two + /// branches. While loops are counted as if the loop body runs once only. Defaults to + /// ``False`` and raises :class:`.DAGCircuitError` if any control flow is present, to + /// avoid silently returning a mostly meaningless number. + /// + /// Returns: + /// int: the circuit size + /// + /// Raises: + /// DAGCircuitError: if an unknown :class:`.ControlFlowOp` is present in a call with + /// ``recurse=True``, or any control flow is present in a non-recursive call. + pub fn size(&self, recurse: bool) -> Result { + let mut length = self.num_ops(); + if !self.has_control_flow() { + return Ok(length); + } + if !recurse { + return Err(DAGError::General( + concat!( + "Size with control flow is ambiguous.", + " You may use `recurse=True` to get a result", + " but see this method's documentation for the meaning of this." + ) + .into(), + )); + } + + // Handle recursively. + for control_flow in self + .op_nodes(false) + .filter_map(|(_, node)| self.try_view_control_flow(node)) + { + match control_flow { + ControlFlowView::ForLoop { + collection, body, .. + } => { + // TODO: is this the intended logic? + length += collection.len() * body.size(true)?; + } + _ => { + for block in control_flow.blocks() { + length += block.size(true)?; + } + } + } + // We don't count a control-flow node itself! + length -= 1; + } + Ok(length) + } + + /// Return the circuit depth. If there is control flow present, this count may only be an + /// estimate, as the complete control-flow path cannot be statically known. + /// + /// Args: + /// recurse: if ``True``, then recurse into control-flow operations. For loops + /// with known-length iterators are counted as if the loop had been manually unrolled + /// (*i.e.* with each iteration of the loop body written out explicitly). + /// If-else blocks take the longer case of the two branches. While loops are counted as + /// if the loop body runs once only. Defaults to ``False`` and raises + /// :class:`.DAGCircuitError` if any control flow is present, to avoid silently + /// returning a nonsensical number. + /// + /// Returns: + /// int: the circuit depth + /// + /// Raises: + /// DAGCircuitError: if not a directed acyclic graph + /// DAGCircuitError: if unknown control flow is present in a recursive call, or any control + /// flow is present in a non-recursive call. + pub fn depth(&self, recurse: bool) -> Result { + if self.qubits.is_empty() && self.clbits.is_empty() && self.num_vars() == 0 { + return Ok(0); + } + if !self.has_control_flow() { + let weight_fn = |_| -> Result { Ok(1) }; + return match rustworkx_core::dag_algo::longest_path_length(&self.dag, weight_fn) + .unwrap() + { + Some(res) => Ok(res - 1), + None => panic!("not a DAG"), + }; + } + if !recurse { + return Err(DAGError::General( + concat!( + "Depth with control flow is ambiguous.", + " You may use `recurse=True` to get a result", + " but see this method's documentation for the meaning of this." + ) + .into(), + )); + } + // Handle recursively. + let mut node_lookup: HashMap = HashMap::new(); + for (node_index, control_flow) in self + .op_nodes(false) + .filter_map(|(index, node)| self.try_view_control_flow(node).map(|cf| (index, cf))) + { + let weight = if let ControlFlowView::ForLoop { collection, .. } = control_flow { + collection.len() + } else { + 1 + }; + if weight == 0 { + node_lookup.insert(node_index, 0); + } else { + let blocks = control_flow.blocks(); + let mut block_weights: Vec = Vec::with_capacity(blocks.len()); + for block in blocks { + block_weights.push(block.depth(true)?); + node_lookup.insert(node_index, weight * block_weights.iter().max().unwrap()); + } + } + } + + let weight_fn = |edge: EdgeReference<'_, Wire>| -> Result { + Ok(*node_lookup.get(&edge.target()).unwrap_or(&1)) + }; + match rustworkx_core::dag_algo::longest_path_length(&self.dag, weight_fn).unwrap() { + Some(res) => Ok(res - 1), + None => panic!("not a DAG"), + } + } } struct NodesOnWireIter<'a> { @@ -8613,6 +8830,156 @@ impl ::std::ops::Index for DAGCircuit { } } +impl PyDAGCircuit { + // Returns an immutable reference to 'metadata' + pub fn get_metadata(&self) -> Option<&Py> { + self.metadata.as_ref() + } + + // Returns an immutable reference to 'name', if it exists + pub fn get_name(&self) -> Option<&String> { + self.name.as_ref() + } + + /// Alternative constructor to build an instance of [DAGCircuit] from a `QuantumCircuit`. + pub fn from_circuit( + qc: QuantumCircuitData, + copy_op: bool, + qubit_order: Option>, + clbit_order: Option>, + ) -> Result { + // Extract necessary attributes + let qc_data = qc.data; + let dag = DAGCircuit::from_circuit_data(&qc_data, copy_op, qubit_order, clbit_order)?; + Ok(PyDAGCircuit { + name: qc.name, + metadata: qc.metadata.map(|data| data.unbind()), + inner: dag, + }) + } + + /// Creates a [`PyDAGCircuit`] from a [`DAGCircuit`] while also accepting Python specific metadata. + /// + /// [`DAGCircuit`] already implements `Into` which is enough for a conversion into + /// Python. However, when an instance originating from Python is to get processed by Rust, the inner + /// [`DAGCircuit`] is exposed and sometimes cloned, discarding its metadata. + /// + /// This static method allows users to transform back to [`PyDAGCircuit`] and preserve the previous + /// instance's metadata. + pub fn from_dagcircuit( + circuit: DAGCircuit, + name: Option, + metadata: Option>, + ) -> Self { + Self { + name, + metadata, + inner: circuit, + } + } + + /// Creates an instance from a [`DAGCircuit`] while copying Python specific metadata from + /// another [`PyDAGCircuit`] instance. + pub fn from_dagcircuit_with_cloned_metadata( + circuit: DAGCircuit, + original: &PyDAGCircuit, + ) -> Self { + Self::from_dagcircuit(circuit, original.name.clone(), original.metadata.clone()) + } + + /// Returns an immutable reference to the inner [`DAGCircuit`]. + pub fn try_read(&self) -> PyResult<&DAGCircuit> { + Ok(&self.inner) + } + + /// Returns a mutable reference to the inner [`DAGCircuit`]. + pub fn try_write(&mut self) -> PyResult<&mut DAGCircuit> { + Ok(&mut self.inner) + } +} + +impl From for PyDAGCircuit { + fn from(value: DAGCircuit) -> Self { + PyDAGCircuit { + name: None, + metadata: None, + inner: value, + } + } +} + +impl From for DAGCircuit { + fn from(value: PyDAGCircuit) -> Self { + value.inner + } +} + +fn idle_wires( + dag: &DAGCircuit, + py: Python, + ignore: Option<&Bound>, +) -> PyResult> { + let mut result: Vec> = Vec::new(); + let wires = (0..dag.qubit_io_map.len()) + .map(|idx| Wire::Qubit(Qubit::new(idx))) + .chain((0..dag.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit::new(idx)))) + .chain((0..dag.var_io_map.len()).map(|idx| Wire::Var(Var::new(idx)))); + match ignore { + Some(ignore) => { + // Convert the list to a Rust set. + let ignore_set = ignore + .into_iter() + .map(|s| s.extract()) + .collect::>>()?; + for wire in wires { + let nodes_found = dag + .nodes_on_wire(wire) + .filter(|node| matches!(dag.dag[*node], NodeType::Operation(_))) + .any(|node| { + let weight = dag.dag.node_weight(node).unwrap(); + if let NodeType::Operation(packed) = weight { + !ignore_set.contains(packed.op.name()) + } else { + false + } + }); + + if !nodes_found { + result.push(match wire { + Wire::Qubit(qubit) => dag.qubits.get(qubit).unwrap().into_py_any(py)?, + Wire::Clbit(clbit) => dag.clbits.get(clbit).unwrap().into_py_any(py)?, + Wire::Var(var) => dag + .vars_stretches + .vars() + .get(var) + .unwrap() + .clone() + .into_py_any(py)?, + }); + } + } + } + None => { + for wire in wires { + if dag.is_wire_idle(wire) { + result.push(match wire { + Wire::Qubit(qubit) => dag.qubits.get(qubit).unwrap().into_py_any(py)?, + Wire::Clbit(clbit) => dag.clbits.get(clbit).unwrap().into_py_any(py)?, + Wire::Var(var) => dag + .vars_stretches + .vars() + .get(var) + .unwrap() + .clone() + .into_py_any(py)?, + }); + } + } + } + } + Ok(PyTuple::new(py, result)?.into_any().try_iter()?.unbind()) +} + /// Add to global phase. Global phase can only be Float or ParameterExpression so this /// does not handle the full possibility of parameter values. /// TODO replace/merge this with add_param/radd_param @@ -8681,7 +9048,7 @@ type SortKeyType<'a> = (&'a [Qubit], &'a [Clbit]); #[cfg(test)] mod test { use crate::bit::{ClassicalRegister, QuantumRegister}; - use crate::dag_circuit::{BlocksMode, DAGCircuit, Wire}; + use crate::dag_circuit::{BlocksMode, DAGCircuit, DAGError, Wire}; use crate::operations::{StandardGate, StandardInstruction}; use crate::packed_instruction::{PackedInstruction, PackedOperation}; use crate::{Clbit, Qubit}; @@ -8869,11 +9236,10 @@ mod test { } #[test] - fn test_physical_empty_like() -> PyResult<()> { + fn test_physical_empty_like() -> Result<(), DAGError> { let mut dag = DAGCircuit::new(); let qr = QuantumRegister::new_owning("virtual".to_owned(), 5); let cr = ClassicalRegister::new_owning("classical".to_owned(), 5); - dag.name = Some("my dag".to_owned()); dag.add_creg(cr.clone())?; dag.add_qreg(qr)?; dag.apply_operation_back( @@ -8895,7 +9261,6 @@ mod test { None, )?; let empty = dag.physical_empty_like_with_capacity(10, 0, 0, BlocksMode::Drop)?; - assert_eq!(empty.name.as_deref(), Some("my dag")); assert_eq!( empty .qregs() diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 2e553943a8db..1f974ac84d44 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -227,7 +227,7 @@ pub fn circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; - m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/synthesis/src/two_qubit_decompose/basis_decomposer.rs b/crates/synthesis/src/two_qubit_decompose/basis_decomposer.rs index c36bc7421839..3a37227254e1 100644 --- a/crates/synthesis/src/two_qubit_decompose/basis_decomposer.rs +++ b/crates/synthesis/src/two_qubit_decompose/basis_decomposer.rs @@ -42,7 +42,7 @@ use crate::matrix::two_qubit; use qiskit_circuit::bit::ShareableQubit; use qiskit_circuit::circuit_data::{CircuitData, PyCircuitData}; use qiskit_circuit::circuit_instruction::OperationFromPython; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::gate_matrix::{CX_GATE, ONE_QUBIT_IDENTITY}; use qiskit_circuit::instruction::{Instruction, Parameters}; use qiskit_circuit::operations::{Operation, OperationRef, Param, StandardGate}; @@ -909,7 +909,7 @@ impl TwoQubitBasisDecomposer { basis_fidelity: Option, approximate: bool, _num_basis_uses: Option, - ) -> PyResult { + ) -> PyResult { let array = unitary.as_array(); let sequence = self.call_inner(array, basis_fidelity, approximate, _num_basis_uses)?; let mut dag = DAGCircuit::with_capacity(2, 0, None, Some(sequence.gates.len()), None, None); @@ -930,7 +930,7 @@ impl TwoQubitBasisDecomposer { None, )?; } - Ok(builder.build()) + Ok(builder.build().into()) } /// Synthesizes a two qubit unitary matrix into a :class:`.CircuitData` object diff --git a/crates/transpiler/src/angle_bound_registry.rs b/crates/transpiler/src/angle_bound_registry.rs index 09722974d8d2..1ce787688696 100644 --- a/crates/transpiler/src/angle_bound_registry.rs +++ b/crates/transpiler/src/angle_bound_registry.rs @@ -19,7 +19,7 @@ use pyo3::types::PyDict; use crate::TranspilerError; use qiskit_circuit::PhysicalQubit; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; #[derive(Clone)] pub(crate) enum CallbackType { @@ -36,8 +36,9 @@ impl CallbackType { inner .bind(py) .call1((angles, qubits))? - .extract() + .extract::() .map_err(PyErr::from) + .map(Into::into) }) } Self::Native(inner) => Ok(inner(angles, qubits)), @@ -66,8 +67,11 @@ impl PyWrapAngleRegistry { name: &str, angles: Vec, qubits: Vec, - ) -> PyResult> { - self.0.substitute_angle_bounds(name, &angles, &qubits) + ) -> PyResult> { + Ok(self + .0 + .substitute_angle_bounds(name, &angles, &qubits)? + .map(Into::into)) } pub fn add_wrapper(&mut self, name: String, callback: Py) { diff --git a/crates/transpiler/src/passes/apply_layout.rs b/crates/transpiler/src/passes/apply_layout.rs index 51694b3d1638..d63af74444e6 100644 --- a/crates/transpiler/src/passes/apply_layout.rs +++ b/crates/transpiler/src/passes/apply_layout.rs @@ -17,7 +17,7 @@ use pyo3::wrap_pyfunction; use hashbrown::HashSet; use qiskit_circuit::bit::{QuantumRegister, Register}; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::nlayout::NLayout; use qiskit_circuit::{PhysicalQubit, Qubit, VirtualQubit}; @@ -149,13 +149,14 @@ fn unique_ancilla_register_name(qregs: &[QuantumRegister]) -> String { #[pyo3(name = "apply_layout")] fn py_apply_layout<'py>( py: Python<'py>, - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, num_virtual_qubits: u32, num_physical_qubits: u32, physical_from_virtual: Vec, permutation: Option>, ) -> PyResult> { - let num_dag_qubits = dag.num_qubits() as u32; + let dag_mut = dag.try_write()?; + let num_dag_qubits = dag_mut.num_qubits() as u32; if num_dag_qubits > num_physical_qubits { return Err(PyValueError::new_err(format!( "More qubits in DAG ({num_dag_qubits}) than physical qubits ({num_physical_qubits})" @@ -166,7 +167,7 @@ fn py_apply_layout<'py>( "More original input qubits ({num_virtual_qubits}) than in the DAG ({num_dag_qubits})" ))); } - if physical_from_virtual.len() != dag.num_qubits() { + if physical_from_virtual.len() != dag_mut.num_qubits() { return Err(PyValueError::new_err(format!( "Layout has different number of qubits ({}) than the DAG ({})", physical_from_virtual.len(), @@ -174,14 +175,14 @@ fn py_apply_layout<'py>( ))); } match permutation.as_ref().map(Vec::len) { - Some(len) if len != dag.num_qubits() => { + Some(len) if len != dag_mut.num_qubits() => { return Err(PyValueError::new_err(format!( "Given permutation has difference number of qubits ({len}) than the DAG ({num_dag_qubits})" ))); } _ => (), } - if physical_from_virtual.len() != dag.num_qubits() { + if physical_from_virtual.len() != dag_mut.num_qubits() { return Err(PyValueError::new_err(format!( "Layout has different number of qubits ({}) than the DAG ({})", physical_from_virtual.len(), @@ -212,35 +213,36 @@ fn py_apply_layout<'py>( let mut cur_layout = TranspileLayout::new( None, permutation, - dag.qubits().objects().to_vec(), + dag_mut.qubits().objects().to_vec(), // We had to take `num_virtual_qubits` by value because the DAG might already have been // expanded with ancillas in the legacy mode. num_virtual_qubits, - dag.qregs().to_vec(), + dag_mut.qregs().to_vec(), ); - apply_layout(dag, &mut cur_layout, num_physical_qubits, |v| { + apply_layout(dag_mut, &mut cur_layout, num_physical_qubits, |v| { physical_from_virtual[v.index()] }); - cur_layout.to_py_native(py, dag.qubits().objects()) + cur_layout.to_py_native(py, dag_mut.qubits().objects()) } #[pyfunction] #[pyo3(name = "update_layout")] fn py_update_layout<'py>( - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, py_layout: &Bound<'py, PyAny>, reorder: Vec, ) -> PyResult> { - if reorder.len() != dag.num_qubits() { + let dag_mut = dag.try_write()?; + if reorder.len() != dag_mut.num_qubits() { return Err(PyValueError::new_err(format!( "Updated layout has different number of qubits ({}) to the DAG ({})", reorder.len(), - dag.num_qubits() + dag_mut.num_qubits() ))); } - let mut seen = vec![false; dag.num_qubits()]; + let mut seen = vec![false; dag_mut.num_qubits()]; for qubit in reorder.iter() { - if qubit.index() >= dag.num_qubits() { + if qubit.index() >= dag_mut.num_qubits() { return Err(PyValueError::new_err(format!( "Layout contains out-of-bounds qubit {}", qubit.index() @@ -255,8 +257,8 @@ fn py_update_layout<'py>( } let mut cur_layout = TranspileLayout::from_py_native(py_layout)?; - update_layout(dag, &mut cur_layout, |q| reorder[q.index()]); - cur_layout.to_py_native(py_layout.py(), dag.qubits().objects()) + update_layout(dag_mut, &mut cur_layout, |q| reorder[q.index()]); + cur_layout.to_py_native(py_layout.py(), dag_mut.qubits().objects()) } pub fn apply_layout_mod(m: &Bound) -> PyResult<()> { diff --git a/crates/transpiler/src/passes/barrier_before_final_measurement.rs b/crates/transpiler/src/passes/barrier_before_final_measurement.rs index 05179cb7c485..6bbb51f68238 100644 --- a/crates/transpiler/src/passes/barrier_before_final_measurement.rs +++ b/crates/transpiler/src/passes/barrier_before_final_measurement.rs @@ -15,7 +15,7 @@ use rayon::prelude::*; use rustworkx_core::petgraph::stable_graph::NodeIndex; use qiskit_circuit::Qubit; -use qiskit_circuit::dag_circuit::{DAGCircuit, DAGError, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, DAGError, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{OperationRef, StandardInstruction}; use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; @@ -24,10 +24,10 @@ const PARALLEL_THRESHOLD: usize = 150; #[pyfunction] #[pyo3(name = "barrier_before_final_measurements", signature=(dag, label=None))] pub fn py_run_barrier_before_final_measurements( - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, label: Option, ) -> PyResult<()> { - run_barrier_before_final_measurements(dag, label).map_err(Into::into) + run_barrier_before_final_measurements(dag.try_write()?, label).map_err(Into::into) } pub fn run_barrier_before_final_measurements( diff --git a/crates/transpiler/src/passes/basis_translator/compose_transforms.rs b/crates/transpiler/src/passes/basis_translator/compose_transforms.rs index 8868e187dd57..b0fe1a38b9bf 100644 --- a/crates/transpiler/src/passes/basis_translator/compose_transforms.rs +++ b/crates/transpiler/src/passes/basis_translator/compose_transforms.rs @@ -119,12 +119,13 @@ pub(super) fn compose_transforms<'a>( ) })?; let replace_dag: DAGCircuit = - DAGCircuit::from_circuit_data(&replacement, true, None, None, None, None) - .map_err(|_| { + DAGCircuit::from_circuit_data(&replacement, true, None, None).map_err( + |_| { BasisTranslatorError::BasisDAGCircuitError( "Error converting circuit to dag".to_string(), ) - })?; + }, + )?; dag.substitute_node_with_dag(node, &replace_dag, None, None, None, None) .map_err(|_| { BasisTranslatorError::BasisDAGCircuitError( diff --git a/crates/transpiler/src/passes/basis_translator/mod.rs b/crates/transpiler/src/passes/basis_translator/mod.rs index 301f8ef6e6b7..1b5badaa5aa5 100644 --- a/crates/transpiler/src/passes/basis_translator/mod.rs +++ b/crates/transpiler/src/passes/basis_translator/mod.rs @@ -36,7 +36,7 @@ use qiskit_circuit::parameter::symbol_expr::SymbolExpr; use qiskit_circuit::parameter::symbol_expr::Value; use qiskit_circuit::{ BlocksMode, VarsMode, - dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType}, + dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType, PyDAGCircuit}, operations::{Operation, OperationRef, Param, PauliBased, PythonOperation}, }; use qiskit_circuit::{Clbit, PhysicalQubit, Qubit}; @@ -55,16 +55,24 @@ type PhysicalQargs = SmallVec<[PhysicalQubit; 2]>; #[pyfunction(name = "base_run", signature = (dag, equiv_lib, min_qubits, target=None, target_basis=None))] fn py_run_basis_translator( - dag: &DAGCircuit, + dag: &PyDAGCircuit, equiv_lib: &mut EquivalenceLibrary, min_qubits: usize, target: Option<&Target>, target_basis: Option>, -) -> PyResult> { +) -> PyResult> { let target_basis_ref: Option> = target_basis .as_ref() .map(|set| set.iter().map(|obj| obj.as_str()).collect()); - run_basis_translator(dag, equiv_lib, min_qubits, target, target_basis_ref).map_err(|e| e.into()) + Ok(run_basis_translator( + dag.try_read()?, + equiv_lib, + min_qubits, + target, + target_basis_ref, + )? + // Turn into Python DAG and restore metadata + .map(|out_dag| PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag))) } pub fn run_basis_translator( diff --git a/crates/transpiler/src/passes/check_map.rs b/crates/transpiler/src/passes/check_map.rs index b862a95d4dcf..622ba70f9a7c 100644 --- a/crates/transpiler/src/passes/check_map.rs +++ b/crates/transpiler/src/passes/check_map.rs @@ -13,7 +13,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::operations::Operation; use qiskit_circuit::{PhysicalQubit, Qubit}; @@ -73,7 +73,11 @@ fn recurse( #[pyfunction] #[pyo3(name = "check_map")] -pub fn py_run_check_map(dag: &DAGCircuit, target: &Target) -> PyResult> { +pub fn py_run_check_map( + py_dag: &PyDAGCircuit, + target: &Target, +) -> PyResult> { + let dag = py_dag.try_read()?; if dag.has_control_flow() { recurse(dag, target, None) } else { diff --git a/crates/transpiler/src/passes/commutation_analysis.rs b/crates/transpiler/src/passes/commutation_analysis.rs index 8e9719687253..6ac966fb95a5 100644 --- a/crates/transpiler/src/passes/commutation_analysis.rs +++ b/crates/transpiler/src/passes/commutation_analysis.rs @@ -21,7 +21,7 @@ use rustworkx_core::petgraph::stable_graph::NodeIndex; use crate::commutation_checker::{CommutationChecker, CommutationError}; use qiskit_circuit::Qubit; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, Wire}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit, Wire}; // Custom types to store the commutation sets and node indices, // see the docstring below for more information. @@ -156,10 +156,10 @@ pub fn analyze_commutations( } #[pyfunction] -#[pyo3(name = "analyze_commutations", signature = (dag, commutation_checker, approximation_degree=1.))] +#[pyo3(name = "analyze_commutations", signature = (py_dag, commutation_checker, approximation_degree=1.))] pub fn py_analyze_commutations( py: Python, - dag: &mut DAGCircuit, + py_dag: &mut PyDAGCircuit, commutation_checker: &mut CommutationChecker, approximation_degree: f64, ) -> PyResult> { @@ -167,11 +167,14 @@ pub fn py_analyze_commutations( // * The commuting nodes per wire: {wire: [commuting_nodes_1, commuting_nodes_2, ...]} // * The index in which commutation set a given node is located on a wire: {(node, wire): index} // The Python dict will store both of these dictionaries in one. - let (commutation_set, node_indices) = - analyze_commutations(dag, commutation_checker, approximation_degree)?; + let (commutation_set, node_indices) = analyze_commutations( + py_dag.try_write()?, + commutation_checker, + approximation_degree, + )?; let out_dict = PyDict::new(py); - + let dag = py_dag.try_read()?; // First set the {wire: [commuting_nodes_1, ...]} bit for (wire_index, commutations) in commutation_set.into_iter().enumerate() { if commutations.is_empty() { diff --git a/crates/transpiler/src/passes/commutation_cancellation.rs b/crates/transpiler/src/passes/commutation_cancellation.rs index 914727c8a920..99fb08b61147 100644 --- a/crates/transpiler/src/passes/commutation_cancellation.rs +++ b/crates/transpiler/src/passes/commutation_cancellation.rs @@ -23,7 +23,7 @@ use super::analyze_commutations; use crate::commutation_checker::CommutationChecker; use approx::abs_diff_eq; use qiskit_circuit::Qubit; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{Operation, Param, StandardGate}; use qiskit_synthesis::QiskitError; @@ -73,8 +73,22 @@ struct CancellationSetKey { second_index: Option, } -#[pyfunction] -#[pyo3(signature = (dag, commutation_checker, basis_gates=None, approximation_degree=1.))] +#[pyfunction(name = "cancel_commutations")] +#[pyo3(signature = (py_dag, commutation_checker, basis_gates=None, approximation_degree=1.))] +pub fn py_cancel_commutations( + py_dag: &mut PyDAGCircuit, + commutation_checker: &mut CommutationChecker, + basis_gates: Option>, + approximation_degree: f64, +) -> PyResult<()> { + cancel_commutations( + py_dag.try_write()?, + commutation_checker, + basis_gates, + approximation_degree, + ) +} + pub fn cancel_commutations( dag: &mut DAGCircuit, commutation_checker: &mut CommutationChecker, @@ -324,6 +338,6 @@ fn is_multiple_of_pi(angle: f64, factor: f64) -> bool { } pub fn commutation_cancellation_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(cancel_commutations))?; + m.add_wrapped(wrap_pyfunction!(py_cancel_commutations))?; Ok(()) } diff --git a/crates/transpiler/src/passes/commutative_optimization.rs b/crates/transpiler/src/passes/commutative_optimization.rs index b04528294563..ec47e121b13c 100644 --- a/crates/transpiler/src/passes/commutative_optimization.rs +++ b/crates/transpiler/src/passes/commutative_optimization.rs @@ -25,7 +25,7 @@ use crate::passes::remove_identity_equiv::{ MINIMUM_TOL, average_gate_fidelity_below_tol, is_identity_equiv, }; use qiskit_circuit::circuit_instruction::OperationFromPython; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::operations::{ Operation, OperationRef, Param, PauliBased, PauliProductMeasurement, PauliProductRotation, StandardGate, multiply_param, radd_param, @@ -594,15 +594,16 @@ fn disjoint_instructions( #[pyfunction] #[pyo3(name = "commutative_optimization")] -#[pyo3(signature = (dag, commutation_checker, approximation_degree=1., matrix_max_num_qubits=0))] +#[pyo3(signature = (py_dag, commutation_checker, approximation_degree=1., matrix_max_num_qubits=0))] pub fn run_commutative_optimization( - dag: &mut DAGCircuit, + py_dag: &PyDAGCircuit, commutation_checker: &mut CommutationChecker, approximation_degree: f64, matrix_max_num_qubits: u32, -) -> PyResult> { +) -> PyResult> { let tol = MINIMUM_TOL.max(1. - approximation_degree); let error_cutoff_fn = |_inst: &PackedInstruction| -> f64 { tol }; + let dag = py_dag.try_read()?; // Create output DAG. // We will use it to intern qubits of canonicalized instructions. @@ -731,8 +732,10 @@ pub fn run_commutative_optimization( } } } - - Ok(Some(new_dag)) + // Preserve metadata from original circuit + Ok(Some(PyDAGCircuit::from_dagcircuit_with_cloned_metadata( + new_dag, py_dag, + ))) } pub fn commutative_optimization_mod(m: &Bound) -> PyResult<()> { diff --git a/crates/transpiler/src/passes/consolidate_blocks.rs b/crates/transpiler/src/passes/consolidate_blocks.rs index 3684ae079d34..18c1aaa0f67d 100644 --- a/crates/transpiler/src/passes/consolidate_blocks.rs +++ b/crates/transpiler/src/passes/consolidate_blocks.rs @@ -27,7 +27,7 @@ use pyo3::intern; use pyo3::prelude::*; use qiskit_circuit::Qubit; use qiskit_circuit::circuit_data::CircuitData; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::gate_matrix::{ CH_GATE, CX_GATE, CY_GATE, CZ_GATE, DCX_GATE, ECR_GATE, ISWAP_GATE, ONE_QUBIT_IDENTITY, }; @@ -481,7 +481,7 @@ fn apply_consolidation( #[pyo3(name = "consolidate_blocks", signature = (dag, decomposer, basis_gate_name, force_consolidate, target=None, basis_gates=None, blocks=None, runs=None, qubit_map=None))] fn py_run_consolidate_blocks( py: Python, - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, decomposer: Option, basis_gate_name: &str, force_consolidate: bool, @@ -491,6 +491,7 @@ fn py_run_consolidate_blocks( runs: Option>>, qubit_map: Option>, ) -> PyResult<()> { + let dag = dag.try_write()?; // If we don't have a decomposer and force consolidate is not set then there is not any // consolidation to do. if decomposer.is_none() && !force_consolidate { @@ -800,9 +801,8 @@ mod test_consolidate_blocks { .expect("Error while adding CXGate to target"); // Convert the circuit to a DAG. - let mut circ_as_dag = - DAGCircuit::from_circuit_data(&circuit, false, None, None, None, None) - .expect("Error converting circuit to DAG."); + let mut circ_as_dag = DAGCircuit::from_circuit_data(&circuit, false, None, None) + .expect("Error converting circuit to DAG."); // Run the pass run_consolidate_blocks(&mut circ_as_dag, false, None, Some(&target)) .expect("Error while running the consolidate blocks pass."); diff --git a/crates/transpiler/src/passes/constrained_reschedule.rs b/crates/transpiler/src/passes/constrained_reschedule.rs index 82615a94f128..712c1c92ecdc 100644 --- a/crates/transpiler/src/passes/constrained_reschedule.rs +++ b/crates/transpiler/src/passes/constrained_reschedule.rs @@ -18,7 +18,7 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::{Bound, PyResult, pyfunction, wrap_pyfunction}; use qiskit_circuit::PhysicalQubit; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{ Operation, OperationRef, Param, PyInstruction, PyOpKind, StandardInstruction, }; @@ -247,7 +247,7 @@ fn push_node_back( #[pyfunction] #[pyo3(name="constrained_reschedule", signature=(dag, node_start_time, clbit_write_latency, acquire_align, pulse_align, target))] pub fn py_run_constrained_reschedule( - dag: &DAGCircuit, + dag: &PyDAGCircuit, mut node_start_time: PyNodeDurations, clbit_write_latency: u32, acquire_align: u32, @@ -260,7 +260,7 @@ pub fn py_run_constrained_reschedule( )); }; run_constrained_reschedule( - dag, + dag.try_read()?, durations, clbit_write_latency, acquire_align, diff --git a/crates/transpiler/src/passes/convert_to_pauli_rotations.rs b/crates/transpiler/src/passes/convert_to_pauli_rotations.rs index c7506623288b..9afed40dba6f 100644 --- a/crates/transpiler/src/passes/convert_to_pauli_rotations.rs +++ b/crates/transpiler/src/passes/convert_to_pauli_rotations.rs @@ -16,7 +16,7 @@ use qiskit_circuit::operations::PauliBased; use smallvec::smallvec; use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, PI}; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{NodeType, PyDAGCircuit}; use qiskit_circuit::instruction::Parameters; use qiskit_circuit::operations::{ Operation, OperationRef, Param, PauliProductMeasurement, PauliProductRotation, StandardGate, @@ -468,7 +468,8 @@ fn generate_pauli_product_rotation_gate(paulis: &[BitTerm], angle: Param) -> Pau /// the pass. #[pyfunction] #[pyo3(name = "convert_to_pauli_rotations")] -pub fn py_convert_to_pauli_rotations(dag: &DAGCircuit) -> PyResult { +pub fn py_convert_to_pauli_rotations(py_dag: &PyDAGCircuit) -> PyResult { + let dag = py_dag.try_read()?; let mut new_dag = dag.copy_empty_like(VarsMode::Alike, BlocksMode::Keep); // Iterate over nodes in the DAG and collect nodes @@ -599,7 +600,10 @@ pub fn py_convert_to_pauli_rotations(dag: &DAGCircuit) -> PyResult { new_dag.add_global_phase(&global_phase)?; - Ok(new_dag) + // Preserve metadata from original circuit + Ok(PyDAGCircuit::from_dagcircuit_with_cloned_metadata( + new_dag, py_dag, + )) } pub fn convert_to_pauli_rotations_mod(m: &Bound) -> PyResult<()> { diff --git a/crates/transpiler/src/passes/disjoint_layout.rs b/crates/transpiler/src/passes/disjoint_layout.rs index 3175303d07d2..babcb5bb183b 100644 --- a/crates/transpiler/src/passes/disjoint_layout.rs +++ b/crates/transpiler/src/passes/disjoint_layout.rs @@ -28,7 +28,7 @@ use uuid::Uuid; use crate::TranspilerError; use crate::target::{Qargs, Target}; use qiskit_circuit::bit::ShareableQubit; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{Operation, OperationRef, StandardInstruction}; use qiskit_circuit::packed_instruction::PackedOperation; use qiskit_circuit::{ @@ -88,12 +88,12 @@ fn subgraph(graph: &CouplingMap, node_set: &HashSet) -> CouplingMap { #[pyfunction(name = "run_pass_over_connected_components")] pub fn py_run_pass_over_connected_components( - dag: Bound, + dag: Bound, target: &Target, run_func: Bound, ) -> PyResult>>> { let py = dag.py(); - let func = |dag: Bound, cmap: &CouplingMap| -> PyResult> { + let func = |dag: Bound, cmap: &CouplingMap| -> PyResult> { let py = run_func.py(); let coupling_map_cls = COUPLING_MAP.get_bound(py); let endpoints: Vec<[usize; 2]> = cmap @@ -121,8 +121,9 @@ pub fn py_run_pass_over_connected_components( }; let components = { let mut borrowed = dag.borrow_mut(); - distribute_components(borrowed.deref_mut(), target)? + distribute_components(borrowed.deref_mut().try_write()?, target)? }; + let borrowed = dag.borrow(); match components { DisjointSplit::NoneNeeded => { let coupling_map: CouplingMap = match build_coupling_map(target) { @@ -152,7 +153,13 @@ pub fn py_run_pass_over_connected_components( .map(|x| NodeIndex::new(x.index())) .collect(), ); - func(component.sub_dag.into_pyobject(py)?, &cmap) + // Since each generated dag originates as a copy from the original + // we can also copy the original's meta data. + let dag_comp = PyDAGCircuit::from_dagcircuit_with_cloned_metadata( + component.sub_dag, + &borrowed, + ); + func(dag_comp.into_pyobject(py)?, &cmap) }) .collect::>>(), ) @@ -523,7 +530,11 @@ fn separate_dag(dag: &mut DAGCircuit) -> PyResult> { decomposed_dags } -#[pyfunction] +#[pyfunction(name = "combine_barriers")] +fn py_combine_barriers(dag: &mut PyDAGCircuit, retain_uuid: bool) -> PyResult<()> { + combine_barriers(dag.try_write()?, retain_uuid) +} + pub fn combine_barriers(dag: &mut DAGCircuit, retain_uuid: bool) -> PyResult<()> { let mut uuid_map: HashMap = HashMap::new(); let barrier_nodes: Vec = dag @@ -578,7 +589,7 @@ pub fn combine_barriers(dag: &mut DAGCircuit, retain_uuid: bool) -> PyResult<()> Ok(()) } -fn split_barriers(dag: &mut DAGCircuit) -> PyResult<()> { +fn split_barriers(dag: &DAGCircuit) -> PyResult<()> { for (_index, inst) in dag.op_nodes(true) { let OperationRef::StandardInstruction(StandardInstruction::Barrier(num_qubits)) = inst.op.view() @@ -610,7 +621,7 @@ fn split_barriers(dag: &mut DAGCircuit) -> PyResult<()> { } pub fn disjoint_utils_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(combine_barriers))?; + m.add_wrapped(wrap_pyfunction!(py_combine_barriers))?; m.add_wrapped(wrap_pyfunction!(py_run_pass_over_connected_components))?; Ok(()) } diff --git a/crates/transpiler/src/passes/elide_permutations.rs b/crates/transpiler/src/passes/elide_permutations.rs index 6b41312d25bd..42f6c7581890 100644 --- a/crates/transpiler/src/passes/elide_permutations.rs +++ b/crates/transpiler/src/passes/elide_permutations.rs @@ -13,7 +13,7 @@ use numpy::PyReadonlyArray1; use pyo3::prelude::*; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{Operation, OperationRef, Param, StandardGate}; use qiskit_circuit::{BlocksMode, Qubit, VarsMode}; @@ -27,6 +27,28 @@ use qiskit_circuit::{BlocksMode, Qubit, VarsMode}; /// tuple consisting of the optimized DAG and the induced qubit permutation. #[pyfunction] #[pyo3(name = "run")] +pub fn py_run_elide_permutations( + dag: &PyDAGCircuit, +) -> PyResult)>> { + Ok( + run_elide_permutations(dag.try_read()?)?.map(|(out_dag, perm)| { + // Preserve the metadata + ( + PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag), + perm, + ) + }), + ) +} + +/// Run the ElidePermutations pass on `dag`. +/// +/// Args: +/// dag (DAGCircuit): the DAG to be optimized. +/// Returns: +/// An `Option`: the value of `None` indicates that no optimization was +/// performed and the original `dag` should be used, otherwise it's a +/// tuple consisting of the optimized DAG and the induced qubit permutation. pub fn run_elide_permutations(dag: &DAGCircuit) -> PyResult)>> { let permutation_gate_names = ["swap".to_string(), "permutation".to_string()]; let op_counts = dag.get_op_counts(); @@ -108,6 +130,6 @@ pub fn run_elide_permutations(dag: &DAGCircuit) -> PyResult) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(run_elide_permutations))?; + m.add_wrapped(wrap_pyfunction!(py_run_elide_permutations))?; Ok(()) } diff --git a/crates/transpiler/src/passes/filter_op_nodes.rs b/crates/transpiler/src/passes/filter_op_nodes.rs index f5bfdff5428d..15a52a2c2b30 100644 --- a/crates/transpiler/src/passes/filter_op_nodes.rs +++ b/crates/transpiler/src/passes/filter_op_nodes.rs @@ -13,7 +13,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::packed_instruction::PackedInstruction; use rustworkx_core::petgraph::stable_graph::NodeIndex; @@ -21,21 +21,23 @@ use rustworkx_core::petgraph::stable_graph::NodeIndex; #[pyo3(name = "filter_op_nodes")] pub fn py_filter_op_nodes( py: Python, - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, predicate: &Bound, ) -> PyResult<()> { + let dag_borrowed = dag.try_read()?; let callable = |node: NodeIndex| -> PyResult { - let dag_op_node = dag.get_node(py, node)?; + let dag_op_node = dag_borrowed.get_node(py, node)?; predicate.call1((dag_op_node,))?.extract() }; let mut remove_nodes: Vec = Vec::new(); - for node in dag.op_node_indices(true) { + for node in dag_borrowed.op_node_indices(true) { if !callable(node)? { remove_nodes.push(node); } } + let dag_mut = dag.try_write()?; for node in remove_nodes { - dag.remove_op_node(node); + dag_mut.remove_op_node(node); } Ok(()) } @@ -45,7 +47,17 @@ pub fn py_filter_op_nodes( /// Args: /// dag (DAGCircuit): The dag circuit to filter the ops from /// label (str): The label to filter nodes on -#[pyfunction] +#[pyfunction(name = "filter_labeled_op")] +fn py_filter_labeled_op(dag: &mut PyDAGCircuit, label: String) -> PyResult<()> { + filter_labeled_op(dag.try_write()?, label); + Ok(()) +} + +/// Remove any nodes that have the provided label set +/// +/// # Arguments: +/// * `dag` - The dag circuit to filter the ops from +/// * `label` - The label to filter nodes on pub fn filter_labeled_op(dag: &mut DAGCircuit, label: String) { let predicate = |node: &PackedInstruction| -> bool { match node.label.as_deref() { @@ -58,6 +70,6 @@ pub fn filter_labeled_op(dag: &mut DAGCircuit, label: String) { pub fn filter_op_nodes_mod(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(py_filter_op_nodes))?; - m.add_wrapped(wrap_pyfunction!(filter_labeled_op))?; + m.add_wrapped(wrap_pyfunction!(py_filter_labeled_op))?; Ok(()) } diff --git a/crates/transpiler/src/passes/gate_direction.rs b/crates/transpiler/src/passes/gate_direction.rs index 1072331ee558..a77f92595872 100644 --- a/crates/transpiler/src/passes/gate_direction.rs +++ b/crates/transpiler/src/passes/gate_direction.rs @@ -19,7 +19,12 @@ use qiskit_circuit::bit::{QuantumRegister, Register}; use qiskit_circuit::instruction::Parameters; use qiskit_circuit::operations::{OperationRef, StandardGate}; use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; -use qiskit_circuit::{Qubit, dag_circuit::DAGCircuit, operations::Operation, operations::Param}; +use qiskit_circuit::{ + Qubit, + dag_circuit::{DAGCircuit, PyDAGCircuit}, + operations::Operation, + operations::Param, +}; use rustworkx_core::petgraph::stable_graph::NodeIndex; use smallvec::SmallVec; use std::f64::consts::PI; @@ -38,6 +43,13 @@ use std::f64::consts::PI; /// true iff all two-qubit gates comply with the coupling constraints #[pyfunction] #[pyo3(name = "check_gate_direction_coupling")] +fn py_check_direction_coupling_map( + dag: &PyDAGCircuit, + coupling_edges: HashSet<[Qubit; 2]>, +) -> PyResult { + check_direction_coupling_map(dag.try_read()?, coupling_edges) +} + pub fn check_direction_coupling_map( dag: &DAGCircuit, coupling_edges: HashSet<[Qubit; 2]>, @@ -59,6 +71,18 @@ pub fn check_direction_coupling_map( /// true iff all two-qubit gates comply with the target's coupling constraints #[pyfunction] #[pyo3(name = "check_gate_direction_target")] +fn py_check_direction_target(dag: &PyDAGCircuit, target: &Target) -> PyResult { + check_direction_target(dag.try_read()?, target) +} + +/// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target. +/// +/// # Arguments: +/// * `dag` - the [`DAGCircuit`] to analyze +/// * `target` - the [`Target`] against which gate directionality compliance is checked +/// +/// # Returns: +/// `true` iff all two-qubit gates comply with the target's coupling constraints pub fn check_direction_target(dag: &DAGCircuit, target: &Target) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { let qargs = [ @@ -147,6 +171,20 @@ where /// the transformed DAGCircuit #[pyfunction] #[pyo3(name = "fix_gate_direction_coupling")] +fn py_fix_direction_coupling_map( + dag: &mut PyDAGCircuit, + coupling_edges: HashSet<[Qubit; 2]>, +) -> PyResult<()> { + fix_direction_coupling_map(dag.try_write()?, coupling_edges) +} +/// Try to swap two-qubit gate directions using pre-defined mapping to follow the right direction with respect to the coupling map. +/// +/// # Arguments: +/// * `dag` - the DAGCircuit to analyze +/// * `coupling_edges` - set of edge pairs representing a directed coupling map, against which gate directionality is checked +/// +/// # Returns: +/// The transformed DAGCircuit pub fn fix_direction_coupling_map( dag: &mut DAGCircuit, coupling_edges: HashSet<[Qubit; 2]>, @@ -172,6 +210,17 @@ pub fn fix_direction_coupling_map( /// the transformed DAGCircuit #[pyfunction] #[pyo3(name = "fix_gate_direction_target")] +fn py_fix_direction_target(dag: &mut PyDAGCircuit, target: &Target) -> PyResult<()> { + fix_direction_target(dag.try_write()?, target) +} +/// Try to swap two-qubit gate directions using pre-defined mapping to follow the right direction with respect to the given target. +/// +/// # Arguments: +/// * `dag` - the [`DAGCircuit`] to analyze +/// * `coupling_edges` - set of edge pairs representing a directed coupling map, against which gate directionality is checked +/// +/// # Returns: +/// The transformed [`DAGCircuit`] pub fn fix_direction_target(dag: &mut DAGCircuit, target: &Target) -> PyResult<()> { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { let qargs: &[PhysicalQubit] = &[ @@ -490,9 +539,9 @@ fn rzx_replacement_dag(param: &[Param]) -> PyResult { } pub fn gate_direction_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(check_direction_coupling_map))?; - m.add_wrapped(wrap_pyfunction!(check_direction_target))?; - m.add_wrapped(wrap_pyfunction!(fix_direction_coupling_map))?; - m.add_wrapped(wrap_pyfunction!(fix_direction_target))?; + m.add_wrapped(wrap_pyfunction!(py_check_direction_coupling_map))?; + m.add_wrapped(wrap_pyfunction!(py_check_direction_target))?; + m.add_wrapped(wrap_pyfunction!(py_fix_direction_coupling_map))?; + m.add_wrapped(wrap_pyfunction!(py_fix_direction_target))?; Ok(()) } diff --git a/crates/transpiler/src/passes/gates_in_basis.rs b/crates/transpiler/src/passes/gates_in_basis.rs index 770914a47baf..1bc0eb3e43bd 100644 --- a/crates/transpiler/src/passes/gates_in_basis.rs +++ b/crates/transpiler/src/passes/gates_in_basis.rs @@ -15,13 +15,17 @@ use hashbrown::{HashMap, HashSet}; use pyo3::prelude::*; use qiskit_circuit::PhysicalQubit; use qiskit_circuit::Qubit; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::operations::Operation; use qiskit_circuit::packed_instruction::PackedInstruction; use rustworkx_core::petgraph::prelude::NodeIndex; #[pyfunction] #[pyo3(name = "any_gate_missing_from_target")] +pub fn py_gates_missing_from_target(dag: &PyDAGCircuit, target: &Target) -> PyResult { + gates_missing_from_target(dag.try_read()?, target) +} + pub fn gates_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult { #[inline] fn is_universal(gate: &PackedInstruction) -> bool { @@ -103,6 +107,10 @@ pub fn gates_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult< #[pyfunction] #[pyo3(name = "any_gate_missing_from_basis")] +pub fn py_gates_missing_from_basis(dag: &PyDAGCircuit, basis: HashSet) -> PyResult { + gates_missing_from_basis(dag.try_read()?, basis) +} + pub fn gates_missing_from_basis(dag: &DAGCircuit, basis: HashSet) -> PyResult { for (gate, _) in dag.count_ops(true)? { if !basis.contains(gate.as_str()) { @@ -113,7 +121,7 @@ pub fn gates_missing_from_basis(dag: &DAGCircuit, basis: HashSet) -> PyR } pub fn gates_in_basis_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(gates_missing_from_target))?; - m.add_wrapped(wrap_pyfunction!(gates_missing_from_basis))?; + m.add_wrapped(wrap_pyfunction!(py_gates_missing_from_target))?; + m.add_wrapped(wrap_pyfunction!(py_gates_missing_from_basis))?; Ok(()) } diff --git a/crates/transpiler/src/passes/high_level_synthesis.rs b/crates/transpiler/src/passes/high_level_synthesis.rs index 0d67765ec858..ba40745cdb08 100644 --- a/crates/transpiler/src/passes/high_level_synthesis.rs +++ b/crates/transpiler/src/passes/high_level_synthesis.rs @@ -22,7 +22,7 @@ use qiskit_circuit::bit::ShareableQubit; use qiskit_circuit::circuit_data::{CircuitData, PyCircuitData}; use qiskit_circuit::circuit_instruction::OperationFromPython; use qiskit_circuit::converters::QuantumCircuitData; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::gate_matrix::CX_GATE; use qiskit_circuit::imports::HLS_SYNTHESIZE_OP_USING_PLUGINS; use qiskit_circuit::operations::{ @@ -1042,18 +1042,19 @@ fn py_synthesize_circuit( /// to do anything, it returns None, meaning that the DAG should remain unchanged. /// Otherwise, the new DAG is returned. #[pyfunction] -#[pyo3(name = "run_on_dag", signature = (dag, data, qubits_initially_zero))] +#[pyo3(name = "run_on_dag", signature = (py_dag, data, qubits_initially_zero))] pub fn run_high_level_synthesis( py: Python, - dag: &DAGCircuit, + py_dag: &PyDAGCircuit, data: &Bound, qubits_initially_zero: bool, -) -> PyResult> { +) -> PyResult> { // Fast-path: check if HighLevelSynthesis can be skipped altogether. This is only // done at the top-level since this does not track the qubit states. // First, we apply a super-fast (but incomplete) check to see if all the operations // present in the circuit are supported by the target / are in the basis. + let dag = py_dag.try_read()?; if all_instructions_supported(py, data, dag)? { return Ok(None); } @@ -1086,11 +1087,11 @@ pub fn run_high_level_synthesis( run_on_circuitdata(py, &circuit, &input_qubits, data, &mut tracker)?; // Using this constructor so name and metadata are not lost - let new_dag = DAGCircuit::from_circuit( + let new_dag = PyDAGCircuit::from_circuit( QuantumCircuitData { data: output_circuit, - name: dag.get_name().cloned(), - metadata: dag.get_metadata().map(|m| m.bind(py)).cloned(), + name: py_dag.name.clone(), + metadata: py_dag.get_metadata().map(|m| m.bind(py)).cloned(), transpile_layout: None, }, false, diff --git a/crates/transpiler/src/passes/instruction_duration_check.rs b/crates/transpiler/src/passes/instruction_duration_check.rs index 3d36463df396..fefa36018c6d 100644 --- a/crates/transpiler/src/passes/instruction_duration_check.rs +++ b/crates/transpiler/src/passes/instruction_duration_check.rs @@ -14,7 +14,7 @@ use crate::TranspilerError; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::PyDAGCircuit; use qiskit_circuit::operations::Param; use qiskit_circuit::operations::{DelayUnit, OperationRef, StandardInstruction}; @@ -33,10 +33,11 @@ use qiskit_circuit::operations::{DelayUnit, OperationRef, StandardInstruction}; #[pyo3(signature=(dag, acquire_align, pulse_align))] pub fn run_instruction_duration_check( py: Python, - dag: &DAGCircuit, + dag: &PyDAGCircuit, acquire_align: u32, pulse_align: u32, ) -> PyResult { + let dag = dag.try_read()?; let num_stretches = dag.num_stretches(); // Rescheduling is not necessary diff --git a/crates/transpiler/src/passes/inverse_cancellation.rs b/crates/transpiler/src/passes/inverse_cancellation.rs index fa732d3aaffb..46b88c598698 100644 --- a/crates/transpiler/src/passes/inverse_cancellation.rs +++ b/crates/transpiler/src/passes/inverse_cancellation.rs @@ -17,7 +17,7 @@ use rustworkx_core::petgraph::stable_graph::NodeIndex; use qiskit_circuit::NoBlocks; use qiskit_circuit::circuit_instruction::OperationFromPython; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::instruction::Instruction; use qiskit_circuit::operations::{Operation, OperationRef, StandardGate}; use qiskit_circuit::packed_instruction::PackedInstruction; @@ -314,7 +314,12 @@ fn std_inverse_pairs(dag: &mut DAGCircuit) { } } -#[pyfunction] +#[pyfunction(name = "run_inverse_cancellation_standard_gates")] +pub fn py_run_inverse_cancellation_standard_gates(dag: &mut PyDAGCircuit) -> PyResult<()> { + run_inverse_cancellation_standard_gates(dag.try_write()?); + Ok(()) +} + pub fn run_inverse_cancellation_standard_gates(dag: &mut DAGCircuit) { std_self_inverse(dag); std_inverse_pairs(dag); @@ -323,13 +328,14 @@ pub fn run_inverse_cancellation_standard_gates(dag: &mut DAGCircuit) { #[pyfunction] #[pyo3(name = "inverse_cancellation")] pub fn py_run_inverse_cancellation( - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, inverse_gates: Vec<[OperationFromPython; 2]>, self_inverse_gates: Vec>, inverse_gate_names: HashSet, self_inverse_gate_names: HashSet, run_defaults: bool, ) -> PyResult<()> { + let dag = dag.try_write()?; if self_inverse_gate_names.is_empty() && inverse_gate_names.is_empty() { return Ok(()); } @@ -349,6 +355,6 @@ pub fn py_run_inverse_cancellation( pub fn inverse_cancellation_mod(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(py_run_inverse_cancellation))?; - m.add_wrapped(wrap_pyfunction!(run_inverse_cancellation_standard_gates))?; + m.add_wrapped(wrap_pyfunction!(py_run_inverse_cancellation_standard_gates))?; Ok(()) } diff --git a/crates/transpiler/src/passes/litinski_transformation.rs b/crates/transpiler/src/passes/litinski_transformation.rs index b295e2209c23..a08ce16d05f8 100644 --- a/crates/transpiler/src/passes/litinski_transformation.rs +++ b/crates/transpiler/src/passes/litinski_transformation.rs @@ -12,7 +12,7 @@ use pyo3::prelude::*; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::imports::PAULI_EVOLUTION_GATE; use qiskit_circuit::instruction::Parameters; use qiskit_circuit::operations::{ @@ -80,8 +80,28 @@ static HANDLED_INSTRUCTION_NAMES: [&str; 10] = [ const MINIMUM_TOL: f64 = 1e-12; -#[pyfunction] +#[pyfunction(name = "run_litinski_transformation")] #[pyo3(signature = (dag, fix_clifford=true, insert_barrier=false, use_ppr=false, approximation_degree=1.0))] +pub fn py_run_litinski_transformation( + dag: &PyDAGCircuit, + fix_clifford: bool, + insert_barrier: bool, + use_ppr: bool, + approximation_degree: f64, +) -> PyResult> { + Ok(run_litinski_transformation( + dag.try_read()?, + fix_clifford, + insert_barrier, + use_ppr, + approximation_degree, + )? + .map(|out_dag| { + // Preserve metadata + PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag) + })) +} + pub fn run_litinski_transformation( dag: &DAGCircuit, fix_clifford: bool, @@ -559,6 +579,6 @@ fn is_ppr_angle_close_to_multiple_of_pi2( } pub fn litinski_transformation_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(run_litinski_transformation))?; + m.add_wrapped(wrap_pyfunction!(py_run_litinski_transformation))?; Ok(()) } diff --git a/crates/transpiler/src/passes/optimize_1q_gates_decomposition.rs b/crates/transpiler/src/passes/optimize_1q_gates_decomposition.rs index f4d3447e58bb..f899427b890c 100644 --- a/crates/transpiler/src/passes/optimize_1q_gates_decomposition.rs +++ b/crates/transpiler/src/passes/optimize_1q_gates_decomposition.rs @@ -24,7 +24,7 @@ use ndarray::prelude::*; use rayon::prelude::*; use rustworkx_core::petgraph::stable_graph::NodeIndex; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{Operation, OperationRef, Param}; use qiskit_util::getenv_use_multiple_threads; @@ -346,6 +346,22 @@ impl Optimize1qGatesDecompositionState { #[pyfunction] #[pyo3(name = "optimize_1q_gates_decomposition", signature = (dag, state, *, target=None, basis_gates=None, global_decomposers=None))] +fn py_run_optimize_1q_gates_decomposition( + dag: &mut PyDAGCircuit, + state: &Optimize1qGatesDecompositionState, + target: Option<&Target>, + basis_gates: Option>, + global_decomposers: Option>, +) -> PyResult<()> { + run_optimize_1q_gates_decomposition( + dag.try_write()?, + state, + target, + basis_gates, + global_decomposers, + ) +} + pub fn run_optimize_1q_gates_decomposition( dag: &mut DAGCircuit, state: &Optimize1qGatesDecompositionState, @@ -513,7 +529,7 @@ pub fn matmul_1q_with_slice(operator: &mut [[Complex64; 2]; 2], other: &[[Comple } pub fn optimize_1q_gates_decomposition_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(run_optimize_1q_gates_decomposition))?; + m.add_wrapped(wrap_pyfunction!(py_run_optimize_1q_gates_decomposition))?; m.add_class::()?; Ok(()) } diff --git a/crates/transpiler/src/passes/optimize_clifford_t.rs b/crates/transpiler/src/passes/optimize_clifford_t.rs index 56b1020d7d81..0cb012ad1e6f 100644 --- a/crates/transpiler/src/passes/optimize_clifford_t.rs +++ b/crates/transpiler/src/passes/optimize_clifford_t.rs @@ -14,7 +14,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; use std::f64::consts::PI; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::{OperationRef, Param, StandardGate}; use qiskit_circuit::packed_instruction::PackedInstruction; use rustworkx_core::petgraph::stable_graph::NodeIndex; @@ -336,6 +336,13 @@ fn optimize_clifford_t_1q( #[pyfunction] #[pyo3(name = "optimize_clifford_t", signature = (dag, basis_gates=None))] +pub fn py_run_optimize_clifford_t( + dag: &mut PyDAGCircuit, + basis_gates: Option>, +) -> PyResult<()> { + run_optimize_clifford_t(dag.try_write()?, basis_gates) +} + pub fn run_optimize_clifford_t( dag: &mut DAGCircuit, basis_gates: Option>, @@ -381,7 +388,7 @@ pub fn run_optimize_clifford_t( } pub fn optimize_clifford_t_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(run_optimize_clifford_t))?; + m.add_wrapped(wrap_pyfunction!(py_run_optimize_clifford_t))?; Ok(()) } diff --git a/crates/transpiler/src/passes/remove_diagonal_gates_before_measure.rs b/crates/transpiler/src/passes/remove_diagonal_gates_before_measure.rs index cf27c7b7ec57..aa9a8902b02b 100644 --- a/crates/transpiler/src/passes/remove_diagonal_gates_before_measure.rs +++ b/crates/transpiler/src/passes/remove_diagonal_gates_before_measure.rs @@ -12,7 +12,7 @@ /// Remove diagonal gates (including diagonal 2Q gates) before a measurement. use pyo3::prelude::*; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::operations::Operation; use qiskit_circuit::operations::StandardGate; @@ -23,6 +23,17 @@ use qiskit_circuit::operations::StandardGate; /// DAGCircuit: the optimized DAG. #[pyfunction] #[pyo3(name = "remove_diagonal_gates_before_measure")] +pub fn py_run_remove_diagonal_before_measure(dag: &mut PyDAGCircuit) -> PyResult<()> { + run_remove_diagonal_before_measure(dag.try_write()?); + Ok(()) +} + +/// Run the RemoveDiagonalGatesBeforeMeasure pass on `dag`. +/// # Arguments: +/// * `dag` - the DAG to be optimized. +/// +/// # Returns: +/// The optimized DAG. pub fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) { static DIAGONAL_1Q_GATES: [StandardGate; 8] = [ StandardGate::RZ, @@ -92,6 +103,6 @@ pub fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) { } pub fn remove_diagonal_gates_before_measure_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(run_remove_diagonal_before_measure))?; + m.add_wrapped(wrap_pyfunction!(py_run_remove_diagonal_before_measure))?; Ok(()) } diff --git a/crates/transpiler/src/passes/remove_identity_equiv.rs b/crates/transpiler/src/passes/remove_identity_equiv.rs index 8f28da2c3e30..3a219759a8f3 100644 --- a/crates/transpiler/src/passes/remove_identity_equiv.rs +++ b/crates/transpiler/src/passes/remove_identity_equiv.rs @@ -20,7 +20,7 @@ use crate::commutation_checker::try_matrix_with_definition; use crate::gate_metrics::rotation_trace_and_dim; use crate::target::Target; use qiskit_circuit::PhysicalQubit; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::imports; use qiskit_circuit::operations::Param; use qiskit_circuit::operations::StandardGate; @@ -238,10 +238,11 @@ where #[pyo3(name = "remove_identity_equiv", signature=(dag, approx_degree=Some(1.0), target=None))] pub fn py_remove_identity_equiv( py: Python, - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, approx_degree: Option, target: Option<&Target>, ) -> PyResult<()> { + let dag = dag.try_write()?; // TODO: This is a hack to avoid panicking in the case that the global phase contains `Py` // pointers (such as backrefs to `ParameterVector` objects in an expression `Symbol`) that would // get cloned when updating the global phase. It's easier to do it out here than to try to diff --git a/crates/transpiler/src/passes/sabre/layout.rs b/crates/transpiler/src/passes/sabre/layout.rs index 82dca3a2075a..3c31e39721d7 100644 --- a/crates/transpiler/src/passes/sabre/layout.rs +++ b/crates/transpiler/src/passes/sabre/layout.rs @@ -21,7 +21,7 @@ use rand_pcg::Pcg64Mcg; use rayon_cond::CondIterator; use rustworkx_core::petgraph::graph::NodeIndex; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::nlayout::NLayout; use qiskit_circuit::{BlocksMode, PhysicalQubit, VirtualQubit}; use qiskit_util::getenv_use_multiple_threads; @@ -39,8 +39,41 @@ use super::heuristic::Heuristic; use super::route::{RoutingProblem, RoutingResult, RoutingTarget, swap_map, swap_map_trial}; #[allow(clippy::too_many_arguments)] -#[pyfunction] +#[pyfunction(name = "sabre_layout_and_routing")] #[pyo3(signature = (dag, target, heuristic, max_iterations, num_swap_trials, num_random_trials, seed=None, partial_layouts=vec![], skip_routing=false))] +pub fn py_sabre_layout_and_routing( + dag: &mut PyDAGCircuit, + target: &Target, + heuristic: &Heuristic, + max_iterations: usize, + num_swap_trials: usize, + num_random_trials: usize, + seed: Option, + partial_layouts: Vec>>, + skip_routing: bool, +) -> PyResult<(PyDAGCircuit, NLayout, NLayout)> { + sabre_layout_and_routing( + dag.try_write()?, + target, + heuristic, + max_iterations, + num_swap_trials, + num_random_trials, + seed, + partial_layouts, + skip_routing, + ) + .map(|(out_dag, init, out)| { + // Preserve metadata + ( + PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag), + init, + out, + ) + }) +} + +#[allow(clippy::too_many_arguments)] pub fn sabre_layout_and_routing( dag: &mut DAGCircuit, target: &Target, diff --git a/crates/transpiler/src/passes/sabre/mod.rs b/crates/transpiler/src/passes/sabre/mod.rs index ebe0c559a42e..c8bc00b1753a 100644 --- a/crates/transpiler/src/passes/sabre/mod.rs +++ b/crates/transpiler/src/passes/sabre/mod.rs @@ -26,8 +26,8 @@ pub use layout::sabre_layout_and_routing; pub(crate) use route::sabre_routing; pub fn sabre(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(route::sabre_routing))?; - m.add_wrapped(wrap_pyfunction!(layout::sabre_layout_and_routing))?; + m.add_wrapped(wrap_pyfunction!(route::py_sabre_routing))?; + m.add_wrapped(wrap_pyfunction!(layout::py_sabre_layout_and_routing))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/transpiler/src/passes/sabre/route.rs b/crates/transpiler/src/passes/sabre/route.rs index 93d5dae493e4..d5a0cd48fcd2 100644 --- a/crates/transpiler/src/passes/sabre/route.rs +++ b/crates/transpiler/src/passes/sabre/route.rs @@ -41,7 +41,7 @@ use super::vec_map::VecMap; use crate::TranspilerError; use crate::neighbors::Neighbors; use crate::target::{Target, TargetCouplingError}; -use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType, Wire}; +use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType, PyDAGCircuit, Wire}; use qiskit_circuit::nlayout::NLayout; use qiskit_circuit::operations::{ControlFlow, StandardGate}; use qiskit_circuit::packed_instruction::PackedInstruction; @@ -968,8 +968,35 @@ impl State { /// Returns: /// A two-tuple of the newly routed :class:`.DAGCircuit`, and the layout that maps virtual /// qubits to their assigned physical qubits at the *end* of the circuit execution. -#[pyfunction] +#[pyfunction(name = "sabre_routing")] #[pyo3(signature=(dag, target, heuristic, initial_layout, num_trials, seed=None, run_in_parallel=None))] +pub fn py_sabre_routing( + dag: &PyDAGCircuit, + target: &PyRoutingTarget, + heuristic: &Heuristic, + initial_layout: &NLayout, + num_trials: usize, + seed: Option, + run_in_parallel: Option, +) -> PyResult<(PyDAGCircuit, NLayout)> { + sabre_routing( + dag.try_read()?, + target, + heuristic, + initial_layout, + num_trials, + seed, + run_in_parallel, + ) + .map(|(out_dag, layout)| { + // Preserve metadata + ( + PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag), + layout, + ) + }) +} + pub fn sabre_routing( dag: &DAGCircuit, target: &PyRoutingTarget, diff --git a/crates/transpiler/src/passes/schedule_analysis/alap_schedule_analysis.rs b/crates/transpiler/src/passes/schedule_analysis/alap_schedule_analysis.rs index 11e121b60614..3adc01244981 100644 --- a/crates/transpiler/src/passes/schedule_analysis/alap_schedule_analysis.rs +++ b/crates/transpiler/src/passes/schedule_analysis/alap_schedule_analysis.rs @@ -15,7 +15,7 @@ use crate::TranspilerError; use crate::passes::schedule_analysis::{NodeDurations, PyNodeDurations}; use hashbrown::HashMap; use pyo3::prelude::*; -use qiskit_circuit::dag_circuit::{DAGCircuit, Wire}; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit, Wire}; use qiskit_circuit::operations::{OperationRef, PyInstruction, PyOpKind, StandardInstruction}; use qiskit_circuit::{Clbit, Qubit}; use qiskit_util::IndexMap; @@ -154,8 +154,8 @@ pub fn run_alap_schedule_analysis( /// PyDict: A dictionary mapping each DAGOpNode to its scheduled start time. /// #[pyo3(name = "alap_schedule_analysis", signature= (dag, clbit_write_latency, node_durations))] -pub fn py_run_alap_schedule_analysis( - dag: &DAGCircuit, +fn py_run_alap_schedule_analysis( + dag: &PyDAGCircuit, clbit_write_latency: u64, mut node_durations: PyNodeDurations, ) -> PyResult { @@ -165,12 +165,14 @@ pub fn py_run_alap_schedule_analysis( // Get the first duration type let new_durations: NodeDurations = match &*node_durations { NodeDurations::Dt(node_durations) => { - run_alap_schedule_analysis(dag, clbit_write_latency, node_durations)?.into() - } - NodeDurations::Seconds(node_durations) => { - run_alap_schedule_analysis::(dag, clbit_write_latency as f64, node_durations)? - .into() + run_alap_schedule_analysis(dag.try_read()?, clbit_write_latency, node_durations)?.into() } + NodeDurations::Seconds(node_durations) => run_alap_schedule_analysis::( + dag.try_read()?, + clbit_write_latency as f64, + node_durations, + )? + .into(), }; node_durations.update_durations(new_durations)?; Ok(node_durations) diff --git a/crates/transpiler/src/passes/schedule_analysis/asap_schedule_analysis.rs b/crates/transpiler/src/passes/schedule_analysis/asap_schedule_analysis.rs index 70f8e329f203..264ebc7168c3 100644 --- a/crates/transpiler/src/passes/schedule_analysis/asap_schedule_analysis.rs +++ b/crates/transpiler/src/passes/schedule_analysis/asap_schedule_analysis.rs @@ -14,7 +14,7 @@ use crate::TranspilerError; use crate::passes::schedule_analysis::{NodeDurations, PyNodeDurations, TimeOps}; use hashbrown::HashMap; use pyo3::prelude::*; -use qiskit_circuit::dag_circuit::{DAGCircuit, Wire}; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit, Wire}; use qiskit_circuit::operations::{OperationRef, PyInstruction, PyOpKind, StandardInstruction}; use qiskit_circuit::{Clbit, Qubit}; use qiskit_util::IndexMap; @@ -159,7 +159,7 @@ pub fn run_asap_schedule_analysis( /// #[pyo3(name = "asap_schedule_analysis", signature= (dag, clbit_write_latency, node_durations))] pub fn py_run_asap_schedule_analysis( - dag: &DAGCircuit, + dag: &PyDAGCircuit, clbit_write_latency: u64, mut node_durations: PyNodeDurations, ) -> PyResult { @@ -167,12 +167,14 @@ pub fn py_run_asap_schedule_analysis( // Get the first duration type let new_durations: NodeDurations = match &*node_durations { NodeDurations::Dt(node_durations) => { - run_asap_schedule_analysis(dag, clbit_write_latency, node_durations)?.into() - } - NodeDurations::Seconds(node_durations) => { - run_asap_schedule_analysis::(dag, clbit_write_latency as f64, node_durations)? - .into() + run_asap_schedule_analysis(dag.try_read()?, clbit_write_latency, node_durations)?.into() } + NodeDurations::Seconds(node_durations) => run_asap_schedule_analysis::( + dag.try_read()?, + clbit_write_latency as f64, + node_durations, + )? + .into(), }; node_durations.update_durations(new_durations)?; Ok(node_durations) diff --git a/crates/transpiler/src/passes/split_2q_unitaries.rs b/crates/transpiler/src/passes/split_2q_unitaries.rs index 25bdca96a699..7a32264497c9 100644 --- a/crates/transpiler/src/passes/split_2q_unitaries.rs +++ b/crates/transpiler/src/passes/split_2q_unitaries.rs @@ -18,7 +18,7 @@ use pyo3::prelude::*; use rustworkx_core::petgraph::stable_graph::NodeIndex; use smallvec::{SmallVec, smallvec}; -use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType, Wire}; +use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType, PyDAGCircuit, Wire}; use qiskit_circuit::operations::{ArrayType, Operation, OperationRef, Param, UnitaryGate}; use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; use qiskit_circuit::{BlocksMode, Qubit, VarsMode}; @@ -27,6 +27,24 @@ use qiskit_synthesis::two_qubit_decompose::{Specialization, TwoQubitWeylDecompos #[pyfunction] #[pyo3(name = "split_2q_unitaries")] +pub fn py_run_split_2q_unitaries( + dag: &mut PyDAGCircuit, + requested_fidelity: f64, + split_swaps: bool, +) -> PyResult)>> { + Ok( + run_split_2q_unitaries(dag.try_write()?, requested_fidelity, split_swaps)?.map( + |(out_dag, list)| { + // Preserve metadata + ( + PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag), + list, + ) + }, + ), + ) +} + pub fn run_split_2q_unitaries( dag: &mut DAGCircuit, requested_fidelity: f64, @@ -180,6 +198,6 @@ pub fn run_split_2q_unitaries( } pub fn split_2q_unitaries_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(run_split_2q_unitaries))?; + m.add_wrapped(wrap_pyfunction!(py_run_split_2q_unitaries))?; Ok(()) } diff --git a/crates/transpiler/src/passes/substitute_pi4_rotations.rs b/crates/transpiler/src/passes/substitute_pi4_rotations.rs index 2a12d6950ca4..a302d281bbf2 100644 --- a/crates/transpiler/src/passes/substitute_pi4_rotations.rs +++ b/crates/transpiler/src/passes/substitute_pi4_rotations.rs @@ -20,7 +20,7 @@ use qiskit_circuit::packed_instruction::PackedOperation; use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, PI}; use crate::gate_metrics::rotation_trace_and_dim; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::operations::{OperationRef, Param, StandardGate}; use qiskit_circuit::packed_instruction::PackedInstruction; @@ -1078,9 +1078,10 @@ fn rotation_to_pi_div(gate: StandardGate) -> usize { #[pyfunction] #[pyo3(name = "substitute_pi4_rotations")] pub fn py_run_substitute_pi4_rotations( - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, approximation_degree: f64, ) -> PyResult<()> { + let dag = dag.try_write()?; // Skip the pass if there are no rotation gates. if dag .get_op_counts() diff --git a/crates/transpiler/src/passes/synthesize_rz_rotations.rs b/crates/transpiler/src/passes/synthesize_rz_rotations.rs index 1e115a346287..719ea328782f 100644 --- a/crates/transpiler/src/passes/synthesize_rz_rotations.rs +++ b/crates/transpiler/src/passes/synthesize_rz_rotations.rs @@ -14,7 +14,7 @@ use pyo3::prelude::*; use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI}; use crate::QiskitError; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::PyDAGCircuit; use qiskit_circuit::operations::{OperationRef, Param, StandardGate, add_param}; use qiskit_synthesis::ross_selinger::{gridsynth_cleanup, gridsynth_rz}; @@ -106,11 +106,12 @@ fn synthesize_rz_gate_via_gridsynth( #[pyo3(name = "synthesize_rz_rotations")] #[pyo3(signature = (dag, approximation_degree=None, synthesis_error=None, cache_error=None))] pub fn py_run_synthesize_rz_rotations( - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, approximation_degree: Option, synthesis_error: Option, cache_error: Option, ) -> PyResult<()> { + let dag = dag.try_write()?; // Skip the pass if there are no RZ rotation gates. if dag.get_op_counts().keys().all(|k| k != "rz") { return Ok(()); diff --git a/crates/transpiler/src/passes/two_qubit_peephole.rs b/crates/transpiler/src/passes/two_qubit_peephole.rs index 5c53c4c03b09..215976770a44 100644 --- a/crates/transpiler/src/passes/two_qubit_peephole.rs +++ b/crates/transpiler/src/passes/two_qubit_peephole.rs @@ -25,7 +25,7 @@ use rustworkx_core::petgraph::algo::toposort; use rustworkx_core::petgraph::stable_graph::NodeIndex; use rustworkx_core::petgraph::visit::NodeIndexable; -use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, PyDAGCircuit}; use qiskit_circuit::instruction::Parameters; use qiskit_circuit::operations::{Operation, OperationRef, Param, PythonOperation}; use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; @@ -54,17 +54,23 @@ type MappingIterItem = Option<(TwoQSynthesisResult, [Qubit; 2])>; #[pyfunction(name = "two_qubit_unitary_peephole_optimize")] pub fn py_two_qubit_unitary_peephole_optimize( py: Python, - dag: &DAGCircuit, + dag: &PyDAGCircuit, target: &Target, approximation_degree: Option, -) -> PyResult> { +) -> PyResult> { + let dag_ref = dag.try_read()?; let result = py.detach(move || { - two_qubit_unitary_peephole_optimize_analysis(dag, target, approximation_degree) + two_qubit_unitary_peephole_optimize_analysis(dag_ref, target, approximation_degree) })?; let Some(result) = result else { return Ok(None); }; - two_qubit_unitary_peephole_optimize_apply(dag, result) + Ok( + two_qubit_unitary_peephole_optimize_apply(dag_ref, result)?.map(|out_dag| { + // Preserve metadata + PyDAGCircuit::from_dagcircuit_with_cloned_metadata(out_dag, dag) + }), + ) } /// A non-python entry-point to the pass function. diff --git a/crates/transpiler/src/passes/unitary_synthesis/mod.rs b/crates/transpiler/src/passes/unitary_synthesis/mod.rs index 9ddd5d30bc4f..47554de9bc21 100644 --- a/crates/transpiler/src/passes/unitary_synthesis/mod.rs +++ b/crates/transpiler/src/passes/unitary_synthesis/mod.rs @@ -35,7 +35,7 @@ use crate::QiskitError; use crate::target::Target; use qiskit_circuit::bit::QuantumRegister; use qiskit_circuit::circuit_data::CircuitData; -use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType}; +use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType, PyDAGCircuit}; use qiskit_circuit::instruction::Parameters; use qiskit_circuit::operations::{Operation, OperationRef, Param, PythonOperation, StandardGate}; use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; @@ -1108,10 +1108,10 @@ fn conjugate_with_swaps(mut m: ArrayViewMut2) { /// Python entry point to [run_unitary_synthesis]. #[allow(clippy::too_many_arguments)] #[pyfunction] -#[pyo3(name = "run_main_loop", signature=(dag, qubit_indices, min_qubits, target, basis_gates, synth_gates, coupling_edges, approximation_degree=None, natural_direction=None, pulse_optimize=None))] +#[pyo3(name = "run_main_loop", signature=(py_dag, qubit_indices, min_qubits, target, basis_gates, synth_gates, coupling_edges, approximation_degree=None, natural_direction=None, pulse_optimize=None))] pub fn py_unitary_synthesis( py: Python, - dag: &DAGCircuit, + py_dag: &PyDAGCircuit, qubit_indices: Vec, min_qubits: usize, target: Option<&Target>, @@ -1121,7 +1121,8 @@ pub fn py_unitary_synthesis( approximation_degree: Option, natural_direction: Option, pulse_optimize: Option, -) -> PyResult> { +) -> PyResult> { + let dag = py_dag.try_read()?; let config = UnitarySynthesisConfig { approximation: Approximation::from_py_approximation_degree(approximation_degree), use_pulse_optimizer: UsePulseOptimizer::from_py_pulse_optimize(pulse_optimize), @@ -1166,7 +1167,7 @@ pub fn py_unitary_synthesis( min_qubits, ) })?; - Ok(Some(apply_synthesis( + let out_dag = apply_synthesis( dag, node_replace_map, &qubit_indices, @@ -1174,16 +1175,24 @@ pub fn py_unitary_synthesis( min_qubits, &mut state, constraint, - )?)) + )?; + // Preserve metadata + Ok(Some(PyDAGCircuit::from_dagcircuit_with_cloned_metadata( + out_dag, py_dag, + ))) } else { - Ok(Some(serial_run_unitary_synthesis( + let out_dag = serial_run_unitary_synthesis( dag, &synth_gates, min_qubits, &qubit_indices, &mut state, constraint, - )?)) + )?; + // Preserve metadata + Ok(Some(PyDAGCircuit::from_dagcircuit_with_cloned_metadata( + out_dag, py_dag, + ))) } } @@ -1199,7 +1208,7 @@ pub fn py_synthesize_unitary_matrix( approximation_degree: Option, natural_direction: Option, pulse_optimize: Option, -) -> PyResult { +) -> PyResult { let config = UnitarySynthesisConfig { approximation: Approximation::from_py_approximation_degree(approximation_degree), use_pulse_optimizer: UsePulseOptimizer::from_py_pulse_optimize(pulse_optimize), @@ -1241,7 +1250,7 @@ pub fn py_synthesize_unitary_matrix( )? { return Err(QiskitError::new_err("Failed to decompose unitary")); } - Ok(out_dag.build()) + Ok(out_dag.build().into()) } pub fn unitary_synthesis_mod(m: &Bound) -> PyResult<()> { diff --git a/crates/transpiler/src/passes/unroll_3q_or_more.rs b/crates/transpiler/src/passes/unroll_3q_or_more.rs index 1e38c7cdd0fb..d0aaeff1bcaa 100644 --- a/crates/transpiler/src/passes/unroll_3q_or_more.rs +++ b/crates/transpiler/src/passes/unroll_3q_or_more.rs @@ -17,7 +17,7 @@ use rustworkx_core::petgraph::stable_graph::NodeIndex; use crate::QiskitError; use crate::target::Target; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::operations::Operation; use thiserror::Error; @@ -31,8 +31,8 @@ pub enum Unroll3qError { #[pyfunction] #[pyo3(name = "unroll_3q_or_more")] -pub fn py_unroll_3q_or_more(dag: &mut DAGCircuit, target: Option<&Target>) -> PyResult<()> { - run_unroll_3q_or_more(dag, target).map_err(|err| match err { +pub fn py_unroll_3q_or_more(dag: &mut PyDAGCircuit, target: Option<&Target>) -> PyResult<()> { + run_unroll_3q_or_more(dag.try_write()?, target).map_err(|err| match err { Unroll3qError::NoDefinition(e) => QiskitError::new_err(format!( "Cannot unroll all 3q or more gates. No rule to expand {}", e @@ -64,8 +64,7 @@ pub fn run_unroll_3q_or_more( } }; let mut decomp_dag = - match DAGCircuit::from_circuit_data(&definition, false, None, None, None, None) - { + match DAGCircuit::from_circuit_data(&definition, false, None, None) { Ok(dag) => dag, Err(e) => return Some(Err(Unroll3qError::SubstitutionError(e))), }; diff --git a/crates/transpiler/src/passes/vf2/vf2_layout.rs b/crates/transpiler/src/passes/vf2/vf2_layout.rs index f0cb50d011cf..903a1ed371fa 100644 --- a/crates/transpiler/src/passes/vf2/vf2_layout.rs +++ b/crates/transpiler/src/passes/vf2/vf2_layout.rs @@ -25,7 +25,7 @@ use rustworkx_core::petgraph::prelude::*; use pyo3::prelude::*; use pyo3::{IntoPyObjectExt, create_exception, wrap_pyfunction}; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::interner::{Interned, Interner}; use qiskit_circuit::operations::{ControlFlowView, Operation}; use qiskit_circuit::packed_instruction::PackedInstruction; @@ -739,8 +739,24 @@ where Some(score) } -#[pyfunction] +#[pyfunction(name = "vf2_layout_pass_average")] #[pyo3(signature = (dag, target, config, *, strict_direction=false, avg_error_map=None))] +pub fn py_vf2_layout_pass_average( + dag: &PyDAGCircuit, + target: &Target, + config: &Vf2PassConfiguration, + strict_direction: bool, + avg_error_map: Option<&ErrorMap>, +) -> PyResult { + vf2_layout_pass_average( + dag.try_read()?, + target, + config, + strict_direction, + avg_error_map, + ) +} + pub fn vf2_layout_pass_average( dag: &DAGCircuit, target: &Target, @@ -807,8 +823,16 @@ pub fn vf2_layout_pass_average( } } -#[pyfunction] +#[pyfunction(name = "vf2_layout_pass_exact")] #[pyo3(signature = (dag, target, config))] +pub fn py_vf2_layout_pass_exact( + dag: &PyDAGCircuit, + target: &Target, + config: &Vf2PassConfiguration, +) -> PyResult { + vf2_layout_pass_exact(dag.try_read()?, target, config) +} + pub fn vf2_layout_pass_exact( dag: &DAGCircuit, target: &Target, @@ -902,8 +926,8 @@ pub fn vf2_layout_pass_exact( } pub fn vf2_layout_mod(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(vf2_layout_pass_average))?; - m.add_wrapped(wrap_pyfunction!(vf2_layout_pass_exact))?; + m.add_wrapped(wrap_pyfunction!(py_vf2_layout_pass_average))?; + m.add_wrapped(wrap_pyfunction!(py_vf2_layout_pass_exact))?; m.add("MultiQEncountered", m.py().get_type::())?; m.add( "VF2PassConfiguration", diff --git a/crates/transpiler/src/passes/wrap_angles.rs b/crates/transpiler/src/passes/wrap_angles.rs index 55b4dec22871..84270d28b482 100644 --- a/crates/transpiler/src/passes/wrap_angles.rs +++ b/crates/transpiler/src/passes/wrap_angles.rs @@ -17,17 +17,17 @@ use rustworkx_core::petgraph::prelude::*; use crate::angle_bound_registry::{PyWrapAngleRegistry, WrapAngleRegistry}; use crate::target::Target; use qiskit_circuit::PhysicalQubit; -use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::dag_circuit::{DAGCircuit, PyDAGCircuit}; use qiskit_circuit::operations::{Operation, Param}; #[pyfunction] #[pyo3(name = "wrap_angles")] pub fn py_run_wrap_angles( - dag: &mut DAGCircuit, + dag: &mut PyDAGCircuit, target: &Target, bounds_registry: &PyWrapAngleRegistry, ) -> PyResult<()> { - run_wrap_angles(dag, target, bounds_registry.get_inner()) + run_wrap_angles(dag.try_write()?, target, bounds_registry.get_inner()) } pub fn run_wrap_angles( diff --git a/crates/transpiler/src/transpiler.rs b/crates/transpiler/src/transpiler.rs index 0962174dcd90..a175f8ffd757 100644 --- a/crates/transpiler/src/transpiler.rs +++ b/crates/transpiler/src/transpiler.rs @@ -485,7 +485,7 @@ pub fn transpile( approximation_degree: Option, seed: Option, ) -> Result<(CircuitData, TranspileLayout)> { - let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None)?; + let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None)?; let mut commutation_checker = get_standard_commutation_checker(); let mut equivalence_library = generate_standard_equivalence_library(); let sabre_heuristic = get_sabre_heuristic(target)?; @@ -885,9 +885,9 @@ mod tests { ) .unwrap(); - let dag1 = DAGCircuit::from_circuit_data(&circuit1, false, None, None, None, None).unwrap(); + let dag1 = DAGCircuit::from_circuit_data(&circuit1, false, None, None).unwrap(); let circuit2 = CircuitData::from_packed_operations(1, 1, vec![], Param::Float(0.)).unwrap(); - let dag2 = DAGCircuit::from_circuit_data(&circuit2, false, None, None, None, None).unwrap(); + let dag2 = DAGCircuit::from_circuit_data(&circuit2, false, None, None).unwrap(); let mut state = MinPointState::new(&dag1); assert!(state.update_with(&dag1)); @@ -907,7 +907,7 @@ mod tests { #[test] fn test_backtrack_limit_stops_loop() { let circuit1 = CircuitData::from_packed_operations(1, 1, vec![], Param::Float(0.)).unwrap(); - let dag1 = DAGCircuit::from_circuit_data(&circuit1, false, None, None, None, None).unwrap(); + let dag1 = DAGCircuit::from_circuit_data(&circuit1, false, None, None).unwrap(); let circuit2 = CircuitData::from_packed_operations( 1, 1, @@ -921,7 +921,7 @@ mod tests { ) .unwrap(); - let dag2 = DAGCircuit::from_circuit_data(&circuit2, false, None, None, None, None).unwrap(); + let dag2 = DAGCircuit::from_circuit_data(&circuit2, false, None, None).unwrap(); let mut state = MinPointState::new(&dag1); state.update_with(&dag1); @@ -947,8 +947,7 @@ mod tests { Param::Float(0.), ) .unwrap(); - let dag_worst = - DAGCircuit::from_circuit_data(&circuit1, false, None, None, None, None).unwrap(); + let dag_worst = DAGCircuit::from_circuit_data(&circuit1, false, None, None).unwrap(); let circuit2 = CircuitData::from_packed_operations( 1, 1, @@ -961,11 +960,9 @@ mod tests { Param::Float(0.), ) .unwrap(); - let dag_better = - DAGCircuit::from_circuit_data(&circuit2, false, None, None, None, None).unwrap(); + let dag_better = DAGCircuit::from_circuit_data(&circuit2, false, None, None).unwrap(); let circuit3 = CircuitData::from_packed_operations(1, 1, vec![], Param::Float(0.)).unwrap(); - let dag_best = - DAGCircuit::from_circuit_data(&circuit3, false, None, None, None, None).unwrap(); + let dag_best = DAGCircuit::from_circuit_data(&circuit3, false, None, None).unwrap(); let mut state = MinPointState::new(&dag_worst); state.update_with(&dag_worst); diff --git a/releasenotes/notes/upgrade-dag-metadata-b0ed10c25cc29eef.yaml b/releasenotes/notes/upgrade-dag-metadata-b0ed10c25cc29eef.yaml new file mode 100644 index 000000000000..95d342b07aca --- /dev/null +++ b/releasenotes/notes/upgrade-dag-metadata-b0ed10c25cc29eef.yaml @@ -0,0 +1,6 @@ +--- +upgrade_circuits: + - The metadata attributes of :attr:`.DAGCircuit.metadata` and :attr:`.DAGCircuit.name` are + no longer tracked for any nested instance of :class:`.ControlFlowOp` when they contain circuit + blocks of type :class:`.DAGCircuit`. This behavior was never supported/documented and + therefore was never meant to be used in this context. \ No newline at end of file