Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 30bf21b

Browse files
committedSep 2, 2024·
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. Depends on the check-gate-direction branch.
1 parent 69d6533 commit 30bf21b

File tree

4 files changed

+191
-57
lines changed

4 files changed

+191
-57
lines changed
 

‎crates/accelerate/src/gate_direction.rs

+184-53
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,69 @@ use crate::nlayout::PhysicalQubit;
1414
use crate::target_transpiler::{Qargs, Target};
1515
use pyo3::prelude::*;
1616
use pyo3::types::{PySet, PyTuple};
17-
use qiskit_circuit::imports;
18-
use qiskit_circuit::operations::{OperationRef, PyInstruction};
17+
use pyo3::intern;
18+
use qiskit_circuit::{imports, TupleLikeArg};
19+
use qiskit_circuit::operations::{OperationRef, PyInstruction, StandardGate};
1920
use qiskit_circuit::{
2021
dag_circuit::{DAGCircuit, NodeType},
2122
error::DAGCircuitError,
2223
operations::Operation,
2324
packed_instruction::PackedInstruction,
24-
Qubit,
25+
Qubit, operations::Param
2526
};
27+
use crate::target_transpiler::exceptions::TranspilerError;
28+
29+
30+
31+
type GateDirectionCheckFn<'a> = Box<dyn Fn(&DAGCircuit, &PackedInstruction, &[Qubit]) -> bool + 'a>;
32+
33+
// Return a closure function that checks whether the direction of a given gate complies with the given coupling map. This is used in the
34+
// pass functions below
35+
fn coupling_direction_checker<'a>(py: &'a Python, dag: &'a DAGCircuit, coupling_edges: &'a Bound<PySet>) -> GateDirectionCheckFn<'a> {
36+
Box::new(move |curr_dag: &DAGCircuit, _: &PackedInstruction, op_args: &[Qubit]| -> bool {
37+
coupling_edges
38+
.contains((
39+
map_qubit(&py, dag, curr_dag, op_args[0]).0,
40+
map_qubit(&py, dag, curr_dag, op_args[1]).0,
41+
))
42+
.unwrap_or(false)
43+
})
44+
}
45+
46+
// Return a closure function that checks whether the direction of a given gate complies with the given target. This is used in the
47+
// pass functions below
48+
fn target_direction_checker<'a>(py: &'a Python, dag: &'a DAGCircuit, target: PyRef<'a, Target>) -> GateDirectionCheckFn<'a> {
49+
Box::new(move |curr_dag: &DAGCircuit, inst: &PackedInstruction, op_args: &[Qubit]| -> bool {
50+
let mut qargs = Qargs::new();
51+
52+
qargs.push(PhysicalQubit::new(
53+
map_qubit(py, dag, curr_dag, op_args[0]).0,
54+
));
55+
qargs.push(PhysicalQubit::new(
56+
map_qubit(py, dag, curr_dag, op_args[1]).0,
57+
));
58+
59+
target.instruction_supported(inst.op.name(), Some(&qargs))
60+
})
61+
}
62+
63+
// Map a qubit interned in curr_dag to its corresponding qubit entry interned in orig_dag.
64+
// Required for checking control flow instructions which are represented in blocks (circuits)
65+
// and converted to DAGCircuit with possibly different qargs than the original one.
66+
fn map_qubit(py: &Python, orig_dag: &DAGCircuit, curr_dag: &DAGCircuit, qubit: Qubit) -> Qubit {
67+
let qubit = curr_dag
68+
.qubits
69+
.get(qubit)
70+
.expect("Qubit in curr_dag")
71+
.bind(*py);
72+
orig_dag.qubits.find(qubit).expect("Qubit in orig_dag")
73+
}
74+
75+
76+
//#########################################################################
77+
// CheckGateDirection analysis pass functions
78+
//#########################################################################
79+
2680

2781
/// Check if the two-qubit gates follow the right direction with respect to the coupling map.
2882
///
@@ -40,19 +94,10 @@ fn py_check_with_coupling_map(
4094
dag: &DAGCircuit,
4195
coupling_edges: &Bound<PySet>,
4296
) -> PyResult<bool> {
43-
let coupling_map_check =
44-
|curr_dag: &DAGCircuit, _: &PackedInstruction, op_args: &[Qubit]| -> bool {
45-
coupling_edges
46-
.contains((
47-
map_qubit(py, dag, curr_dag, op_args[0]).0,
48-
map_qubit(py, dag, curr_dag, op_args[1]).0,
49-
))
50-
.unwrap_or(false)
51-
};
52-
53-
check_gate_direction(py, dag, &coupling_map_check)
97+
check_gate_direction(py, dag, &coupling_direction_checker(&py, dag, coupling_edges))
5498
}
5599

100+
56101
/// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target.
57102
///
58103
/// Args:
@@ -67,31 +112,42 @@ fn py_check_with_coupling_map(
67112
fn py_check_with_target(py: Python, dag: &DAGCircuit, target: &Bound<Target>) -> PyResult<bool> {
68113
let target = target.borrow();
69114

70-
let target_check =
71-
|curr_dag: &DAGCircuit, inst: &PackedInstruction, op_args: &[Qubit]| -> bool {
72-
let mut qargs = Qargs::new();
73-
74-
qargs.push(PhysicalQubit::new(
75-
map_qubit(py, dag, curr_dag, op_args[0]).0,
76-
));
77-
qargs.push(PhysicalQubit::new(
78-
map_qubit(py, dag, curr_dag, op_args[1]).0,
79-
));
115+
check_gate_direction(py, dag, &target_direction_checker(&py, dag, target))
116+
}
80117

81-
target.instruction_supported(inst.op.name(), Some(&qargs))
118+
// The main routine for checking gate directionality
119+
fn check_gate_direction(py: Python, dag: &DAGCircuit, gate_complies: &GateDirectionCheckFn) -> PyResult<bool>
120+
{
121+
for node in dag.op_nodes(false) {
122+
let NodeType::Operation(packed_inst) = &dag.dag[node] else {
123+
return Err(DAGCircuitError::new_err("PackedInstruction is expected"));
82124
};
83125

84-
check_gate_direction(py, dag, &target_check)
126+
if let OperationRef::Instruction(py_inst) = packed_inst.op.view() {
127+
if py_inst.control_flow() {
128+
if !check_gate_direction_control_flow(py, py_inst, gate_complies)? {
129+
return Ok(false);
130+
} else {
131+
continue;
132+
}
133+
}
134+
}
135+
136+
let op_args = dag.get_inst_qubits(packed_inst.qubits);
137+
if op_args.len() == 2 && !gate_complies(dag, packed_inst, op_args) {
138+
return Ok(false);
139+
}
140+
}
141+
142+
Ok(true)
85143
}
86144

87145
// Handle a control flow instruction, namely check recursively into its circuit blocks
88-
fn check_gate_direction_control_flow<T>(
146+
fn check_gate_direction_control_flow(
89147
py: Python,
90148
py_inst: &PyInstruction,
91-
gate_complies: &T,
149+
gate_complies: &GateDirectionCheckFn,
92150
) -> PyResult<bool>
93-
where
94-
T: Fn(&DAGCircuit, &PackedInstruction, &[Qubit]) -> bool,
95151
{
96152
let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); // TODO: Take out of the recursion
97153
let py_inst = py_inst.instruction.bind(py);
@@ -110,50 +166,125 @@ where
110166
Ok(true)
111167
}
112168

113-
// The main routine for checking gate directionality
114-
fn check_gate_direction<T>(py: Python, dag: &DAGCircuit, gate_complies: &T) -> PyResult<bool>
115-
where
116-
T: Fn(&DAGCircuit, &PackedInstruction, &[Qubit]) -> bool,
117-
{
169+
//#########################################################################
170+
// GateDirection transformation pass functions
171+
//#########################################################################
172+
173+
///
174+
///
175+
///
176+
///
177+
///
178+
#[pyfunction]
179+
#[pyo3(name = "fix_gate_direction_coupling")]
180+
fn py_fix_with_coupling_map(py: Python, dag: &DAGCircuit, coupling_edges: &Bound<PySet>) -> PyResult<DAGCircuit> {
181+
fix_gate_direction(py, dag, &coupling_direction_checker(&py, dag, coupling_edges))
182+
}
183+
184+
185+
fn fix_gate_direction(py: Python, dag: &DAGCircuit, gate_complies: &GateDirectionCheckFn) -> PyResult<DAGCircuit> {
118186
for node in dag.op_nodes(false) {
119187
let NodeType::Operation(packed_inst) = &dag.dag[node] else {
120188
return Err(DAGCircuitError::new_err("PackedInstruction is expected"));
121189
};
122190

123191
if let OperationRef::Instruction(py_inst) = packed_inst.op.view() {
124192
if py_inst.control_flow() {
125-
if !check_gate_direction_control_flow(py, py_inst, gate_complies)? {
126-
return Ok(false);
127-
} else {
128-
continue;
129-
}
193+
todo!("direction fix control flow blocks");
130194
}
131195
}
132196

133197
let op_args = dag.get_inst_qubits(packed_inst.qubits);
134-
if op_args.len() == 2 && !gate_complies(dag, packed_inst, op_args) {
135-
return Ok(false);
198+
if op_args.len() != 2 {continue;}
199+
200+
if !gate_complies(dag, packed_inst, op_args) {
201+
if !gate_complies(dag, packed_inst, &[op_args[1], op_args[0]]) {
202+
return Err(TranspilerError::new_err(format!("The circuit requires a connection between physical qubits {:?}", op_args)));
203+
}
204+
205+
if let OperationRef::Standard(std_gate) = packed_inst.op.view() {
206+
match std_gate {
207+
StandardGate::CXGate |
208+
StandardGate::CZGate |
209+
StandardGate::ECRGate |
210+
StandardGate::SwapGate |
211+
StandardGate::RZXGate |
212+
StandardGate::RXXGate |
213+
StandardGate::RYYGate => todo!("Direction fix for {:?}", std_gate),
214+
StandardGate::RZZGate => println!("PARAMs: {:?}", packed_inst.params),
215+
_ => continue,
216+
}
217+
}
136218
}
137219
}
138220

139-
Ok(true)
221+
Ok(dag.clone()) // TODO: avoid cloning
140222
}
141223

142-
// Map a qubit interned in curr_dag to its corresponding qubit entry interned in orig_dag.
143-
// Required for checking control flow instruction which are represented in blocks (circuits)
144-
// and converted to DAGCircuit with possibly different qargs than the original one.
145-
fn map_qubit(py: Python, orig_dag: &DAGCircuit, curr_dag: &DAGCircuit, qubit: Qubit) -> Qubit {
146-
let qubit = curr_dag
147-
.qubits
148-
.get(qubit)
149-
.expect("Qubit in curr_dag")
150-
.bind(py);
151-
orig_dag.qubits.find(qubit).expect("Qubit in orig_dag")
224+
225+
//###################################################
226+
// Utility functions to build the replacement dags
227+
// TODO: replace once we have fully Rust-friendly versions of QuantumRegister, DAGCircuit and ParemeterExpression
228+
229+
fn create_qreg<'py>(py: Python<'py>, size: u32) -> PyResult<Bound<'py, PyAny>> {
230+
imports::QUANTUM_REGISTER.get_bound(py).call1((size,))
152231
}
153232

233+
fn qreg_bit<'py>(py: Python, qreg: &Bound<'py, PyAny>, index: u32) -> PyResult<Bound<'py, PyAny>> {
234+
qreg.call_method1(intern!(py, "__getitem__"), (index,))
235+
}
236+
237+
fn std_gate(py: Python, gate: StandardGate) -> PyResult<Py<PyAny>> {
238+
gate.create_py_op(py, None, None)
239+
}
240+
241+
fn parameterized_std_gate(py: Python, gate: StandardGate, param: Param) -> PyResult<Py<PyAny>> {
242+
gate.create_py_op(py, Some(&[param]), None)
243+
}
244+
245+
fn apply_op_back(py: Python, dag: &mut DAGCircuit, op: &Py<PyAny>, qargs: &Vec<&Bound<PyAny>>) -> PyResult<()> {
246+
dag.py_apply_operation_back(py,
247+
op.bind(py).clone(),
248+
Some( TupleLikeArg::extract_bound( &PyTuple::new_bound(py, qargs))? ),
249+
None,
250+
false)?;
251+
252+
Ok(())
253+
}
254+
255+
// fn build_dag(py: Python) -> PyResult<DAGCircuit> {
256+
// let qreg = create_qreg(py, 2)?;
257+
// let new_dag = &mut DAGCircuit::new(py)?;
258+
// new_dag.add_qreg(py, &qreg)?;
259+
260+
// let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 0)?);
261+
262+
// apply_standard_gate_back(py, new_dag, StandardGate::HGate, &vec![&q0])?;
263+
// apply_standard_gate_back(py, new_dag, StandardGate::CXGate, &vec![&q0, &q1])?;
264+
265+
// Ok( new_dag.clone() ) // TODO: Get rid of the clone
266+
// }
267+
268+
fn cx_replacement_dag(py: Python) -> PyResult<DAGCircuit> {
269+
let qreg = create_qreg(py, 2)?;
270+
let new_dag = &mut DAGCircuit::new(py)?;
271+
new_dag.add_qreg(py, &qreg)?;
272+
273+
let (q0, q1) = (qreg_bit(py, &qreg, 0)?, qreg_bit(py, &qreg, 0)?);
274+
apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?;
275+
apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?;
276+
apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1, &q0])?;
277+
apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q0])?;
278+
apply_op_back(py, new_dag, &std_gate(py, StandardGate::HGate)?, &vec![&q1])?;
279+
280+
Ok( new_dag.clone() ) // TODO: Get rid of the clone
281+
}
282+
283+
154284
#[pymodule]
155285
pub fn gate_direction(m: &Bound<PyModule>) -> PyResult<()> {
156286
m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?;
157287
m.add_wrapped(wrap_pyfunction!(py_check_with_target))?;
288+
m.add_wrapped(wrap_pyfunction!(py_fix_with_coupling_map))?;
158289
Ok(())
159290
}

‎crates/accelerate/src/target_transpiler/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use instruction_properties::InstructionProperties;
4343

4444
use self::exceptions::TranspilerError;
4545

46-
mod exceptions {
46+
pub mod exceptions {
4747
use pyo3::import_exception_bound;
4848
import_exception_bound! {qiskit.exceptions, QiskitError}
4949
import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError}

‎crates/circuit/src/dag_circuit.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ def _format(operand):
985985
}
986986

987987
/// Add all wires in a quantum register.
988-
fn add_qreg(&mut self, py: Python, qreg: &Bound<PyAny>) -> PyResult<()> {
988+
pub fn add_qreg(&mut self, py: Python, qreg: &Bound<PyAny>) -> PyResult<()> {
989989
if !qreg.is_instance(imports::QUANTUM_REGISTER.get_bound(py))? {
990990
return Err(DAGCircuitError::new_err("not a QuantumRegister instance."));
991991
}
@@ -1661,7 +1661,7 @@ def _format(operand):
16611661
/// Raises:
16621662
/// DAGCircuitError: if a leaf node is connected to multiple outputs
16631663
#[pyo3(name = "apply_operation_back", signature = (op, qargs=None, cargs=None, *, check=true))]
1664-
fn py_apply_operation_back(
1664+
pub fn py_apply_operation_back(
16651665
&mut self,
16661666
py: Python,
16671667
op: Bound<PyAny>,

‎qiskit/transpiler/passes/utils/gate_direction.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
SwapGate,
3636
)
3737

38+
from qiskit._accelerate.gate_direction import fix_gate_direction_coupling
39+
3840

3941
def _swap_node_qargs(node):
4042
return DAGOpNode(node.op, node.qargs[::-1], node.cargs)
@@ -345,5 +347,6 @@ def run(self, dag):
345347
"""
346348
layout_map = {bit: i for i, bit in enumerate(dag.qubits)}
347349
if self.target is None:
348-
return self._run_coupling_map(dag, layout_map)
350+
return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges()))
351+
# return self._run_coupling_map(dag, layout_map)
349352
return self._run_target(dag, layout_map)

0 commit comments

Comments
 (0)
Please sign in to comment.