@@ -69,7 +69,7 @@ fn compare_params(params1: &[Param], params2: &[Param]) -> PyResult<bool> {
6969
7070/// List of symmetric gates, that is the gate remains the same under all
7171/// permutations of its arguments.
72- static SYMMETRIC_GATES : [ StandardGate ; 13 ] = [
72+ static SYMMETRIC_GATES : [ StandardGate ; 12 ] = [
7373 StandardGate :: CZ ,
7474 StandardGate :: Swap ,
7575 StandardGate :: ISwap ,
@@ -81,7 +81,6 @@ static SYMMETRIC_GATES: [StandardGate; 13] = [
8181 StandardGate :: RYY ,
8282 StandardGate :: RZZ ,
8383 StandardGate :: XXMinusYY ,
84- StandardGate :: XXPlusYY ,
8584 StandardGate :: CCZ ,
8685] ;
8786
@@ -103,6 +102,35 @@ static MERGEABLE_ROTATION_GATES: [StandardGate; 12] = [
103102 StandardGate :: CU1 ,
104103] ;
105104
105+ /// Check if `inst` is symmetric for some special values of its parameters,
106+ /// taking tolerance into account.
107+ fn is_special_symmetric ( inst : & PackedInstruction , tol : f64 ) -> bool {
108+ // For now, this only handles the standard XXPlusYY gate.
109+ if let OperationRef :: StandardGate ( StandardGate :: XXPlusYY ) = inst. op . view ( ) {
110+ // From the matrix representation, applying XXPlusYY(theta, beta) on reversed qubits [q2, q1]
111+ // is the same as applying XXPlusYY(theta, -beta) on [q1, q2].
112+ // The direct computation for the average gate fidelity between XXPlusYY(theta, beta) and
113+ // XXPlusYY(theta, -beta) gives the following: 1/5 + 4/5 [1-sin^(theta/2) sin^2(beta)]^2.
114+ match inst. params_view ( ) {
115+ [ Param :: Float ( theta) , Param :: Float ( beta) ] => {
116+ // If both theta and beta are floating-point, we can evaluate the above condition
117+ // directly.
118+ let x = ( 1.0 - ( theta / 2.0 ) . sin ( ) . powf ( 2.0 ) * beta. sin ( ) . powf ( 2.0 ) ) . powf ( 2.0 ) ;
119+ return x > 1.0 - 5. / 4. * tol;
120+ }
121+ [ Param :: ParameterExpression ( _theta) , Param :: Float ( beta) ] => {
122+ // If theta is parametric, we take the estimate that works for all theta.
123+ let x = ( 1.0 - beta. sin ( ) . powf ( 2.0 ) ) . powf ( 2.0 ) ;
124+ return x > 1.0 - 5. / 4. * tol;
125+ }
126+ _ => {
127+ return false ;
128+ }
129+ }
130+ }
131+ false
132+ }
133+
106134/// Computes the canonical representative of a packed instruction, and in particular:
107135/// * replaces all types of Z-rotations by RZ-gates,
108136/// * replaces all types of X-rotations by RX-gates,
@@ -114,6 +142,8 @@ static MERGEABLE_ROTATION_GATES: [StandardGate; 12] = [
114142/// * `dag` - The output [DAGCircuit]. We use its `qargs_interner` to store sorted
115143/// qubits for symmetric gates.
116144/// * `inst` - The instruction to canonicalize.
145+ /// * `tol` - The tolerance used for fidelity computations (for instance, checking
146+ /// whether a gate is symmetric within the specified tolerance).
117147///
118148/// # Returns:
119149///
@@ -122,6 +152,7 @@ static MERGEABLE_ROTATION_GATES: [StandardGate; 12] = [
122152fn canonicalize (
123153 dag : & mut DAGCircuit ,
124154 inst : & PackedInstruction ,
155+ tol : f64 ,
125156) -> Option < ( PackedInstruction , Param ) > {
126157 // ToDo: possibly consider other rotations as well (e.g. CS -> CRZ).
127158 let rotation = match inst. op . view ( ) {
@@ -179,7 +210,7 @@ fn canonicalize(
179210 }
180211
181212 if let OperationRef :: StandardGate ( standard_gate) = inst. op . view ( ) {
182- if SYMMETRIC_GATES . contains ( & standard_gate) {
213+ if SYMMETRIC_GATES . contains ( & standard_gate) || is_special_symmetric ( inst , tol ) {
183214 let qargs = dag. get_qargs ( inst. qubits ) ;
184215 if !qargs. is_sorted ( ) {
185216 let mut sorted_qargs = qargs. to_vec ( ) ;
@@ -604,7 +635,7 @@ pub fn run_commutative_optimization(
604635 continue ;
605636 }
606637
607- if let Some ( ( new_instruction, phase_update) ) = canonicalize ( & mut new_dag, instr1) {
638+ if let Some ( ( new_instruction, phase_update) ) = canonicalize ( & mut new_dag, instr1, tol ) {
608639 node_actions[ idx1] = NodeAction :: Canonical ( new_instruction, phase_update) ;
609640 }
610641
0 commit comments