@@ -14,15 +14,69 @@ use crate::nlayout::PhysicalQubit;
14
14
use crate :: target_transpiler:: { Qargs , Target } ;
15
15
use pyo3:: prelude:: * ;
16
16
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 } ;
19
20
use qiskit_circuit:: {
20
21
dag_circuit:: { DAGCircuit , NodeType } ,
21
22
error:: DAGCircuitError ,
22
23
operations:: Operation ,
23
24
packed_instruction:: PackedInstruction ,
24
- Qubit ,
25
+ Qubit , operations :: Param
25
26
} ;
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
+
26
80
27
81
/// Check if the two-qubit gates follow the right direction with respect to the coupling map.
28
82
///
@@ -40,19 +94,10 @@ fn py_check_with_coupling_map(
40
94
dag : & DAGCircuit ,
41
95
coupling_edges : & Bound < PySet > ,
42
96
) -> 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) )
54
98
}
55
99
100
+
56
101
/// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target.
57
102
///
58
103
/// Args:
@@ -67,31 +112,42 @@ fn py_check_with_coupling_map(
67
112
fn py_check_with_target ( py : Python , dag : & DAGCircuit , target : & Bound < Target > ) -> PyResult < bool > {
68
113
let target = target. borrow ( ) ;
69
114
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
+ }
80
117
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" ) ) ;
82
124
} ;
83
125
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 )
85
143
}
86
144
87
145
// 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 (
89
147
py : Python ,
90
148
py_inst : & PyInstruction ,
91
- gate_complies : & T ,
149
+ gate_complies : & GateDirectionCheckFn ,
92
150
) -> PyResult < bool >
93
- where
94
- T : Fn ( & DAGCircuit , & PackedInstruction , & [ Qubit ] ) -> bool ,
95
151
{
96
152
let circuit_to_dag = imports:: CIRCUIT_TO_DAG . get_bound ( py) ; // TODO: Take out of the recursion
97
153
let py_inst = py_inst. instruction . bind ( py) ;
@@ -110,50 +166,125 @@ where
110
166
Ok ( true )
111
167
}
112
168
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 > {
118
186
for node in dag. op_nodes ( false ) {
119
187
let NodeType :: Operation ( packed_inst) = & dag. dag [ node] else {
120
188
return Err ( DAGCircuitError :: new_err ( "PackedInstruction is expected" ) ) ;
121
189
} ;
122
190
123
191
if let OperationRef :: Instruction ( py_inst) = packed_inst. op . view ( ) {
124
192
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" ) ;
130
194
}
131
195
}
132
196
133
197
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
+ }
136
218
}
137
219
}
138
220
139
- Ok ( true )
221
+ Ok ( dag . clone ( ) ) // TODO: avoid cloning
140
222
}
141
223
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, ) )
152
231
}
153
232
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
+
154
284
#[ pymodule]
155
285
pub fn gate_direction ( m : & Bound < PyModule > ) -> PyResult < ( ) > {
156
286
m. add_wrapped ( wrap_pyfunction ! ( py_check_with_coupling_map) ) ?;
157
287
m. add_wrapped ( wrap_pyfunction ! ( py_check_with_target) ) ?;
288
+ m. add_wrapped ( wrap_pyfunction ! ( py_fix_with_coupling_map) ) ?;
158
289
Ok ( ( ) )
159
290
}
0 commit comments