1313use pyo3:: prelude:: * ;
1414use pyo3:: types:: { PyList , PyString , PyTuple } ;
1515use qiskit_circuit:: circuit_data:: CircuitData ;
16- use qiskit_circuit:: operations:: { multiply_param, radd_param, Param , StandardGate } ;
16+ use qiskit_circuit:: operations:: { multiply_param, radd_param, Param , PyInstruction , StandardGate } ;
1717use qiskit_circuit:: packed_instruction:: PackedOperation ;
18- use qiskit_circuit:: { Clbit , Qubit } ;
18+ use qiskit_circuit:: { imports , Clbit , Qubit } ;
1919use smallvec:: { smallvec, SmallVec } ;
2020
21- use crate :: circuit_library:: utils;
22-
2321// custom types for a more readable code
2422type StandardInstruction = ( StandardGate , SmallVec < [ Param ; 3 ] > , SmallVec < [ Qubit ; 2 ] > ) ;
2523type Instruction = (
@@ -115,7 +113,7 @@ fn two_qubit_evolution<'a>(
115113 "zx" => Box :: new ( std:: iter:: once ( ( StandardGate :: RZXGate , param, qubits) ) ) ,
116114 "yy" => Box :: new ( std:: iter:: once ( ( StandardGate :: RYYGate , param, qubits) ) ) ,
117115 "zz" => Box :: new ( std:: iter:: once ( ( StandardGate :: RZZGate , param, qubits) ) ) ,
118- // note that the CX modes (do_fountain=true/false) give the same circuit for a 2-qubit
116+ // Note: the CX modes (do_fountain=true/false) give the same circuit for a 2-qubit
119117 // Pauli, so we just set it to false here
120118 _ => Box :: new ( multi_qubit_evolution ( pauli, indices, time, false , false ) ) ,
121119 }
@@ -135,22 +133,25 @@ fn multi_qubit_evolution(
135133 . collect ( ) ;
136134
137135 // get the basis change: x -> HGate, y -> SXdgGate, z -> nothing
138- let basis_change = active_paulis
139- . clone ( )
140- . into_iter ( )
136+ let basis_change: Vec < StandardInstruction > = active_paulis
137+ . iter ( )
141138 . filter ( |( p, _) | * p != 'z' )
142139 . map ( |( p, q) | match p {
143- 'x' => ( StandardGate :: HGate , smallvec ! [ ] , smallvec ! [ q] ) ,
144- 'y' => ( StandardGate :: SXdgGate , smallvec ! [ ] , smallvec ! [ q] ) ,
140+ 'x' => ( StandardGate :: HGate , smallvec ! [ ] , smallvec ! [ q. clone ( ) ] ) ,
141+ 'y' => ( StandardGate :: SXdgGate , smallvec ! [ ] , smallvec ! [ q. clone ( ) ] ) ,
145142 _ => unreachable ! ( "Invalid Pauli string." ) , // "z" and "i" have been filtered out
146- } ) ;
143+ } )
144+ . collect ( ) ;
147145
148146 // get the inverse basis change
149- let inverse_basis_change = basis_change. clone ( ) . map ( |( gate, _, qubit) | match gate {
150- StandardGate :: HGate => ( gate, smallvec ! [ ] , qubit) ,
151- StandardGate :: SXdgGate => ( StandardGate :: SXGate , smallvec ! [ ] , qubit) ,
152- _ => unreachable ! ( "Invalid basis-changing Clifford." ) ,
153- } ) ;
147+ let inverse_basis_change: Vec < StandardInstruction > = basis_change
148+ . iter ( )
149+ . map ( |( gate, _, qubit) | match gate {
150+ StandardGate :: HGate => ( StandardGate :: HGate , smallvec ! [ ] , qubit. clone ( ) ) ,
151+ StandardGate :: SXdgGate => ( StandardGate :: SXGate , smallvec ! [ ] , qubit. clone ( ) ) ,
152+ _ => unreachable ! ( "Invalid basis-changing Clifford." ) ,
153+ } )
154+ . collect ( ) ;
154155
155156 // get the CX propagation up to the first qubit, and down
156157 let ( chain_up, chain_down) = match do_fountain {
@@ -178,6 +179,7 @@ fn multi_qubit_evolution(
178179
179180 // and finally chain everything together
180181 basis_change
182+ . into_iter ( )
181183 . chain ( chain_down)
182184 . chain ( z_rotation)
183185 . chain ( chain_up)
@@ -215,42 +217,39 @@ fn multi_qubit_evolution(
215217/// Returns:
216218/// Circuit data for to implement the evolution.
217219#[ pyfunction]
218- #[ pyo3( signature = ( num_qubits, sparse_paulis, insert_barriers=false , do_fountain=false ) ) ]
220+ #[ pyo3( name = "pauli_evolution" , signature = ( num_qubits, sparse_paulis, insert_barriers=false , do_fountain=false ) ) ]
219221pub fn py_pauli_evolution (
220- py : Python ,
221222 num_qubits : i64 ,
222223 sparse_paulis : & Bound < PyList > ,
223224 insert_barriers : bool ,
224225 do_fountain : bool ,
225226) -> PyResult < CircuitData > {
227+ let py = sparse_paulis. py ( ) ;
226228 let num_paulis = sparse_paulis. len ( ) ;
227229 let mut paulis: Vec < String > = Vec :: with_capacity ( num_paulis) ;
228230 let mut indices: Vec < Vec < u32 > > = Vec :: with_capacity ( num_paulis) ;
229231 let mut times: Vec < Param > = Vec :: with_capacity ( num_paulis) ;
230232 let mut global_phase = Param :: Float ( 0.0 ) ;
233+ let mut modified_phase = false ; // keep track of whether we modified the phase
231234
232235 for el in sparse_paulis. iter ( ) {
233236 let tuple = el. downcast :: < PyTuple > ( ) ?;
234237 let pauli = tuple. get_item ( 0 ) ?. downcast :: < PyString > ( ) ?. to_string ( ) ;
235238 let time = Param :: extract_no_coerce ( & tuple. get_item ( 2 ) ?) ?;
236239
237240 if pauli. as_str ( ) . chars ( ) . all ( |p| p == 'i' ) {
238- global_phase = radd_param ( global_phase, time, py) ; // apply factor -1 at the end
241+ global_phase = radd_param ( global_phase, time, py) ;
242+ modified_phase = true ;
239243 continue ;
240244 }
241245
242246 paulis. push ( pauli) ;
243247 times. push ( time) ; // note we do not multiply by 2 here, this is done Python side!
244- indices. push (
245- tuple
246- . get_item ( 1 ) ?
247- . downcast :: < PyList > ( ) ?
248- . iter ( )
249- . map ( |index| index. extract :: < u32 > ( ) )
250- . collect :: < PyResult < _ > > ( ) ?,
251- ) ;
248+ indices. push ( tuple. get_item ( 1 ) ?. extract :: < Vec < u32 > > ( ) ?)
252249 }
253250
251+ let barrier = get_barrier ( py, num_qubits as u32 ) ;
252+
254253 let evos = paulis. iter ( ) . enumerate ( ) . zip ( indices) . zip ( times) . flat_map (
255254 |( ( ( i, pauli) , qubits) , time) | {
256255 let as_packed = pauli_evolution ( pauli, qubits, time, false , do_fountain) . map (
@@ -263,16 +262,23 @@ pub fn py_pauli_evolution(
263262 ) )
264263 } ,
265264 ) ;
266- as_packed. chain ( utils:: maybe_barrier (
267- py,
268- num_qubits as u32 ,
269- insert_barriers && i < ( num_paulis - 1 ) , // do not add barrier on final block
270- ) )
265+
266+ // this creates an iterator containing a barrier only if required, otherwise it is empty
267+ let maybe_barrier = ( insert_barriers && i < ( num_paulis - 1 ) )
268+ . then_some ( Ok ( barrier. clone ( ) ) )
269+ . into_iter ( ) ;
270+ as_packed. chain ( maybe_barrier)
271271 } ,
272272 ) ;
273273
274- // apply factor -1 for global phase
275- global_phase = multiply_param ( & global_phase, -1.0 , py) ;
274+ // When handling all-identity Paulis above, we added the time as global phase.
275+ // However, the all-identity Paulis should add a negative phase, as they implement
276+ // exp(-i t I). We apply the negative sign here, to only do a single (-1) multiplication,
277+ // instead of doing it every time we find an all-identity Pauli.
278+ if modified_phase {
279+ global_phase = multiply_param ( & global_phase, -1.0 , py) ;
280+ }
281+
276282 CircuitData :: from_packed_operations ( py, num_qubits as u32 , 0 , evos, global_phase)
277283}
278284
@@ -301,3 +307,24 @@ fn cx_fountain(
301307 )
302308 } ) )
303309}
310+
311+ fn get_barrier ( py : Python , num_qubits : u32 ) -> Instruction {
312+ let barrier_cls = imports:: BARRIER . get_bound ( py) ;
313+ let barrier = barrier_cls
314+ . call1 ( ( num_qubits, ) )
315+ . expect ( "Could not create Barrier Python-side" ) ;
316+ let barrier_inst = PyInstruction {
317+ qubits : num_qubits,
318+ clbits : 0 ,
319+ params : 0 ,
320+ op_name : "barrier" . to_string ( ) ,
321+ control_flow : false ,
322+ instruction : barrier. into ( ) ,
323+ } ;
324+ (
325+ barrier_inst. into ( ) ,
326+ smallvec ! [ ] ,
327+ ( 0 ..num_qubits) . map ( Qubit ) . collect ( ) ,
328+ vec ! [ ] ,
329+ )
330+ }
0 commit comments