@@ -28,24 +28,29 @@ use qiskit_circuit::circuit_instruction::{ExtraInstructionAttributes, OperationF
2828use qiskit_circuit:: dag_node:: DAGOpNode ;
2929use qiskit_circuit:: imports:: QI_OPERATOR ;
3030use qiskit_circuit:: operations:: OperationRef :: { Gate as PyGateType , Operation as PyOperationType } ;
31- use qiskit_circuit:: operations:: { Operation , OperationRef , Param , StandardGate } ;
31+ use qiskit_circuit:: operations:: {
32+ get_standard_gate_names, Operation , OperationRef , Param , StandardGate ,
33+ } ;
3234use qiskit_circuit:: { BitType , Clbit , Qubit } ;
3335
3436use crate :: unitary_compose;
3537use crate :: QiskitError ;
3638
39+ const TWOPI : f64 = 2.0 * std:: f64:: consts:: PI ;
40+
41+ // These gates do not commute with other gates, we do not check them.
3742static SKIPPED_NAMES : [ & str ; 4 ] = [ "measure" , "reset" , "delay" , "initialize" ] ;
38- static NO_CACHE_NAMES : [ & str ; 2 ] = [ "annotated" , "linear_function" ] ;
43+
44+ // We keep a hash-set of operations eligible for commutation checking. This is because checking
45+ // eligibility is not for free.
3946static SUPPORTED_OP : Lazy < HashSet < & str > > = Lazy :: new ( || {
4047 HashSet :: from ( [
4148 "rxx" , "ryy" , "rzz" , "rzx" , "h" , "x" , "y" , "z" , "sx" , "sxdg" , "t" , "tdg" , "s" , "sdg" , "cx" ,
4249 "cy" , "cz" , "swap" , "iswap" , "ecr" , "ccx" , "cswap" ,
4350 ] )
4451} ) ;
4552
46- const TWOPI : f64 = 2.0 * std:: f64:: consts:: PI ;
47-
48- // map rotation gates to their generators, or to ``None`` if we cannot currently efficiently
53+ // Map rotation gates to their generators, or to ``None`` if we cannot currently efficiently
4954// represent the generator in Rust and store the commutation relation in the commutation dictionary
5055static SUPPORTED_ROTATIONS : Lazy < HashMap < & str , Option < OperationRef > > > = Lazy :: new ( || {
5156 HashMap :: from ( [
@@ -322,15 +327,17 @@ impl CommutationChecker {
322327 ( qargs1, qargs2)
323328 } ;
324329
325- let skip_cache: bool = NO_CACHE_NAMES . contains ( & first_op. name ( ) ) ||
326- NO_CACHE_NAMES . contains ( & second_op. name ( ) ) ||
327- // Skip params that do not evaluate to floats for caching and commutation library
328- first_params. iter ( ) . any ( |p| !matches ! ( p, Param :: Float ( _) ) ) ||
329- second_params. iter ( ) . any ( |p| !matches ! ( p, Param :: Float ( _) ) )
330- && !SUPPORTED_OP . contains ( op1. name ( ) )
331- && !SUPPORTED_OP . contains ( op2. name ( ) ) ;
332-
333- if skip_cache {
330+ // For our cache to work correctly, we require the gate's definition to only depend on the
331+ // ``params`` attribute. This cannot be guaranteed for custom gates, so we only check
332+ // the cache for our standard gates, which we know are defined by the ``params`` AND
333+ // that the ``params`` are float-only at this point.
334+ let whitelist = get_standard_gate_names ( ) ;
335+ let check_cache = whitelist. contains ( & first_op. name ( ) )
336+ && whitelist. contains ( & second_op. name ( ) )
337+ && first_params. iter ( ) . all ( |p| matches ! ( p, Param :: Float ( _) ) )
338+ && second_params. iter ( ) . all ( |p| matches ! ( p, Param :: Float ( _) ) ) ;
339+
340+ if !check_cache {
334341 return self . commute_matmul (
335342 py,
336343 first_op,
@@ -630,21 +637,24 @@ fn map_rotation<'a>(
630637) -> ( & ' a OperationRef < ' a > , & ' a [ Param ] , bool ) {
631638 let name = op. name ( ) ;
632639 if let Some ( generator) = SUPPORTED_ROTATIONS . get ( name) {
633- // if the rotation angle is below the tolerance, the gate is assumed to
640+ // If the rotation angle is below the tolerance, the gate is assumed to
634641 // commute with everything, and we simply return the operation with the flag that
635- // it commutes trivially
642+ // it commutes trivially.
636643 if let Param :: Float ( angle) = params[ 0 ] {
637644 if ( angle % TWOPI ) . abs ( ) < tol {
638645 return ( op, params, true ) ;
639646 } ;
640647 } ;
641648
642- // otherwise, we check if a generator is given -- if not, we'll just return the operation
643- // itself (e.g. RXX does not have a generator and is just stored in the commutations
644- // dictionary)
649+ // Otherwise we need to cover two cases -- either a generator is given, in which case
650+ // we return it, or we don't have a generator yet, but we know we have the operation
651+ // stored in the commutation library. For example, RXX does not have a generator in Rust
652+ // yet (PauliGate is not in Rust currently), but it is stored in the library, so we
653+ // can strip the parameters and just return the gate.
645654 if let Some ( gate) = generator {
646655 return ( gate, & [ ] , false ) ;
647656 } ;
657+ return ( op, & [ ] , false ) ;
648658 }
649659 ( op, params, false )
650660}
0 commit comments