1010// copyright notice, and modified files need to carry a notice indicating
1111// that they have been altered from the originals.
1212
13+ use itertools:: Itertools ;
14+
1315use std:: f64:: consts:: PI ;
1416
1517use num_complex:: Complex64 ;
@@ -23,7 +25,8 @@ use crate::passes::remove_identity_equiv::{average_gate_fidelity_below_tol, is_i
2325use qiskit_circuit:: circuit_instruction:: OperationFromPython ;
2426use qiskit_circuit:: dag_circuit:: DAGCircuit ;
2527use qiskit_circuit:: operations:: {
26- Operation , OperationRef , Param , StandardGate , multiply_param, radd_param,
28+ Operation , OperationRef , Param , PauliBased , PauliProductRotation , StandardGate , multiply_param,
29+ radd_param,
2730} ;
2831use qiskit_circuit:: { BlocksMode , Clbit , NoBlocks , Qubit , imports} ;
2932
@@ -192,6 +195,40 @@ fn canonicalize(
192195 }
193196 }
194197 }
198+
199+ if let OperationRef :: PauliProductRotation ( ppr) = inst. op . view ( ) {
200+ let qargs = dag. get_qargs ( inst. qubits ) ;
201+ let mut paired = qargs
202+ . iter ( )
203+ . zip ( ppr. z . iter ( ) )
204+ . zip ( ppr. x . iter ( ) )
205+ . map ( |( ( q, z) , x) | ( q, z, x) )
206+ . collect :: < Vec < _ > > ( ) ;
207+ paired. sort_by_key ( |( q, _, _) | * * q) ;
208+ let ( sorted_qargs, sorted_z, sorted_x) =
209+ paired
210+ . into_iter ( )
211+ . multiunzip :: < ( Vec < Qubit > , Vec < bool > , Vec < bool > ) > ( ) ;
212+ let sorted_ppr = PauliProductRotation {
213+ z : sorted_z,
214+ x : sorted_x,
215+ angle : ppr. angle . clone ( ) ,
216+ } ;
217+
218+ let sorted_qubits = dag. add_qargs ( & sorted_qargs) ;
219+
220+ let canonical_instruction = PackedInstruction {
221+ op : PauliBased :: PauliProductRotation ( sorted_ppr) . into ( ) ,
222+ qubits : sorted_qubits,
223+ clbits : Default :: default ( ) ,
224+ params : inst. params . clone ( ) ,
225+ label : None ,
226+ #[ cfg( feature = "cache_pygates" ) ]
227+ py_op : std:: sync:: OnceLock :: new ( ) ,
228+ } ;
229+ return Some ( ( canonical_instruction, Param :: Float ( 0. ) ) ) ;
230+ }
231+
195232 None
196233}
197234
@@ -309,6 +346,34 @@ fn try_merge(
309346 }
310347 }
311348
349+ // Special handling for PauliProductRotations.
350+ if let ( OperationRef :: PauliProductRotation ( ppr1) , OperationRef :: PauliProductRotation ( ppr2) ) =
351+ ( inst1. op . view ( ) , inst2. op . view ( ) )
352+ {
353+ let merge_result = ppr1. merge_with ( ppr2) ;
354+
355+ if let Some ( merged_ppr) = merge_result {
356+ let angle = merged_ppr. angle . clone ( ) ;
357+ let merged_params = Some ( Box :: new ( Parameters :: Params ( smallvec ! [ angle] ) ) ) ;
358+
359+ let packed = PackedInstruction {
360+ op : PauliBased :: PauliProductRotation ( merged_ppr) . into ( ) ,
361+ qubits : inst1. qubits ,
362+ clbits : inst1. clbits ,
363+ params : merged_params,
364+ label : None ,
365+ #[ cfg( feature = "cache_pygates" ) ]
366+ py_op : std:: sync:: OnceLock :: new ( ) ,
367+ } ;
368+
369+ if let Some ( phase_update) = is_identity_equiv ( & packed, false , None , error_cutoff_fn) ? {
370+ return Ok ( ( true , None , phase_update) ) ;
371+ } else {
372+ return Ok ( ( true , Some ( packed) , 0. ) ) ;
373+ }
374+ }
375+ }
376+
312377 // Special handling for PauliEvolutionGates.
313378 if inst1. op . name ( ) == "PauliEvolution" && inst2. op . name ( ) == "PauliEvolution" {
314379 if let ( OperationRef :: Gate ( py_gate1) , OperationRef :: Gate ( py_gate2) ) =
0 commit comments