|
| 1 | +// This code is part of Qiskit. |
| 2 | +// |
| 3 | +// (C) Copyright IBM 2024 |
| 4 | +// |
| 5 | +// This code is licensed under the Apache License, Version 2.0. You may |
| 6 | +// obtain a copy of this license in the LICENSE.txt file in the root directory |
| 7 | +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. |
| 8 | +// |
| 9 | +// Any modifications or derivative works of this code must retain this |
| 10 | +// copyright notice, and modified files need to carry a notice indicating |
| 11 | +// that they have been altered from the originals. |
| 12 | + |
| 13 | +use crate::nlayout::PhysicalQubit; |
| 14 | +use crate::target_transpiler::Target; |
| 15 | +use hashbrown::HashSet; |
| 16 | +use pyo3::prelude::*; |
| 17 | +use qiskit_circuit::imports; |
| 18 | +use qiskit_circuit::operations::OperationRef; |
| 19 | +use qiskit_circuit::{ |
| 20 | + dag_circuit::{DAGCircuit, NodeType}, |
| 21 | + operations::Operation, |
| 22 | + packed_instruction::PackedInstruction, |
| 23 | + Qubit, |
| 24 | +}; |
| 25 | +use smallvec::smallvec; |
| 26 | + |
| 27 | +/// Check if the two-qubit gates follow the right direction with respect to the coupling map. |
| 28 | +/// |
| 29 | +/// Args: |
| 30 | +/// dag: the DAGCircuit to analyze |
| 31 | +/// |
| 32 | +/// coupling_edges: set of edge pairs representing a directed coupling map, against which gate directionality is checked |
| 33 | +/// |
| 34 | +/// Returns: |
| 35 | +/// true iff all two-qubit gates comply with the coupling constraints |
| 36 | +#[pyfunction] |
| 37 | +#[pyo3(name = "check_gate_direction_coupling")] |
| 38 | +fn py_check_with_coupling_map( |
| 39 | + py: Python, |
| 40 | + dag: &DAGCircuit, |
| 41 | + coupling_edges: HashSet<[Qubit; 2]>, |
| 42 | +) -> PyResult<bool> { |
| 43 | + let coupling_map_check = |
| 44 | + |_: &PackedInstruction, op_args: &[Qubit]| -> bool { coupling_edges.contains(op_args) }; |
| 45 | + |
| 46 | + check_gate_direction(py, dag, &coupling_map_check, None) |
| 47 | +} |
| 48 | + |
| 49 | +/// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target. |
| 50 | +/// |
| 51 | +/// Args: |
| 52 | +/// dag: the DAGCircuit to analyze |
| 53 | +/// |
| 54 | +/// target: the Target against which gate directionality compliance is checked |
| 55 | +/// |
| 56 | +/// Returns: |
| 57 | +/// true iff all two-qubit gates comply with the target's coupling constraints |
| 58 | +#[pyfunction] |
| 59 | +#[pyo3(name = "check_gate_direction_target")] |
| 60 | +fn py_check_with_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult<bool> { |
| 61 | + let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool { |
| 62 | + let qargs = smallvec![ |
| 63 | + PhysicalQubit::new(op_args[0].0), |
| 64 | + PhysicalQubit::new(op_args[1].0) |
| 65 | + ]; |
| 66 | + |
| 67 | + target.instruction_supported(inst.op.name(), Some(&qargs)) |
| 68 | + }; |
| 69 | + |
| 70 | + check_gate_direction(py, dag, &target_check, None) |
| 71 | +} |
| 72 | + |
| 73 | +// The main routine for checking gate directionality. |
| 74 | +// |
| 75 | +// gate_complies: a function returning true iff the two-qubit gate direction complies with directionality constraints |
| 76 | +// |
| 77 | +// qubit_mapping: used for mapping the index of a given qubit within an instruction qargs vector to the corresponding qubit index of the |
| 78 | +// original DAGCircuit the pass was called with. This mapping is required since control flow blocks are represented by nested DAGCircuit |
| 79 | +// objects whose instruction qubit indices are relative to the parent DAGCircuit they reside in, thus when we recurse into nested DAGs, we need |
| 80 | +// to carry the mapping context relative to the original DAG. |
| 81 | +// When qubit_mapping is None, the identity mapping is assumed |
| 82 | +fn check_gate_direction<T>( |
| 83 | + py: Python, |
| 84 | + dag: &DAGCircuit, |
| 85 | + gate_complies: &T, |
| 86 | + qubit_mapping: Option<&[Qubit]>, |
| 87 | +) -> PyResult<bool> |
| 88 | +where |
| 89 | + T: Fn(&PackedInstruction, &[Qubit]) -> bool, |
| 90 | +{ |
| 91 | + for node in dag.op_nodes(false) { |
| 92 | + let NodeType::Operation(packed_inst) = &dag.dag[node] else { |
| 93 | + panic!("PackedInstruction is expected"); |
| 94 | + }; |
| 95 | + |
| 96 | + let inst_qargs = dag.get_qargs(packed_inst.qubits); |
| 97 | + |
| 98 | + if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { |
| 99 | + if py_inst.control_flow() { |
| 100 | + let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); // TODO: Take out of the recursion |
| 101 | + let py_inst = py_inst.instruction.bind(py); |
| 102 | + |
| 103 | + for block in py_inst.getattr("blocks")?.iter()? { |
| 104 | + let inner_dag: DAGCircuit = circuit_to_dag.call1((block?,))?.extract()?; |
| 105 | + |
| 106 | + let block_ok = if let Some(mapping) = qubit_mapping { |
| 107 | + let mapping = inst_qargs // Create a temp mapping for the recursive call |
| 108 | + .iter() |
| 109 | + .map(|q| mapping[q.0 as usize]) |
| 110 | + .collect::<Vec<Qubit>>(); |
| 111 | + |
| 112 | + check_gate_direction(py, &inner_dag, gate_complies, Some(&mapping))? |
| 113 | + } else { |
| 114 | + check_gate_direction(py, &inner_dag, gate_complies, Some(inst_qargs))? |
| 115 | + }; |
| 116 | + |
| 117 | + if !block_ok { |
| 118 | + return Ok(false); |
| 119 | + } |
| 120 | + } |
| 121 | + continue; |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + if inst_qargs.len() == 2 |
| 126 | + && !match qubit_mapping { |
| 127 | + // Check gate direction based either on a given custom mapping or the identity mapping |
| 128 | + Some(mapping) => gate_complies( |
| 129 | + packed_inst, |
| 130 | + &[ |
| 131 | + mapping[inst_qargs[0].0 as usize], |
| 132 | + mapping[inst_qargs[1].0 as usize], |
| 133 | + ], |
| 134 | + ), |
| 135 | + None => gate_complies(packed_inst, inst_qargs), |
| 136 | + } |
| 137 | + { |
| 138 | + return Ok(false); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + Ok(true) |
| 143 | +} |
| 144 | + |
| 145 | +#[pymodule] |
| 146 | +pub fn gate_direction(m: &Bound<PyModule>) -> PyResult<()> { |
| 147 | + m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?; |
| 148 | + m.add_wrapped(wrap_pyfunction!(py_check_with_target))?; |
| 149 | + Ok(()) |
| 150 | +} |
0 commit comments