From e7188a04fca5b6fb36fe8fb5e87b658fdcf4a56d Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 15 Sep 2024 11:38:36 +0300 Subject: [PATCH 1/8] First working implementation --- crates/accelerate/src/gate_direction.rs | 174 +++++++++++++++++- .../accelerate/src/target_transpiler/mod.rs | 2 +- crates/circuit/src/dag_circuit.rs | 4 +- 3 files changed, 171 insertions(+), 9 deletions(-) diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 7c7d9d898726..dd5d77f5f5f5 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -10,19 +10,27 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use pyo3::prelude::*; +use pyo3::intern; +use pyo3::types::PyTuple; +use hashbrown::HashSet; +use smallvec::smallvec; use crate::nlayout::PhysicalQubit; use crate::target_transpiler::Target; -use hashbrown::HashSet; -use pyo3::prelude::*; use qiskit_circuit::imports; -use qiskit_circuit::operations::OperationRef; use qiskit_circuit::{ dag_circuit::{DAGCircuit, NodeType}, - operations::Operation, packed_instruction::PackedInstruction, - Qubit, + Qubit, TupleLikeArg, + operations::{OperationRef, StandardGate, Param, Operation}, + circuit_instruction::ExtraInstructionAttributes, }; -use smallvec::smallvec; +use crate::target_transpiler::exceptions::TranspilerError; + +//######################################################################### +// CheckGateDirection analysis pass functions +//######################################################################### + /// Check if the two-qubit gates follow the right direction with respect to the coupling map. /// @@ -142,8 +150,162 @@ where Ok(true) } +//######################################################################### +// GateDirection transformation pass functions +//######################################################################### + + +// type GateDirectionCheckFn<'a> = Box bool + 'a>; + +// // Return a closure function that checks whether the direction of a given gate complies with the given coupling map. This is used in the +// // pass functions below +// fn coupling_direction_checker<'a>(py: &'a Python, dag: &'a DAGCircuit, coupling_edges: &'a Bound) -> GateDirectionCheckFn<'a> { +// Box::new(move |curr_dag: &DAGCircuit, _: &PackedInstruction, op_args: &[Qubit]| -> bool { +// coupling_edges +// .contains(( +// map_qubit(&py, dag, curr_dag, op_args[0]).0, +// map_qubit(&py, dag, curr_dag, op_args[1]).0, +// )) +// .unwrap_or(false) +// }) +// } + +// // Return a closure function that checks whether the direction of a given gate complies with the given target. This is used in the +// // pass functions below +// fn target_direction_checker<'a>(py: &'a Python, dag: &'a DAGCircuit, target: PyRef<'a, Target>) -> GateDirectionCheckFn<'a> { +// Box::new(move |curr_dag: &DAGCircuit, inst: &PackedInstruction, op_args: &[Qubit]| -> bool { +// let mut qargs = Qargs::new(); + +// qargs.push(PhysicalQubit::new( +// map_qubit(py, dag, curr_dag, op_args[0]).0, +// )); +// qargs.push(PhysicalQubit::new( +// map_qubit(py, dag, curr_dag, op_args[1]).0, +// )); + +// target.instruction_supported(inst.op.name(), Some(&qargs)) +// }) +// } + +/// +/// +/// +/// +/// +#[pyfunction] +#[pyo3(name = "fix_gate_direction_coupling")] +fn py_fix_with_coupling_map(py: Python, dag: &DAGCircuit, coupling_edges: HashSet<[Qubit; 2]>) -> PyResult { + let coupling_map_check = + |_: &PackedInstruction, op_args: &[Qubit]| -> bool { coupling_edges.contains(op_args) }; + + + fix_gate_direction(py, dag, coupling_map_check) +} + +fn fix_gate_direction(py: Python, dag: &DAGCircuit, gate_complies: T) -> PyResult +where T: Fn(&PackedInstruction, &[Qubit]) -> bool + +{ + for node in dag.op_nodes(false) { + let NodeType::Operation(packed_inst) = &dag.dag()[node] else {panic!("PackedInstruction is expected"); + }; + + if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { + if py_inst.control_flow() { + todo!("direction fix control flow blocks"); + } + } + + let op_args = dag.get_qargs(packed_inst.qubits); + if op_args.len() != 2 {continue;} + + if !gate_complies(packed_inst, op_args) { + if !gate_complies(packed_inst, &[op_args[1], op_args[0]]) { + return Err(TranspilerError::new_err(format!("The circuit requires a connection between physical qubits {:?}", op_args))); + } + + if let OperationRef::Standard(std_gate) = packed_inst.op.view() { + match std_gate { + StandardGate::CXGate | + StandardGate::CZGate | + StandardGate::ECRGate | + StandardGate::SwapGate | + StandardGate::RZXGate | + StandardGate::RXXGate | + StandardGate::RYYGate => todo!("Direction fix for {:?}", std_gate), + StandardGate::RZZGate => println!("PARAMs: {:?}", packed_inst.params), + _ => continue, + } + } + } + } + + Ok(dag.clone()) // TODO: avoid cloning +} + +//################################################### +// Utility functions to build the replacement dags +// TODO: replace once we have fully Rust-friendly versions of QuantumRegister, DAGCircuit and ParemeterExpression + +fn create_qreg<'py>(py: Python<'py>, size: u32) -> PyResult> { + imports::QUANTUM_REGISTER.get_bound(py).call1((size,)) +} + +fn qreg_bit<'py>(py: Python, qreg: &Bound<'py, PyAny>, index: u32) -> PyResult> { + qreg.call_method1(intern!(py, "__getitem__"), (index,)) +} + +fn std_gate(py: Python, gate: StandardGate) -> PyResult> { + gate.create_py_op(py, None, &ExtraInstructionAttributes::new(None, None, None, None)) +} + +fn parameterized_std_gate(py: Python, gate: StandardGate, param: Param) -> PyResult> { + gate.create_py_op(py, Some(&[param]), &ExtraInstructionAttributes::new(None, None, None, None)) +} + +fn apply_op_back(py: Python, dag: &mut DAGCircuit, op: &Py, qargs: &Vec<&Bound>) -> PyResult<()> { + dag.py_apply_operation_back(py, + op.bind(py).clone(), + Some( TupleLikeArg::extract_bound( &PyTuple::new_bound(py, qargs))? ), + None, + false)?; + + Ok(()) +} + +// fn build_dag(py: Python) -> PyResult { +// let qreg = create_qreg(py, 2)?; +// let new_dag = &mut DAGCircuit::new(py)?; +// new_dag.add_qreg(py, &qreg)?; + +// let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 0)?); + +// apply_standard_gate_back(py, new_dag, StandardGate::HGate, &vec![&q0])?; +// apply_standard_gate_back(py, new_dag, StandardGate::CXGate, &vec![&q0, &q1])?; + +// Ok( new_dag.clone() ) // TODO: Get rid of the clone +// } + +fn cx_replacement_dag(py: Python) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 0)?); + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1, &q0])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; + + Ok( new_dag.clone() ) // TODO: Get rid of the clone +} + + + pub fn gate_direction(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?; m.add_wrapped(wrap_pyfunction!(py_check_with_target))?; + m.add_wrapped(wrap_pyfunction!(py_fix_with_coupling_map))?; Ok(()) } diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 65fc8e80d750..9af4036382f1 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -43,7 +43,7 @@ use instruction_properties::InstructionProperties; use self::exceptions::TranspilerError; -mod exceptions { +pub mod exceptions { use pyo3::import_exception_bound; import_exception_bound! {qiskit.exceptions, QiskitError} import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 5b218b2de818..64757912ee9b 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -987,7 +987,7 @@ def _format(operand): } /// Add all wires in a quantum register. - fn add_qreg(&mut self, py: Python, qreg: &Bound) -> PyResult<()> { + pub fn add_qreg(&mut self, py: Python, qreg: &Bound) -> PyResult<()> { if !qreg.is_instance(imports::QUANTUM_REGISTER.get_bound(py))? { return Err(DAGCircuitError::new_err("not a QuantumRegister instance.")); } @@ -1670,7 +1670,7 @@ def _format(operand): /// Raises: /// DAGCircuitError: if a leaf node is connected to multiple outputs #[pyo3(name = "apply_operation_back", signature = (op, qargs=None, cargs=None, *, check=true))] - fn py_apply_operation_back( + pub fn py_apply_operation_back( &mut self, py: Python, op: Bound, From 257c0d354fb5bde7477b5e846c3da2639169bfaa Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Tue, 27 Aug 2024 12:18:08 +0300 Subject: [PATCH 2/8] Rebase on main with CheckGateDirection ported --- crates/accelerate/src/gate_direction.rs | 2 ++ crates/circuit/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index dd5d77f5f5f5..b7d161e500ed 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -20,6 +20,8 @@ use crate::target_transpiler::Target; use qiskit_circuit::imports; use qiskit_circuit::{ dag_circuit::{DAGCircuit, NodeType}, + error::DAGCircuitError, + operations::Operation, packed_instruction::PackedInstruction, Qubit, TupleLikeArg, operations::{OperationRef, StandardGate, Param, Operation}, diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index dcff558ade64..231eee334da6 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -17,7 +17,7 @@ pub mod converters; pub mod dag_circuit; pub mod dag_node; mod dot_utils; -mod error; +pub mod error; pub mod gate_matrix; pub mod imports; mod interner; From 8dc1337e499d737310b4473e9f36f5764ae5f012 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Mon, 2 Sep 2024 14:15:36 +0300 Subject: [PATCH 3/8] Port GateDirection to Rust Move GateDirection._run_coupling_map and GateDirection._run_target to Rust. Also moving the code to create replacement DAGs to Rust. Both GateDirection and CheckGateDirection will reside in gate_direction.rs. --- crates/accelerate/src/gate_direction.rs | 3 ++- qiskit/transpiler/passes/utils/gate_direction.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index b7d161e500ed..74bd5c2bd3b1 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -56,6 +56,7 @@ fn py_check_with_coupling_map( check_gate_direction(py, dag, &coupling_map_check, None) } + /// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target. /// /// Args: @@ -304,7 +305,7 @@ fn cx_replacement_dag(py: Python) -> PyResult { } - +#[pymodule] pub fn gate_direction(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?; m.add_wrapped(wrap_pyfunction!(py_check_with_target))?; diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 8ea77f7ccd46..9d4c74645898 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -35,6 +35,8 @@ SwapGate, ) +from qiskit._accelerate.gate_direction import fix_gate_direction_coupling + def _swap_node_qargs(node): return DAGOpNode(node.op, node.qargs[::-1], node.cargs) @@ -345,5 +347,6 @@ def run(self, dag): """ layout_map = {bit: i for i, bit in enumerate(dag.qubits)} if self.target is None: - return self._run_coupling_map(dag, layout_map) + return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) + # return self._run_coupling_map(dag, layout_map) return self._run_target(dag, layout_map) From 1a3c38d5c9a34562a026f660b02a492498880e17 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Mon, 26 Aug 2024 16:47:48 +0300 Subject: [PATCH 4/8] First working implementation --- crates/accelerate/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 9111f932e270..a75e222f5799 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -45,6 +45,7 @@ pub mod two_qubit_decompose; pub mod uc_gate; pub mod utils; pub mod vf2_layout; +pub mod gate_direction; mod rayon_ext; #[cfg(test)] From e5ad4214892f208dd93188c2f8f1e57c15e802dd Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 15 Sep 2024 12:32:32 +0300 Subject: [PATCH 5/8] Fix minor build issues --- crates/accelerate/src/gate_direction.rs | 2 -- crates/accelerate/src/lib.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 74bd5c2bd3b1..dcb2256887f0 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -20,8 +20,6 @@ use crate::target_transpiler::Target; use qiskit_circuit::imports; use qiskit_circuit::{ dag_circuit::{DAGCircuit, NodeType}, - error::DAGCircuitError, - operations::Operation, packed_instruction::PackedInstruction, Qubit, TupleLikeArg, operations::{OperationRef, StandardGate, Param, Operation}, diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index a75e222f5799..9111f932e270 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -45,7 +45,6 @@ pub mod two_qubit_decompose; pub mod uc_gate; pub mod utils; pub mod vf2_layout; -pub mod gate_direction; mod rayon_ext; #[cfg(test)] From 0adc48f46c9ea3e34d94f6d935dc9d711623dacb Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 25 Oct 2024 11:43:29 +0300 Subject: [PATCH 6/8] Complete implementation --- crates/accelerate/src/gate_direction.rs | 541 ++++++++++++++---- crates/circuit/src/dag_circuit.rs | 2 +- .../transpiler/passes/utils/gate_direction.py | 288 +--------- test/python/transpiler/test_gate_direction.py | 28 +- 4 files changed, 471 insertions(+), 388 deletions(-) mode change 100644 => 100755 crates/accelerate/src/gate_direction.rs diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs old mode 100644 new mode 100755 index ec60cf8e46c6..2b2065b6bacf --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -10,28 +10,35 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use pyo3::prelude::*; -use pyo3::intern; -use pyo3::types::PyTuple; -use hashbrown::HashSet; -use smallvec::smallvec; use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::exceptions::TranspilerError; use crate::target_transpiler::Target; -use qiskit_circuit::imports; +use hashbrown::HashSet; +use pyo3::intern; +use pyo3::prelude::*; +use pyo3::types::PyTuple; +use qiskit_circuit::operations::OperationRef; use qiskit_circuit::{ + circuit_instruction::CircuitInstruction, + circuit_instruction::ExtraInstructionAttributes, dag_circuit::{DAGCircuit, NodeType}, + dag_node::{DAGNode, DAGOpNode}, + imports, + imports::get_std_gate_class, + operations::Operation, + operations::Param, + operations::StandardGate, packed_instruction::PackedInstruction, Qubit, TupleLikeArg, - operations::{OperationRef, StandardGate, Param, Operation}, - circuit_instruction::ExtraInstructionAttributes, }; -use crate::target_transpiler::exceptions::TranspilerError; +use rustworkx_core::petgraph::stable_graph::NodeIndex; +use smallvec::smallvec; +use std::f64::consts::PI; //######################################################################### // CheckGateDirection analysis pass functions //######################################################################### - /// Check if the two-qubit gates follow the right direction with respect to the coupling map. /// /// Args: @@ -54,7 +61,6 @@ fn py_check_with_coupling_map( check_gate_direction(py, dag, &coupling_map_check, None) } - /// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target. /// /// Args: @@ -106,7 +112,7 @@ where if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { if py_inst.control_flow() { - let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); // TODO: Take out of the recursion + let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); let py_inst = py_inst.instruction.bind(py); for block in py_inst.getattr("blocks")?.iter()? { @@ -155,158 +161,501 @@ where // GateDirection transformation pass functions //######################################################################### - -// type GateDirectionCheckFn<'a> = Box bool + 'a>; - -// // Return a closure function that checks whether the direction of a given gate complies with the given coupling map. This is used in the -// // pass functions below -// fn coupling_direction_checker<'a>(py: &'a Python, dag: &'a DAGCircuit, coupling_edges: &'a Bound) -> GateDirectionCheckFn<'a> { -// Box::new(move |curr_dag: &DAGCircuit, _: &PackedInstruction, op_args: &[Qubit]| -> bool { -// coupling_edges -// .contains(( -// map_qubit(&py, dag, curr_dag, op_args[0]).0, -// map_qubit(&py, dag, curr_dag, op_args[1]).0, -// )) -// .unwrap_or(false) -// }) -// } - -// // Return a closure function that checks whether the direction of a given gate complies with the given target. This is used in the -// // pass functions below -// fn target_direction_checker<'a>(py: &'a Python, dag: &'a DAGCircuit, target: PyRef<'a, Target>) -> GateDirectionCheckFn<'a> { -// Box::new(move |curr_dag: &DAGCircuit, inst: &PackedInstruction, op_args: &[Qubit]| -> bool { -// let mut qargs = Qargs::new(); - -// qargs.push(PhysicalQubit::new( -// map_qubit(py, dag, curr_dag, op_args[0]).0, -// )); -// qargs.push(PhysicalQubit::new( -// map_qubit(py, dag, curr_dag, op_args[1]).0, -// )); - -// target.instruction_supported(inst.op.name(), Some(&qargs)) -// }) -// } - -/// -/// +/// Try to swap two-qubit gate directions using pre-defined mapping to follow the right direction with respect to the coupling map. /// +/// Args: +/// 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 #[pyfunction] #[pyo3(name = "fix_gate_direction_coupling")] -fn py_fix_with_coupling_map(py: Python, dag: &DAGCircuit, coupling_edges: HashSet<[Qubit; 2]>) -> PyResult { - let coupling_map_check = - |_: &PackedInstruction, op_args: &[Qubit]| -> bool { coupling_edges.contains(op_args) }; +fn py_fix_with_coupling_map( + py: Python, + dag: &mut DAGCircuit, + coupling_edges: HashSet<[Qubit; 2]>, +) -> PyResult { + if coupling_edges.is_empty() { + return Ok(dag.clone()); + } + let coupling_map_check = + |_: &PackedInstruction, op_args: &[Qubit]| -> bool { coupling_edges.contains(op_args) }; - fix_gate_direction(py, dag, coupling_map_check) + fix_gate_direction(py, dag, &coupling_map_check, None).cloned() } -fn fix_gate_direction(py: Python, dag: &DAGCircuit, gate_complies: T) -> PyResult -where T: Fn(&PackedInstruction, &[Qubit]) -> bool +/// Try to swap two-qubit gate directions using pre-defined mapping to follow the right direction with respect to the given target. +/// +/// Args: +/// 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 +#[pyfunction] +#[pyo3(name = "fix_gate_direction_target")] +fn py_fix_with_target(py: Python, dag: &mut DAGCircuit, target: &Target) -> PyResult { + let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { + let qargs = smallvec![ + PhysicalQubit::new(op_args[0].0), + PhysicalQubit::new(op_args[1].0) + ]; + // Take this path so Target can check for exact match of the parameterized gate's angle + if let OperationRef::Standard(std_gate) = inst.op.view() { + match std_gate { + StandardGate::RXXGate + | StandardGate::RYYGate + | StandardGate::RZZGate + | StandardGate::RZXGate => { + return target + .py_instruction_supported( + py, + None, + Some(qargs), + Some( + get_std_gate_class(py, std_gate) + .expect("These gates should have Python classes") + .bind(py), + ), + Some(inst.params_view().to_vec()), + ) + .unwrap_or(false) + } + _ => {} + } + } + target.instruction_supported(inst.op.name(), Some(&qargs)) + }; + + fix_gate_direction(py, dag, &target_check, None).cloned() +} + +// The main routine for fixing gate direction. Same parameters are check_gate_direction +fn fix_gate_direction<'a, T>( + py: Python, + dag: &'a mut DAGCircuit, + gate_complies: &T, + qubit_mapping: Option<&[Qubit]>, +) -> PyResult<&'a DAGCircuit> +where + T: Fn(&PackedInstruction, &[Qubit]) -> bool, { + let mut nodes_to_replace: Vec<(NodeIndex, DAGCircuit)> = Vec::new(); + let mut ops_to_replace: Vec<(NodeIndex, Vec>)> = Vec::new(); + for node in dag.op_nodes(false) { - let NodeType::Operation(packed_inst) = &dag.dag()[node] else {panic!("PackedInstruction is expected"); + let NodeType::Operation(packed_inst) = &dag.dag()[node] else { + panic!("PackedInstruction is expected"); }; + let op_args = dag.get_qargs(packed_inst.qubits); + if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { if py_inst.control_flow() { - todo!("direction fix control flow blocks"); + let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); + let dag_to_circuit = imports::DAG_TO_CIRCUIT.get_bound(py); + + let blocks = py_inst.instruction.bind(py).getattr("blocks")?; + let blocks = blocks.downcast::()?; + + let mut blocks_to_replace = Vec::with_capacity(blocks.len()); + for block in blocks { + let mut inner_dag: DAGCircuit = circuit_to_dag.call1((block,))?.extract()?; + + let inner_dag = if let Some(mapping) = qubit_mapping { + let mapping = op_args // Create a temp mapping for the recursive call + .iter() + .map(|q| mapping[q.index()]) + .collect::>(); + + fix_gate_direction(py, &mut inner_dag, gate_complies, Some(&mapping))? + } else { + fix_gate_direction(py, &mut inner_dag, gate_complies, Some(op_args))? + }; + + let circuit = dag_to_circuit.call1((inner_dag.clone(),))?; + blocks_to_replace.push(circuit); + } + + // Store this for replacement outside the dag.op_nodes loop + ops_to_replace.push((node, blocks_to_replace)); + + continue; } } - let op_args = dag.get_qargs(packed_inst.qubits); - if op_args.len() != 2 {continue;} + if op_args.len() != 2 || dag.has_calibration_for_index(py, node)? { + continue; + }; - if !gate_complies(packed_inst, op_args) { - if !gate_complies(packed_inst, &[op_args[1], op_args[0]]) { - return Err(TranspilerError::new_err(format!("The circuit requires a connection between physical qubits {:?}", op_args))); - } + // Take into account qubit index mapping if we're inside a control-flow block + let (op_args0, op_args1) = if let Some(mapping) = qubit_mapping { + (mapping[op_args[0].index()], mapping[op_args[1].index()]) + } else { + (op_args[0], op_args[1]) + }; + + if gate_complies(packed_inst, &[op_args0, op_args1]) { + continue; + } - if let OperationRef::Standard(std_gate) = packed_inst.op.view() { - match std_gate { - StandardGate::CXGate | - StandardGate::CZGate | - StandardGate::ECRGate | - StandardGate::SwapGate | - StandardGate::RZXGate | - StandardGate::RXXGate | - StandardGate::RYYGate => todo!("Direction fix for {:?}", std_gate), - StandardGate::RZZGate => println!("PARAMs: {:?}", packed_inst.params), - _ => continue, + // If the op has a pre-defined replacement - replace if the other direction is supported otherwise error + // If no pre-defined replacement for the op - if the other direction is supported error saying no pre-defined rule otherwise error saying op is not supported + if let OperationRef::Standard(std_gate) = packed_inst.op.view() { + match std_gate { + StandardGate::CXGate + | StandardGate::ECRGate + | StandardGate::CZGate + | StandardGate::SwapGate + | StandardGate::RXXGate + | StandardGate::RYYGate + | StandardGate::RZZGate + | StandardGate::RZXGate => { + if gate_complies(packed_inst, &[op_args1, op_args0]) { + // Store this for replacement outside the dag.op_nodes loop + nodes_to_replace.push((node, replace_dag(py, std_gate, packed_inst)?)); + continue; + } else { + return Err(TranspilerError::new_err(format!( + "The circuit requires a connection between physical qubits {:?} for {}", + op_args, + packed_inst.op.name() + ))); + } } + _ => {} } } + // No matching replacement found + if gate_complies(packed_inst, &[op_args1, op_args0]) + || has_calibration_for_op_node(py, dag, packed_inst, &[op_args1, op_args0])? + { + return Err(TranspilerError::new_err(format!("{} would be supported on {:?} if the direction was swapped, but no rules are known to do that. {:?} can be automatically flipped.", packed_inst.op.name(), op_args, vec!["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]))); + // NOTE: Make sure to update the list of the supported gates if adding more replacements + } else { + return Err(TranspilerError::new_err(format!( + "{} with parameters {:?} is not supported on qubits {:?} in either direction.", + packed_inst.op.name(), + packed_inst.params_view(), + op_args + ))); + } + } + + for (node, op_blocks) in ops_to_replace { + let NodeType::Operation(packed_inst) = &dag.dag()[node] else { + panic!("PackedInstruction is expected"); + }; + let OperationRef::Instruction(py_inst) = packed_inst.op.view() else { + panic!("PyInstruction is expected"); + }; + let new_op = py_inst + .instruction + .bind(py) + .call_method1("replace_blocks", (op_blocks,))?; + + dag.py_substitute_node(dag.get_node(py, node)?.bind(py), &new_op, false, false)?; + } + + for (node, replacemanet_dag) in nodes_to_replace { + dag.py_substitute_node_with_dag( + py, + dag.get_node(py, node)?.bind(py), + &replacemanet_dag, + None, + true, + )?; } - Ok(dag.clone()) // TODO: avoid cloning + Ok(dag) +} + +// Check whether the dag as calibration for a DAGOpNode +fn has_calibration_for_op_node( + py: Python, + dag: &DAGCircuit, + packed_inst: &PackedInstruction, + qargs: &[Qubit], +) -> PyResult { + let py_args = PyTuple::new_bound(py, dag.qubits().map_indices(qargs)); + + let dag_op_node = Py::new( + py, + ( + DAGOpNode { + instruction: CircuitInstruction { + operation: packed_inst.op.clone(), + qubits: py_args.unbind(), + clbits: PyTuple::empty_bound(py).unbind(), + params: packed_inst.params_view().iter().cloned().collect(), + extra_attrs: packed_inst.extra_attrs.clone(), + #[cfg(feature = "cache_pygates")] + py_op: packed_inst.py_op.clone(), + }, + sort_key: "".into_py(py), + }, + DAGNode { node: None }, + ), + )?; + + dag.has_calibration_for(py, dag_op_node.borrow(py)) +} + +// Return a replacement DAG for the given standard gate in the supported list +// TODO: optimize it by caching the DAGs of the non-parametric gates and caching and +// mutating upon request the DAGs of the parametric gates +fn replace_dag( + py: Python, + std_gate: StandardGate, + inst: &PackedInstruction, +) -> PyResult { + let replacement_dag = match std_gate { + StandardGate::CXGate => cx_replacement_dag(py), + StandardGate::ECRGate => ecr_replacement_dag(py), + StandardGate::CZGate => cz_replacement_dag(py), + StandardGate::SwapGate => swap_replacement_dag(py), + StandardGate::RXXGate => rxx_replacement_dag(py, inst.params_view()), + StandardGate::RYYGate => ryy_replacement_dag(py, inst.params_view()), + StandardGate::RZZGate => rzz_replacement_dag(py, inst.params_view()), + StandardGate::RZXGate => rzx_replacement_dag(py, inst.params_view()), + _ => panic!("Mismatch in supported gates assumption"), + }; + + replacement_dag } //################################################### // Utility functions to build the replacement dags // TODO: replace once we have fully Rust-friendly versions of QuantumRegister, DAGCircuit and ParemeterExpression - -fn create_qreg<'py>(py: Python<'py>, size: u32) -> PyResult> { +#[inline] +fn create_qreg(py: Python<'_>, size: u32) -> PyResult> { imports::QUANTUM_REGISTER.get_bound(py).call1((size,)) } +#[inline] fn qreg_bit<'py>(py: Python, qreg: &Bound<'py, PyAny>, index: u32) -> PyResult> { qreg.call_method1(intern!(py, "__getitem__"), (index,)) } +#[inline] fn std_gate(py: Python, gate: StandardGate) -> PyResult> { - gate.create_py_op(py, None, &ExtraInstructionAttributes::new(None, None, None, None)) + gate.create_py_op( + py, + None, + &ExtraInstructionAttributes::new(None, None, None, None), + ) } -fn parameterized_std_gate(py: Python, gate: StandardGate, param: Param) -> PyResult> { - gate.create_py_op(py, Some(&[param]), &ExtraInstructionAttributes::new(None, None, None, None)) +#[inline] +fn parameterized_std_gate(py: Python, gate: StandardGate, param: &[Param]) -> PyResult> { + debug_assert!(param.len() == 1); // + gate.create_py_op( + py, + Some(param), + &ExtraInstructionAttributes::new(None, None, None, None), + ) } -fn apply_op_back(py: Python, dag: &mut DAGCircuit, op: &Py, qargs: &Vec<&Bound>) -> PyResult<()> { - dag.py_apply_operation_back(py, +#[inline] +fn apply_op_back( + py: Python, + dag: &mut DAGCircuit, + op: &Py, + qargs: &Vec<&Bound>, +) -> PyResult<()> { + dag.py_apply_operation_back( + py, op.bind(py).clone(), - Some( TupleLikeArg::extract_bound( &PyTuple::new_bound(py, qargs))? ), + Some(TupleLikeArg::extract_bound(&PyTuple::new_bound(py, qargs))?), None, - false)?; + false, + )?; Ok(()) } -// fn build_dag(py: Python) -> PyResult { -// let qreg = create_qreg(py, 2)?; -// let new_dag = &mut DAGCircuit::new(py)?; -// new_dag.add_qreg(py, &qreg)?; +fn cx_replacement_dag(py: Python) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; -// let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 0)?); + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::CXGate)?, + &vec![&q1, &q0], + )?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; -// apply_standard_gate_back(py, new_dag, StandardGate::HGate, &vec![&q0])?; -// apply_standard_gate_back(py, new_dag, StandardGate::CXGate, &vec![&q0, &q1])?; + Ok(new_dag.clone()) +} + +fn ecr_replacement_dag(py: Python) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + new_dag.add_global_phase(py, &Param::Float(-PI / 2.0))?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + + apply_op_back(py, new_dag, &std_gate(py, StandardGate::SGate)?, &vec![&q0])?; + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::SXGate)?, + &vec![&q0], + )?; + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::SdgGate)?, + &vec![&q0], + )?; + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::SdgGate)?, + &vec![&q1], + )?; + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::SXGate)?, + &vec![&q1], + )?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::SGate)?, &vec![&q1])?; + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::ECRGate)?, + &vec![&q1, &q0], + )?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; -// Ok( new_dag.clone() ) // TODO: Get rid of the clone -// } + Ok(new_dag.clone()) +} -fn cx_replacement_dag(py: Python) -> PyResult { +fn cz_replacement_dag(py: Python) -> PyResult { let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; new_dag.add_qreg(py, &qreg)?; - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 0)?); + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::CZGate)?, + &vec![&q1, &q0], + )?; + + Ok(new_dag.clone()) +} + +fn swap_replacement_dag(py: Python) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + + apply_op_back( + py, + new_dag, + &std_gate(py, StandardGate::SwapGate)?, + &vec![&q1, &q0], + )?; + + Ok(new_dag.clone()) +} + +fn rxx_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + + apply_op_back( + py, + new_dag, + ¶meterized_std_gate(py, StandardGate::RXXGate, param)?, + &vec![&q1, &q0], + )?; + + Ok(new_dag.clone()) +} + +fn ryy_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + + apply_op_back( + py, + new_dag, + ¶meterized_std_gate(py, StandardGate::RYYGate, param)?, + &vec![&q1, &q0], + )?; + + Ok(new_dag.clone()) +} + +fn rzz_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + + apply_op_back( + py, + new_dag, + ¶meterized_std_gate(py, StandardGate::RZZGate, param)?, + &vec![&q1, &q0], + )?; + + Ok(new_dag.clone()) +} + +fn rzx_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let qreg = create_qreg(py, 2)?; + let new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_qreg(py, &qreg)?; + + let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1, &q0])?; + apply_op_back( + py, + new_dag, + ¶meterized_std_gate(py, StandardGate::RZXGate, param)?, + &vec![&q1, &q0], + )?; apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; - Ok( new_dag.clone() ) // TODO: Get rid of the clone + Ok(new_dag.clone()) } - #[pymodule] pub fn gate_direction(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?; m.add_wrapped(wrap_pyfunction!(py_check_with_target))?; m.add_wrapped(wrap_pyfunction!(py_fix_with_coupling_map))?; + m.add_wrapped(wrap_pyfunction!(py_fix_with_target))?; Ok(()) } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index e8f32e5dafee..9ae8ec32aa92 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -955,7 +955,7 @@ def _format(operand): /// Return True if the dag has a calibration defined for the node operation. In this /// case, the operation does not need to be translated to the device basis. - fn has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { + pub fn has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { if !self .calibrations .contains_key(node.instruction.operation.name()) diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 9d4c74645898..d7df153497c6 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -10,42 +10,17 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Rearrange the direction of the cx nodes to match the directed coupling map.""" - -from math import pi +"""Rearrange the direction of the 2-qubit gate nodes to match the directed coupling map.""" from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError - -from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.circuit import QuantumRegister, ControlFlowOp -from qiskit.dagcircuit import DAGCircuit, DAGOpNode -from qiskit.circuit.library.standard_gates import ( - SGate, - SdgGate, - SXGate, - HGate, - CXGate, - CZGate, - ECRGate, - RXXGate, - RYYGate, - RZZGate, - RZXGate, - SwapGate, -) - -from qiskit._accelerate.gate_direction import fix_gate_direction_coupling - - -def _swap_node_qargs(node): - return DAGOpNode(node.op, node.qargs[::-1], node.cargs) +from qiskit._accelerate.gate_direction import fix_gate_direction_coupling, fix_gate_direction_target class GateDirection(TransformationPass): """Modify asymmetric gates to match the hardware coupling direction. - This pass makes use of the following identities:: + This pass supports replacements for the `cx`, `cz`, `ecr`, `swap`, `rzx`, `rxx`, `ryy` and `rzz` gates, + using the following identities:: ┌───┐┌───┐┌───┐ q_0: ──■── q_0: ┤ H ├┤ X ├┤ H ├ @@ -60,6 +35,9 @@ class GateDirection(TransformationPass): │ ECR │ = ┌┴───┴┐├────┤└┬───┬┘│ Ecr │├───┤ q_1: ┤1 ├ q_1: ┤ Sdg ├┤ √X ├─┤ S ├─┤0 ├┤ H ├ └──────┘ └─────┘└────┘ └───┘ └──────┘└───┘ + Note: This is done in terms of less-efficient S/SX/Sdg gates instead of the more natural + `RY(pi /2)` so we have a chance for basis translation to keep things in a discrete basis + during resynthesis, if that's what's being asked for. ┌──────┐ ┌───┐┌──────┐┌───┐ @@ -68,18 +46,18 @@ class GateDirection(TransformationPass): q_1: ┤1 ├ q_1: ┤ H ├┤0 ├┤ H ├ └──────┘ └───┘└──────┘└───┘ + cz, swap, rxx, ryy and rzz directions are fixed by reversing their qargs order. + This pass assumes that the positions of the qubits in the :attr:`.DAGCircuit.qubits` attribute are the physical qubit indices. For example if ``dag.qubits[0]`` is qubit 0 in the :class:`.CouplingMap` or :class:`.Target`. """ - _KNOWN_REPLACEMENTS = frozenset(["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]) - def __init__(self, coupling_map, target=None): """GateDirection pass. Args: - coupling_map (CouplingMap): Directed graph represented a coupling map. + coupling_map (CouplingMap): Directed graph representing a coupling map. target (Target): The backend target to use for this pass. If this is specified it will be used instead of the coupling map """ @@ -87,248 +65,6 @@ def __init__(self, coupling_map, target=None): self.coupling_map = coupling_map self.target = target - # Create the replacement dag and associated register. - self._cx_dag = DAGCircuit() - qr = QuantumRegister(2) - self._cx_dag.add_qreg(qr) - self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) - self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) - self._cx_dag.apply_operation_back(CXGate(), [qr[1], qr[0]], []) - self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) - self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) - - # This is done in terms of less-efficient S/SX/Sdg gates instead of the more natural - # `RY(pi /2)` so we have a chance for basis translation to keep things in a discrete basis - # during resynthesis, if that's what's being asked for. - self._ecr_dag = DAGCircuit() - qr = QuantumRegister(2) - self._ecr_dag.global_phase = -pi / 2 - self._ecr_dag.add_qreg(qr) - self._ecr_dag.apply_operation_back(SGate(), [qr[0]], []) - self._ecr_dag.apply_operation_back(SXGate(), [qr[0]], []) - self._ecr_dag.apply_operation_back(SdgGate(), [qr[0]], []) - self._ecr_dag.apply_operation_back(SdgGate(), [qr[1]], []) - self._ecr_dag.apply_operation_back(SXGate(), [qr[1]], []) - self._ecr_dag.apply_operation_back(SGate(), [qr[1]], []) - self._ecr_dag.apply_operation_back(ECRGate(), [qr[1], qr[0]], []) - self._ecr_dag.apply_operation_back(HGate(), [qr[0]], []) - self._ecr_dag.apply_operation_back(HGate(), [qr[1]], []) - - self._cz_dag = DAGCircuit() - qr = QuantumRegister(2) - self._cz_dag.add_qreg(qr) - self._cz_dag.apply_operation_back(CZGate(), [qr[1], qr[0]], []) - - self._swap_dag = DAGCircuit() - qr = QuantumRegister(2) - self._swap_dag.add_qreg(qr) - self._swap_dag.apply_operation_back(SwapGate(), [qr[1], qr[0]], []) - - # If adding more replacements (either static or dynamic), also update the class variable - # `_KNOWN_REPLACMENTS` to include them in the error messages. - self._static_replacements = { - "cx": self._cx_dag, - "cz": self._cz_dag, - "ecr": self._ecr_dag, - "swap": self._swap_dag, - } - - @staticmethod - def _rzx_dag(parameter): - _rzx_dag = DAGCircuit() - qr = QuantumRegister(2) - _rzx_dag.add_qreg(qr) - _rzx_dag.apply_operation_back(HGate(), [qr[0]], []) - _rzx_dag.apply_operation_back(HGate(), [qr[1]], []) - _rzx_dag.apply_operation_back(RZXGate(parameter), [qr[1], qr[0]], []) - _rzx_dag.apply_operation_back(HGate(), [qr[0]], []) - _rzx_dag.apply_operation_back(HGate(), [qr[1]], []) - return _rzx_dag - - @staticmethod - def _rxx_dag(parameter): - _rxx_dag = DAGCircuit() - qr = QuantumRegister(2) - _rxx_dag.add_qreg(qr) - _rxx_dag.apply_operation_back(RXXGate(parameter), [qr[1], qr[0]], []) - return _rxx_dag - - @staticmethod - def _ryy_dag(parameter): - _ryy_dag = DAGCircuit() - qr = QuantumRegister(2) - _ryy_dag.add_qreg(qr) - _ryy_dag.apply_operation_back(RYYGate(parameter), [qr[1], qr[0]], []) - return _ryy_dag - - @staticmethod - def _rzz_dag(parameter): - _rzz_dag = DAGCircuit() - qr = QuantumRegister(2) - _rzz_dag.add_qreg(qr) - _rzz_dag.apply_operation_back(RZZGate(parameter), [qr[1], qr[0]], []) - return _rzz_dag - - def _run_coupling_map(self, dag, wire_map, edges=None): - if edges is None: - edges = set(self.coupling_map.get_edges()) - if not edges: - return dag - # Don't include directives to avoid things like barrier, which are assumed always supported. - for node in dag.op_nodes(include_directives=False): - if isinstance(node.op, ControlFlowOp): - new_op = node.op.replace_blocks( - dag_to_circuit( - self._run_coupling_map( - circuit_to_dag(block), - { - inner: wire_map[outer] - for outer, inner in zip(node.qargs, block.qubits) - }, - edges, - ) - ) - for block in node.op.blocks - ) - dag.substitute_node(node, new_op, propagate_condition=False) - continue - if len(node.qargs) != 2: - continue - if dag.has_calibration_for(node): - continue - qargs = (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) - if qargs not in edges and (qargs[1], qargs[0]) not in edges: - raise TranspilerError( - f"The circuit requires a connection between physical qubits {qargs}" - ) - if qargs not in edges: - replacement = self._static_replacements.get(node.name) - if replacement is not None: - dag.substitute_node_with_dag(node, replacement) - elif node.name == "rzx": - dag.substitute_node_with_dag(node, self._rzx_dag(*node.op.params)) - elif node.name == "rxx": - dag.substitute_node_with_dag(node, self._rxx_dag(*node.op.params)) - elif node.name == "ryy": - dag.substitute_node_with_dag(node, self._ryy_dag(*node.op.params)) - elif node.name == "rzz": - dag.substitute_node_with_dag(node, self._rzz_dag(*node.op.params)) - else: - raise TranspilerError( - f"'{node.name}' would be supported on '{qargs}' if the direction were" - f" swapped, but no rules are known to do that." - f" {list(self._KNOWN_REPLACEMENTS)} can be automatically flipped." - ) - return dag - - def _run_target(self, dag, wire_map): - # Don't include directives to avoid things like barrier, which are assumed always supported. - for node in dag.op_nodes(include_directives=False): - if isinstance(node.op, ControlFlowOp): - new_op = node.op.replace_blocks( - dag_to_circuit( - self._run_target( - circuit_to_dag(block), - { - inner: wire_map[outer] - for outer, inner in zip(node.qargs, block.qubits) - }, - ) - ) - for block in node.op.blocks - ) - dag.substitute_node(node, new_op, propagate_condition=False) - continue - if len(node.qargs) != 2: - continue - if dag.has_calibration_for(node): - continue - qargs = (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) - swapped = (qargs[1], qargs[0]) - if node.name in self._static_replacements: - if self.target.instruction_supported(node.name, qargs): - continue - if self.target.instruction_supported(node.name, swapped): - dag.substitute_node_with_dag(node, self._static_replacements[node.name]) - else: - raise TranspilerError( - f"The circuit requires a connection between physical qubits {qargs}" - f" for {node.name}" - ) - elif node.name == "rzx": - if self.target.instruction_supported( - qargs=qargs, operation_class=RZXGate, parameters=node.op.params - ): - continue - if self.target.instruction_supported( - qargs=swapped, operation_class=RZXGate, parameters=node.op.params - ): - dag.substitute_node_with_dag(node, self._rzx_dag(*node.op.params)) - else: - raise TranspilerError( - f"The circuit requires a connection between physical qubits {qargs}" - f" for {node.name}" - ) - elif node.name == "rxx": - if self.target.instruction_supported( - qargs=qargs, operation_class=RXXGate, parameters=node.op.params - ): - continue - if self.target.instruction_supported( - qargs=swapped, operation_class=RXXGate, parameters=node.op.params - ): - dag.substitute_node_with_dag(node, self._rxx_dag(*node.op.params)) - else: - raise TranspilerError( - f"The circuit requires a connection between physical qubits {qargs}" - f" for {node.name}" - ) - elif node.name == "ryy": - if self.target.instruction_supported( - qargs=qargs, operation_class=RYYGate, parameters=node.op.params - ): - continue - if self.target.instruction_supported( - qargs=swapped, operation_class=RYYGate, parameters=node.op.params - ): - dag.substitute_node_with_dag(node, self._ryy_dag(*node.op.params)) - else: - raise TranspilerError( - f"The circuit requires a connection between physical qubits {qargs}" - f" for {node.name}" - ) - elif node.name == "rzz": - if self.target.instruction_supported( - qargs=qargs, operation_class=RZZGate, parameters=node.op.params - ): - continue - if self.target.instruction_supported( - qargs=swapped, operation_class=RZZGate, parameters=node.op.params - ): - dag.substitute_node_with_dag(node, self._rzz_dag(*node.op.params)) - else: - raise TranspilerError( - f"The circuit requires a connection between physical qubits {qargs}" - f" for {node.name}" - ) - elif self.target.instruction_supported(node.name, qargs): - continue - elif self.target.instruction_supported(node.name, swapped) or dag.has_calibration_for( - _swap_node_qargs(node) - ): - raise TranspilerError( - f"'{node.name}' would be supported on '{qargs}' if the direction were" - f" swapped, but no rules are known to do that." - f" {list(self._KNOWN_REPLACEMENTS)} can be automatically flipped." - ) - else: - raise TranspilerError( - f"'{node.name}' with parameters '{node.op.params}' is not supported on qubits" - f" '{qargs}' in either direction." - ) - - return dag - def run(self, dag): """Run the GateDirection pass on `dag`. @@ -345,8 +81,6 @@ def run(self, dag): TranspilerError: If the circuit cannot be mapped just by flipping the cx nodes. """ - layout_map = {bit: i for i, bit in enumerate(dag.qubits)} if self.target is None: return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) - # return self._run_coupling_map(dag, layout_map) - return self._run_target(dag, layout_map) + return fix_gate_direction_target(dag, self.target) diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 569a210f8a9d..d8a90d9c66a4 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test the CX Direction pass""" +"""Test the Gate Direction pass""" import unittest from math import pi @@ -65,9 +65,9 @@ def test_direction_error(self): """The mapping cannot be fixed by direction mapper qr0:--------- - qr1:---(+)--- + qr1:----.----- | - qr2:----.---- + qr2:---(+)---- CouplingMap map: [2] <- [0] -> [1] """ @@ -84,9 +84,9 @@ def test_direction_error(self): def test_direction_correct(self): """The CX is in the right direction - qr0:---(+)--- + qr0:----.---- | - qr1:----.---- + qr1:---(+)---- CouplingMap map: [0] -> [1] """ @@ -103,9 +103,9 @@ def test_direction_correct(self): def test_multi_register(self): """The CX is in the right direction - qr0:---(+)--- + qr0:----.----- | - qr1:----.---- + qr1:---(+)---- CouplingMap map: [0] -> [1] """ @@ -123,15 +123,15 @@ def test_multi_register(self): def test_direction_flip(self): """Flip a CX - qr0:----.---- + qr0:---(+)--- | - qr1:---(+)--- + qr1:----.---- CouplingMap map: [0] -> [1] - qr0:-[H]-(+)-[H]-- + qr0:-[H]--.--[H]-- | - qr1:-[H]--.--[H]-- + qr1:-[H]-(+)--[H]-- """ qr = QuantumRegister(2, "qr") circuit = QuantumCircuit(qr) @@ -484,7 +484,7 @@ def test_target_cannot_flip_message(self): circuit.append(gate, (1, 0)) pass_ = GateDirection(None, target) - with self.assertRaisesRegex(TranspilerError, "'my_2q_gate' would be supported.*"): + with self.assertRaisesRegex(TranspilerError, "my_2q_gate would be supported.*"): pass_(circuit) def test_target_cannot_flip_message_calibrated(self): @@ -499,7 +499,7 @@ def test_target_cannot_flip_message_calibrated(self): circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) pass_ = GateDirection(None, target) - with self.assertRaisesRegex(TranspilerError, "'my_2q_gate' would be supported.*"): + with self.assertRaisesRegex(TranspilerError, "my_2q_gate would be supported.*"): pass_(circuit) def test_target_unknown_gate_message(self): @@ -513,7 +513,7 @@ def test_target_unknown_gate_message(self): circuit.append(gate, (0, 1)) pass_ = GateDirection(None, target) - with self.assertRaisesRegex(TranspilerError, "'my_2q_gate'.*not supported on qubits .*"): + with self.assertRaisesRegex(TranspilerError, "my_2q_gate.*not supported on qubits .*"): pass_(circuit) def test_allows_calibrated_gates_coupling_map(self): From 4b363a19bd5920926d3f31748738f7b2070e4dd2 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Tue, 29 Oct 2024 14:23:50 +0200 Subject: [PATCH 7/8] Address review comments Mainly replace going through Python with Rust-native calls in various places, most prominently when building replacement DAGs. --- crates/accelerate/src/gate_direction.rs | 238 ++++++++++-------------- 1 file changed, 103 insertions(+), 135 deletions(-) diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 2b2065b6bacf..46883dcd987e 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -18,9 +18,11 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyTuple; use qiskit_circuit::operations::OperationRef; +use qiskit_circuit::packed_instruction::PackedOperation; use qiskit_circuit::{ circuit_instruction::CircuitInstruction, circuit_instruction::ExtraInstructionAttributes, + converters::{circuit_to_dag, QuantumCircuitData}, dag_circuit::{DAGCircuit, NodeType}, dag_node::{DAGNode, DAGOpNode}, imports, @@ -29,10 +31,10 @@ use qiskit_circuit::{ operations::Param, operations::StandardGate, packed_instruction::PackedInstruction, - Qubit, TupleLikeArg, + Qubit, }; use rustworkx_core::petgraph::stable_graph::NodeIndex; -use smallvec::smallvec; +use smallvec::{smallvec, SmallVec}; use std::f64::consts::PI; //######################################################################### @@ -249,15 +251,12 @@ where let mut ops_to_replace: Vec<(NodeIndex, Vec>)> = Vec::new(); for node in dag.op_nodes(false) { - let NodeType::Operation(packed_inst) = &dag.dag()[node] else { - panic!("PackedInstruction is expected"); - }; + let packed_inst = dag.dag()[node].unwrap_operation(); let op_args = dag.get_qargs(packed_inst.qubits); if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { if py_inst.control_flow() { - let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); let dag_to_circuit = imports::DAG_TO_CIRCUIT.get_bound(py); let blocks = py_inst.instruction.bind(py).getattr("blocks")?; @@ -265,7 +264,13 @@ where let mut blocks_to_replace = Vec::with_capacity(blocks.len()); for block in blocks { - let mut inner_dag: DAGCircuit = circuit_to_dag.call1((block,))?.extract()?; + let mut inner_dag = circuit_to_dag( + py, + QuantumCircuitData::extract_bound(&block)?, + false, + None, + None, + )?; let inner_dag = if let Some(mapping) = qubit_mapping { let mapping = op_args // Create a temp mapping for the recursive call @@ -348,9 +353,7 @@ where } for (node, op_blocks) in ops_to_replace { - let NodeType::Operation(packed_inst) = &dag.dag()[node] else { - panic!("PackedInstruction is expected"); - }; + let packed_inst = dag.dag()[node].unwrap_operation(); let OperationRef::Instruction(py_inst) = packed_inst.op.view() else { panic!("PyInstruction is expected"); }; @@ -431,222 +434,187 @@ fn replace_dag( //################################################### // Utility functions to build the replacement dags -// TODO: replace once we have fully Rust-friendly versions of QuantumRegister, DAGCircuit and ParemeterExpression -#[inline] -fn create_qreg(py: Python<'_>, size: u32) -> PyResult> { - imports::QUANTUM_REGISTER.get_bound(py).call1((size,)) -} - -#[inline] -fn qreg_bit<'py>(py: Python, qreg: &Bound<'py, PyAny>, index: u32) -> PyResult> { - qreg.call_method1(intern!(py, "__getitem__"), (index,)) -} - +// +// TODO: replace this once we have a Rust version of QuantumRegister #[inline] -fn std_gate(py: Python, gate: StandardGate) -> PyResult> { - gate.create_py_op( - py, - None, - &ExtraInstructionAttributes::new(None, None, None, None), - ) -} +fn add_qreg(py: Python, dag: &mut DAGCircuit, num_qubits: u32) -> PyResult> { + let qreg = imports::QUANTUM_REGISTER + .get_bound(py) + .call1((num_qubits,))?; + dag.add_qreg(py, &qreg)?; + let mut qargs = Vec::new(); + + for i in 0..num_qubits { + let qubit = qreg.call_method1(intern!(py, "__getitem__"), (i,))?; + qargs.push(dag.qubits().find(&qubit).expect("Qubit should stored")); + } -#[inline] -fn parameterized_std_gate(py: Python, gate: StandardGate, param: &[Param]) -> PyResult> { - debug_assert!(param.len() == 1); // - gate.create_py_op( - py, - Some(param), - &ExtraInstructionAttributes::new(None, None, None, None), - ) + Ok(qargs) } #[inline] -fn apply_op_back( +fn apply_operation_back( py: Python, dag: &mut DAGCircuit, - op: &Py, - qargs: &Vec<&Bound>, + gate: StandardGate, + qargs: &[Qubit], + param: Option>, ) -> PyResult<()> { - dag.py_apply_operation_back( + dag.apply_operation_back( py, - op.bind(py).clone(), - Some(TupleLikeArg::extract_bound(&PyTuple::new_bound(py, qargs))?), + PackedOperation::from_standard(gate), + qargs, + &[], + param, + ExtraInstructionAttributes::new(None, None, None, None), + #[cfg(feature = "cache_pygates")] None, - false, )?; Ok(()) } fn cx_replacement_dag(py: Python) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; - apply_op_back( + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[1]], None)?; + apply_operation_back( py, new_dag, - &std_gate(py, StandardGate::CXGate)?, - &vec![&q1, &q0], + StandardGate::CXGate, + &[qargs[1], qargs[0]], + None, )?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[1]], None)?; Ok(new_dag.clone()) } fn ecr_replacement_dag(py: Python) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; new_dag.add_global_phase(py, &Param::Float(-PI / 2.0))?; - - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); - - apply_op_back(py, new_dag, &std_gate(py, StandardGate::SGate)?, &vec![&q0])?; - apply_op_back( - py, - new_dag, - &std_gate(py, StandardGate::SXGate)?, - &vec![&q0], - )?; - apply_op_back( - py, - new_dag, - &std_gate(py, StandardGate::SdgGate)?, - &vec![&q0], - )?; - apply_op_back( - py, - new_dag, - &std_gate(py, StandardGate::SdgGate)?, - &vec![&q1], - )?; - apply_op_back( - py, - new_dag, - &std_gate(py, StandardGate::SXGate)?, - &vec![&q1], - )?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::SGate)?, &vec![&q1])?; - apply_op_back( + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + apply_operation_back(py, new_dag, StandardGate::SGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::SXGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::SdgGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::SdgGate, &[qargs[1]], None)?; + apply_operation_back(py, new_dag, StandardGate::SXGate, &[qargs[1]], None)?; + apply_operation_back(py, new_dag, StandardGate::SGate, &[qargs[1]], None)?; + apply_operation_back( py, new_dag, - &std_gate(py, StandardGate::ECRGate)?, - &vec![&q1, &q0], + StandardGate::ECRGate, + &[qargs[1], qargs[0]], + None, )?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[1]], None)?; Ok(new_dag.clone()) } fn cz_replacement_dag(py: Python) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); - - apply_op_back( + apply_operation_back( py, new_dag, - &std_gate(py, StandardGate::CZGate)?, - &vec![&q1, &q0], + StandardGate::CZGate, + &[qargs[1], qargs[0]], + None, )?; Ok(new_dag.clone()) } fn swap_replacement_dag(py: Python) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; - - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - apply_op_back( + apply_operation_back( py, new_dag, - &std_gate(py, StandardGate::SwapGate)?, - &vec![&q1, &q0], + StandardGate::SwapGate, + &[qargs[1], qargs[0]], + None, )?; Ok(new_dag.clone()) } fn rxx_replacement_dag(py: Python, param: &[Param]) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; - - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - apply_op_back( + apply_operation_back( py, new_dag, - ¶meterized_std_gate(py, StandardGate::RXXGate, param)?, - &vec![&q1, &q0], + StandardGate::RXXGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), )?; Ok(new_dag.clone()) } fn ryy_replacement_dag(py: Python, param: &[Param]) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; - - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - apply_op_back( + apply_operation_back( py, new_dag, - ¶meterized_std_gate(py, StandardGate::RYYGate, param)?, - &vec![&q1, &q0], + StandardGate::RYYGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), )?; Ok(new_dag.clone()) } fn rzz_replacement_dag(py: Python, param: &[Param]) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); - - apply_op_back( + apply_operation_back( py, new_dag, - ¶meterized_std_gate(py, StandardGate::RZZGate, param)?, - &vec![&q1, &q0], + StandardGate::RZZGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), )?; Ok(new_dag.clone()) } fn rzx_replacement_dag(py: Python, param: &[Param]) -> PyResult { - let qreg = create_qreg(py, 2)?; let new_dag = &mut DAGCircuit::new(py)?; - new_dag.add_qreg(py, &qreg)?; - - let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 1)?); + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; - apply_op_back( + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[1]], None)?; + apply_operation_back( py, new_dag, - ¶meterized_std_gate(py, StandardGate::RZXGate, param)?, - &vec![&q1, &q0], + StandardGate::RZXGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), )?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?; - apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[0]], None)?; + apply_operation_back(py, new_dag, StandardGate::HGate, &[qargs[1]], None)?; Ok(new_dag.clone()) } From 79a62174af9c6955a3d9b05adcb456d939641950 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 3 Nov 2024 12:44:01 +0200 Subject: [PATCH 8/8] Rename functions and other minor changes --- crates/accelerate/src/gate_direction.rs | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 46883dcd987e..a20dfea00535 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -52,7 +52,7 @@ 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_with_coupling_map( +fn py_check_direction_coupling_map( py: Python, dag: &DAGCircuit, coupling_edges: HashSet<[Qubit; 2]>, @@ -74,7 +74,7 @@ fn py_check_with_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_with_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult { +fn py_check_direction_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { let qargs = smallvec![ PhysicalQubit::new(op_args[0].0), @@ -174,7 +174,7 @@ where /// the transformed DAGCircuit #[pyfunction] #[pyo3(name = "fix_gate_direction_coupling")] -fn py_fix_with_coupling_map( +fn py_fix_direction_coupling_map( py: Python, dag: &mut DAGCircuit, coupling_edges: HashSet<[Qubit; 2]>, @@ -200,7 +200,11 @@ fn py_fix_with_coupling_map( /// the transformed DAGCircuit #[pyfunction] #[pyo3(name = "fix_gate_direction_target")] -fn py_fix_with_target(py: Python, dag: &mut DAGCircuit, target: &Target) -> PyResult { +fn py_fix_direction_target( + py: Python, + dag: &mut DAGCircuit, + target: &Target, +) -> PyResult { let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { let qargs = smallvec![ PhysicalQubit::new(op_args[0].0), @@ -446,7 +450,11 @@ fn add_qreg(py: Python, dag: &mut DAGCircuit, num_qubits: u32) -> PyResult PyResult { #[pymodule] pub fn gate_direction(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?; - m.add_wrapped(wrap_pyfunction!(py_check_with_target))?; - m.add_wrapped(wrap_pyfunction!(py_fix_with_coupling_map))?; - m.add_wrapped(wrap_pyfunction!(py_fix_with_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(()) }