diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs old mode 100644 new mode 100755 index 3143a11a22a2..a20dfea00535 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -11,18 +11,35 @@ // that they have been altered from the originals. use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::exceptions::TranspilerError; use crate::target_transpiler::Target; use hashbrown::HashSet; +use pyo3::intern; use pyo3::prelude::*; -use qiskit_circuit::imports; +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, + imports::get_std_gate_class, operations::Operation, + operations::Param, + operations::StandardGate, packed_instruction::PackedInstruction, Qubit, }; -use smallvec::smallvec; +use rustworkx_core::petgraph::stable_graph::NodeIndex; +use smallvec::{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. /// @@ -35,7 +52,7 @@ use smallvec::smallvec; /// 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]>, @@ -57,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), @@ -97,7 +114,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()? { @@ -142,8 +159,479 @@ where Ok(true) } +//######################################################################### +// GateDirection transformation pass functions +//######################################################################### + +/// 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_direction_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, None).cloned() +} + +/// 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_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), + 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 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 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 = 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 + .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; + } + } + + if op_args.len() != 2 || dag.has_calibration_for_index(py, node)? { + continue; + }; + + // 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 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 packed_inst = dag.dag()[node].unwrap_operation(); + 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) +} + +// 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 this once we have a Rust version of QuantumRegister +#[inline] +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 have been stored in the DAGCircuit"), + ); + } + + Ok(qargs) +} + +#[inline] +fn apply_operation_back( + py: Python, + dag: &mut DAGCircuit, + gate: StandardGate, + qargs: &[Qubit], + param: Option>, +) -> PyResult<()> { + dag.apply_operation_back( + py, + PackedOperation::from_standard(gate), + qargs, + &[], + param, + ExtraInstructionAttributes::default(), + #[cfg(feature = "cache_pygates")] + None, + )?; + + Ok(()) +} + +fn cx_replacement_dag(py: Python) -> PyResult { + let new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + 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, + StandardGate::CXGate, + &[qargs[1], qargs[0]], + None, + )?; + 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 new_dag = &mut DAGCircuit::new(py)?; + new_dag.add_global_phase(py, &Param::Float(-PI / 2.0))?; + 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, + StandardGate::ECRGate, + &[qargs[1], qargs[0]], + None, + )?; + 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 new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + apply_operation_back( + py, + new_dag, + StandardGate::CZGate, + &[qargs[1], qargs[0]], + None, + )?; + + Ok(new_dag.clone()) +} + +fn swap_replacement_dag(py: Python) -> PyResult { + let new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + apply_operation_back( + py, + new_dag, + StandardGate::SwapGate, + &[qargs[1], qargs[0]], + None, + )?; + + Ok(new_dag.clone()) +} + +fn rxx_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + apply_operation_back( + py, + new_dag, + StandardGate::RXXGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), + )?; + + Ok(new_dag.clone()) +} + +fn ryy_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + apply_operation_back( + py, + new_dag, + StandardGate::RYYGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), + )?; + + Ok(new_dag.clone()) +} + +fn rzz_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + apply_operation_back( + py, + new_dag, + StandardGate::RZZGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), + )?; + + Ok(new_dag.clone()) +} + +fn rzx_replacement_dag(py: Python, param: &[Param]) -> PyResult { + let new_dag = &mut DAGCircuit::new(py)?; + let qargs = add_qreg(py, new_dag, 2)?; + let qargs = qargs.as_slice(); + + 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, + StandardGate::RZXGate, + &[qargs[1], qargs[0]], + Some(SmallVec::from(param)), + )?; + 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()) +} + +#[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_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/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index e631b6971580..15c0bc5b1a97 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -961,7 +961,7 @@ def _format(operand): /// case, the operation does not need to be translated to the device basis. /// /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 - fn has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { + pub fn has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { emit_pulse_dependency_deprecation( py, "method ``qiskit.dagcircuit.dagcircuit.DAGCircuit.has_calibration_for``", diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 8705d52d1aa7..a4064d44b917 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; pub mod interner; diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 198eb34c501f..fbdcc9321cbb 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -10,40 +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, -) - - -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 ├ @@ -58,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. ┌──────┐ ┌───┐┌──────┐┌───┐ @@ -66,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 """ @@ -85,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`. @@ -343,7 +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 self._run_coupling_map(dag, layout_map) - return self._run_target(dag, layout_map) + return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) + 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 592ce124263c..41734d1ca8ee 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) @@ -492,7 +492,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): @@ -508,7 +508,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): @@ -522,7 +522,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):