@@ -15,8 +15,12 @@ use ndarray::linalg::kron;
15
15
use ndarray:: Array2 ;
16
16
use num_complex:: Complex64 ;
17
17
use num_complex:: ComplexFloat ;
18
+ <<<<<<< HEAD
18
19
use once_cell:: sync:: Lazy ;
19
20
use qiskit_circuit:: bit_data:: VarAsKey ;
21
+ =======
22
+ use qiskit_circuit:: object_registry:: PyObjectAsKey ;
23
+ >>>>>>> abb0cf9db ( Fix string and standard gate mismatch in commutation checker ( #13991 ) )
20
24
use smallvec:: SmallVec ;
21
25
use std:: fmt:: Debug ;
22
26
@@ -33,57 +37,69 @@ use qiskit_circuit::dag_node::DAGOpNode;
33
37
use qiskit_circuit:: imports:: QI_OPERATOR ;
34
38
use qiskit_circuit:: operations:: OperationRef :: { Gate as PyGateType , Operation as PyOperationType } ;
35
39
use qiskit_circuit:: operations:: {
36
- get_standard_gate_names , Operation , OperationRef , Param , StandardGate ,
40
+ Operation , OperationRef , Param , StandardGate , STANDARD_GATE_SIZE ,
37
41
} ;
38
42
use qiskit_circuit:: { BitType , Clbit , Qubit } ;
39
43
40
44
use crate :: gate_metrics ;
41
45
use crate :: unitary_compose ;
42
46
use crate :: QiskitError ;
43
47
44
- // These gates do not commute with other gates, we do not check them.
45
- static SKIPPED_NAMES : [ & str ; 4 ] = [ "measure" , "reset" , "delay" , "initialize" ] ;
48
+ const fn build_supported_ops ( ) -> [ bool ; STANDARD_GATE_SIZE ] {
49
+ let mut lut = [ false ; STANDARD_GATE_SIZE ] ;
50
+ lut[ StandardGate :: RXXGate as usize ] = true;
51
+ lut[ StandardGate :: RYYGate as usize ] = true;
52
+ lut[ StandardGate :: RZZGate as usize ] = true;
53
+ lut[ StandardGate :: RZXGate as usize ] = true;
54
+ lut[ StandardGate :: HGate as usize ] = true;
55
+ lut[ StandardGate :: XGate as usize ] = true;
56
+ lut[ StandardGate :: YGate as usize ] = true;
57
+ lut[ StandardGate :: ZGate as usize ] = true;
58
+ lut[ StandardGate :: SXGate as usize ] = true;
59
+ lut[ StandardGate :: SXdgGate as usize ] = true;
60
+ lut[ StandardGate :: TGate as usize ] = true;
61
+ lut[ StandardGate :: TdgGate as usize ] = true;
62
+ lut[ StandardGate :: SGate as usize ] = true;
63
+ lut[ StandardGate :: SdgGate as usize ] = true;
64
+ lut[ StandardGate :: CXGate as usize ] = true;
65
+ lut[ StandardGate :: CYGate as usize ] = true;
66
+ lut[ StandardGate :: CZGate as usize ] = true;
67
+ lut[ StandardGate :: SwapGate as usize ] = true;
68
+ lut[ StandardGate :: ISwapGate as usize ] = true;
69
+ lut[ StandardGate :: ECRGate as usize ] = true;
70
+ lut[ StandardGate :: CCXGate as usize ] = true;
71
+ lut[ StandardGate :: CSwapGate as usize ] = true;
72
+ lut
73
+ }
46
74
47
- // We keep a hash-set of operations eligible for commutation checking. This is because checking
48
- // eligibility is not for free.
49
- static SUPPORTED_OP : Lazy < HashSet < & str > > = Lazy :: new ( || {
50
- HashSet :: from ( [
51
- "rxx" , "ryy" , "rzz" , "rzx" , "h" , "x" , "y" , "z" , "sx" , "sxdg" , "t" , "tdg" , "s" , "sdg" , "cx" ,
52
- "cy" , "cz" , "swap" , "iswap" , "ecr" , "ccx" , "cswap" ,
53
- ] )
54
- } ) ;
75
+ static SUPPORTED_OP : [ bool ; STANDARD_GATE_SIZE ] = build_supported_ops ( ) ;
55
76
56
77
// Map rotation gates to their generators (or to ``None`` if we cannot currently efficiently
57
78
// represent the generator in Rust and store the commutation relation in the commutation dictionary)
58
79
// and their pi-periodicity. Here we mean a gate is n-pi periodic, if for angles that are
59
80
// multiples of n*pi, the gate is equal to the identity up to a global phase.
60
81
// E.g. RX is generated by X and 2-pi periodic, while CRX is generated by CX and 4-pi periodic.
61
- static SUPPORTED_ROTATIONS : Lazy < HashMap < & str , Option < OperationRef > > > = Lazy :: new ( || {
62
- HashMap :: from ( [
63
- ( "rx" , Some ( OperationRef :: StandardGate ( StandardGate :: XGate ) ) ) ,
64
- ( "ry" , Some ( OperationRef :: StandardGate ( StandardGate :: YGate ) ) ) ,
65
- ( "rz" , Some ( OperationRef :: StandardGate ( StandardGate :: ZGate ) ) ) ,
66
- ( "p" , Some ( OperationRef :: StandardGate ( StandardGate :: ZGate ) ) ) ,
67
- ( "u1" , Some ( OperationRef :: StandardGate ( StandardGate :: ZGate ) ) ) ,
68
- ( "rxx" , None ) , // None means the gate is in the commutation dictionary
69
- ( "ryy" , None ) ,
70
- ( "rzx" , None ) ,
71
- ( "rzz" , None ) ,
72
- (
73
- "crx" ,
74
- Some ( OperationRef :: StandardGate ( StandardGate :: CXGate ) ) ,
75
- ) ,
76
- (
77
- "cry" ,
78
- Some ( OperationRef :: StandardGate ( StandardGate :: CYGate ) ) ,
79
- ) ,
80
- (
81
- "crz" ,
82
- Some ( OperationRef :: StandardGate ( StandardGate :: CZGate ) ) ,
83
- ) ,
84
- ( "cp" , Some ( OperationRef :: StandardGate ( StandardGate :: CZGate ) ) ) ,
85
- ] )
86
- } ) ;
82
+ const fn build_supported_rotations( ) -> [ Option < Option < StandardGate > > ; STANDARD_GATE_SIZE ] {
83
+ let mut lut = [ None ; STANDARD_GATE_SIZE ] ;
84
+ lut[ StandardGate :: RXGate as usize ] = Some ( Some ( StandardGate :: XGate ) ) ;
85
+ lut[ StandardGate :: RYGate as usize ] = Some ( Some ( StandardGate :: YGate ) ) ;
86
+ lut[ StandardGate :: RZGate as usize ] = Some ( Some ( StandardGate :: ZGate ) ) ;
87
+ lut[ StandardGate :: PhaseGate as usize ] = Some ( Some ( StandardGate :: ZGate ) ) ;
88
+ lut[ StandardGate :: U1Gate as usize ] = Some ( Some ( StandardGate :: ZGate ) ) ;
89
+ lut[ StandardGate :: CRXGate as usize ] = Some ( Some ( StandardGate :: CXGate ) ) ;
90
+ lut[ StandardGate :: CRYGate as usize ] = Some ( Some ( StandardGate :: CYGate ) ) ;
91
+ lut[ StandardGate :: CRZGate as usize ] = Some ( Some ( StandardGate :: CZGate ) ) ;
92
+ lut[ StandardGate :: CPhaseGate as usize ] = Some ( Some ( StandardGate :: CZGate ) ) ;
93
+ // RXXGate, RYYGate, RZXGate, and RZZGate are supported by the commutation dictionary
94
+ lut[ StandardGate :: RXXGate as usize ] = Some ( None ) ;
95
+ lut[ StandardGate :: RYYGate as usize ] = Some ( None ) ;
96
+ lut[ StandardGate :: RZXGate as usize ] = Some ( None ) ;
97
+ lut[ StandardGate :: RZZGate as usize ] = Some ( None ) ;
98
+ lut
99
+ }
100
+
101
+ static SUPPORTED_ROTATIONS : [ Option < Option < StandardGate > > ; STANDARD_GATE_SIZE ] =
102
+ build_supported_rotations ( ) ;
87
103
88
104
fn get_bits < T > ( bits1 : & Bound < PyTuple > , bits2 : & Bound < PyTuple > ) -> PyResult < ( Vec < T > , Vec < T > ) >
89
105
where
@@ -132,8 +148,6 @@ impl CommutationChecker {
132
148
gates: Option < HashSet < String > > ,
133
149
) -> Self {
134
150
// Initialize sets before they are used in the commutation checker
135
- Lazy :: force ( & SUPPORTED_OP ) ;
136
- Lazy :: force ( & SUPPORTED_ROTATIONS ) ;
137
151
CommutationChecker {
138
152
library : CommutationLibrary :: new( standard_gate_commutations) ,
139
153
cache : HashMap :: new( ) ,
@@ -287,14 +301,24 @@ impl CommutationChecker {
287
301
288
302
// if we have rotation gates, we attempt to map them to their generators, for example
289
303
// RX -> X or CPhase -> CZ
290
- let ( op1 , params1, trivial1) = map_rotation ( op1, params1, tol) ;
304
+ let ( op1_gate , params1, trivial1) = map_rotation( op1, params1, tol) ;
291
305
if trivial1 {
292
306
return Ok ( true ) ;
293
307
}
294
- let ( op2, params2, trivial2) = map_rotation ( op2, params2, tol) ;
308
+ let op1 = if let Some ( gate) = op1_gate {
309
+ & OperationRef :: StandardGate ( gate)
310
+ } else {
311
+ op1
312
+ } ;
313
+ let ( op2_gate, params2, trivial2) = map_rotation( op2, params2, tol) ;
295
314
if trivial2 {
296
315
return Ok ( true) ;
297
316
}
317
+ let op2 = if let Some ( gate) = op2_gate {
318
+ & OperationRef :: StandardGate ( gate)
319
+ } else {
320
+ op2
321
+ } ;
298
322
299
323
if let Some ( gates) = & self . gates {
300
324
if !gates. is_empty( ) && ( !gates. contains( op1. name( ) ) || !gates. contains( op2. name( ) ) ) {
@@ -339,14 +363,15 @@ impl CommutationChecker {
339
363
// the cache for
340
364
// * gates we know are in the cache (SUPPORTED_OPS), or
341
365
// * standard gates with float params (otherwise we cannot cache them)
342
- let standard_gates = get_standard_gate_names ( ) ;
343
- let is_cachable = |name : & str , params : & [ Param ] | {
344
- SUPPORTED_OP . contains ( name)
345
- || ( standard_gates. contains ( & name)
346
- && params. iter ( ) . all ( |p| matches ! ( p, Param :: Float ( _) ) ) )
366
+ let is_cachable = |op: & OperationRef , params : & [ Param ] | {
367
+ if let Some ( gate) = op. standard_gate( ) {
368
+ SUPPORTED_OP [ gate as usize] || params . iter( ) . all( |p| matches ! ( p, Param :: Float ( _) ) )
369
+ } else {
370
+ false
371
+ }
347
372
} ;
348
- let check_cache = is_cachable ( first_op . name ( ) , first_params )
349
- && is_cachable ( second_op. name ( ) , second_params) ;
373
+ let check_cache =
374
+ is_cachable ( first_op , first_params ) && is_cachable( second_op, second_params) ;
350
375
351
376
if !check_cache {
352
377
return self. commute_matmul(
@@ -544,11 +569,25 @@ fn commutation_precheck(
544
569
return Some ( false ) ;
545
570
}
546
571
547
- if SUPPORTED_OP . contains ( op1. name ( ) ) && SUPPORTED_OP . contains ( op2. name ( ) ) {
548
- return None ;
572
+ if let Some ( gate_1) = op1. standard_gate( ) {
573
+ if let Some ( gate_2) = op2. standard_gate( ) {
574
+ if SUPPORTED_OP [ gate_1 as usize ] && SUPPORTED_OP [ gate_2 as usize ] {
575
+ return None ;
576
+ }
577
+ }
578
+ }
579
+
580
+ if matches ! (
581
+ op1,
582
+ OperationRef :: StandardInstruction ( _) | OperationRef :: Instruction ( _)
583
+ ) || matches ! (
584
+ op2,
585
+ OperationRef :: StandardInstruction ( _) | OperationRef :: Instruction ( _)
586
+ ) {
587
+ return Some ( false ) ;
549
588
}
550
589
551
- if is_commutation_skipped ( op1 , params1) || is_commutation_skipped ( op2 , params2) {
590
+ if is_parameterized ( params1) || is_parameterized ( params2) {
552
591
return Some ( false ) ;
553
592
}
554
593
@@ -580,15 +619,10 @@ fn matrix_via_operator(py: Python, py_obj: &PyObject) -> PyResult<Array2<Complex
580
619
. to_owned( ) )
581
620
}
582
621
583
- fn is_commutation_skipped < T > ( op : & T , params : & [ Param ] ) -> bool
584
- where
585
- T : Operation ,
586
- {
587
- op. directive ( )
588
- || SKIPPED_NAMES . contains ( & op. name ( ) )
589
- || params
590
- . iter ( )
591
- . any ( |x| matches ! ( x, Param :: ParameterExpression ( _) ) )
622
+ fn is_parameterized( params: & [ Param ] ) -> bool {
623
+ params
624
+ . iter( )
625
+ . any( |x| matches ! ( x, Param :: ParameterExpression ( _) ) )
592
626
}
593
627
594
628
/// Check if a given operation can be mapped onto a generator.
@@ -604,36 +638,33 @@ fn map_rotation<'a>(
604
638
op: & ' a OperationRef < ' a > ,
605
639
params: & ' a [ Param ] ,
606
640
tol: f64,
607
- ) -> ( & ' a OperationRef < ' a > , & ' a [ Param ] , bool ) {
608
- let name = op. name ( ) ;
609
-
610
- if let Some ( generator) = SUPPORTED_ROTATIONS . get ( name) {
611
- // If the rotation angle is below the tolerance, the gate is assumed to
612
- // commute with everything, and we simply return the operation with the flag that
613
- // it commutes trivially.
614
- if let Param :: Float ( angle) = params[ 0 ] {
615
- let gate = op
616
- . standard_gate ( )
617
- . expect ( "Supported gates are standard gates" ) ;
618
- let ( tr_over_dim, dim) = gate_metrics:: rotation_trace_and_dim ( gate, angle)
619
- . expect ( "All rotation should be covered at this point" ) ;
620
- let gate_fidelity = tr_over_dim. abs ( ) . powi ( 2 ) ;
621
- let process_fidelity = ( dim * gate_fidelity + 1. ) / ( dim + 1. ) ;
622
- if ( 1. - process_fidelity) . abs ( ) <= tol {
623
- return ( op, params, true ) ;
641
+ ) -> ( Option < StandardGate > , & ' a [ Param ] , bool) {
642
+ if let Some ( gate) = op. standard_gate( ) {
643
+ if let Some ( generator) = SUPPORTED_ROTATIONS [ gate as usize] {
644
+ // If the rotation angle is below the tolerance, the gate is assumed to
645
+ // commute with everything, and we simply return the operation with the flag that
646
+ // it commutes trivially.
647
+ if let Param :: Float ( angle) = params[ 0 ] {
648
+ let ( tr_over_dim, dim) = gate_metrics:: rotation_trace_and_dim( gate, angle)
649
+ . expect( "All rotation should be covered at this point" ) ;
650
+ let gate_fidelity = tr_over_dim. abs( ) . powi( 2 ) ;
651
+ let process_fidelity = ( dim * gate_fidelity + 1. ) / ( dim + 1. ) ;
652
+ if ( 1. - process_fidelity) . abs( ) <= tol {
653
+ return ( Some ( gate) , params, true ) ;
654
+ } ;
624
655
} ;
625
- } ;
626
656
627
- // Otherwise we need to cover two cases -- either a generator is given, in which case
628
- // we return it, or we don't have a generator yet, but we know we have the operation
629
- // stored in the commutation library. For example, RXX does not have a generator in Rust
630
- // yet (PauliGate is not in Rust currently), but it is stored in the library, so we
631
- // can strip the parameters and just return the gate.
632
- if let Some ( gate) = generator {
633
- return ( gate, & [ ] , false ) ;
634
- } ;
657
+ // Otherwise we need to cover two cases -- either a generator is given, in which case
658
+ // we return it, or we don't have a generator yet, but we know we have the operation
659
+ // stored in the commutation library. For example, RXX does not have a generator in Rust
660
+ // yet (PauliGate is not in Rust currently), but it is stored in the library, so we
661
+ // can strip the parameters and just return the gate.
662
+ if let Some ( gate) = generator {
663
+ return ( Some ( gate) , & [ ] , false ) ;
664
+ } ;
665
+ }
635
666
}
636
- ( op , params, false )
667
+ ( None , params, false )
637
668
}
638
669
639
670
fn get_relative_placement(
0 commit comments