Skip to content

Commit dd4ceef

Browse files
Fix 4-pi periodicity of controlled rotations in the CommutationChecker (#13670) (#13676)
* start * fix 4pi periodicity of controlled pauli rots * review comments by Elena & Sasha (cherry picked from commit dffc2df) # Conflicts: # crates/accelerate/src/commutation_checker.rs Co-authored-by: Julien Gacon <[email protected]>
1 parent 98f02d7 commit dd4ceef

File tree

3 files changed

+62
-38
lines changed

3 files changed

+62
-38
lines changed

crates/accelerate/src/commutation_checker.rs

+35-20
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ use qiskit_circuit::{BitType, Clbit, Qubit};
3636
use crate::unitary_compose;
3737
use crate::QiskitError;
3838

39-
const TWOPI: f64 = 2.0 * std::f64::consts::PI;
40-
4139
// These gates do not commute with other gates, we do not check them.
4240
static SKIPPED_NAMES: [&str; 4] = ["measure", "reset", "delay", "initialize"];
4341

@@ -50,23 +48,38 @@ static SUPPORTED_OP: Lazy<HashSet<&str>> = Lazy::new(|| {
5048
])
5149
});
5250

53-
// Map rotation gates to their generators, or to ``None`` if we cannot currently efficiently
54-
// represent the generator in Rust and store the commutation relation in the commutation dictionary
55-
static SUPPORTED_ROTATIONS: Lazy<HashMap<&str, Option<OperationRef>>> = Lazy::new(|| {
51+
// Map rotation gates to their generators (or to ``None`` if we cannot currently efficiently
52+
// represent the generator in Rust and store the commutation relation in the commutation dictionary)
53+
// and their pi-periodicity. Here we mean a gate is n-pi periodic, if for angles that are
54+
// multiples of n*pi, the gate is equal to the identity up to a global phase.
55+
// E.g. RX is generated by X and 2-pi periodic, while CRX is generated by CX and 4-pi periodic.
56+
static SUPPORTED_ROTATIONS: Lazy<HashMap<&str, (u8, Option<OperationRef>)>> = Lazy::new(|| {
5657
HashMap::from([
57-
("rx", Some(OperationRef::Standard(StandardGate::XGate))),
58-
("ry", Some(OperationRef::Standard(StandardGate::YGate))),
59-
("rz", Some(OperationRef::Standard(StandardGate::ZGate))),
60-
("p", Some(OperationRef::Standard(StandardGate::ZGate))),
61-
("u1", Some(OperationRef::Standard(StandardGate::ZGate))),
62-
("crx", Some(OperationRef::Standard(StandardGate::CXGate))),
63-
("cry", Some(OperationRef::Standard(StandardGate::CYGate))),
64-
("crz", Some(OperationRef::Standard(StandardGate::CZGate))),
65-
("cp", Some(OperationRef::Standard(StandardGate::CZGate))),
66-
("rxx", None), // None means the gate is in the commutation dictionary
67-
("ryy", None),
68-
("rzx", None),
69-
("rzz", None),
58+
("rx", (2, Some(OperationRef::Standard(StandardGate::XGate)))),
59+
("ry", (2, Some(OperationRef::Standard(StandardGate::YGate)))),
60+
("rz", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
61+
("p", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
62+
("u1", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
63+
("rxx", (2, None)), // None means the gate is in the commutation dictionary
64+
("ryy", (2, None)),
65+
("rzx", (2, None)),
66+
("rzz", (2, None)),
67+
(
68+
"crx",
69+
(4, Some(OperationRef::Standard(StandardGate::CXGate))),
70+
),
71+
(
72+
"cry",
73+
(4, Some(OperationRef::Standard(StandardGate::CYGate))),
74+
),
75+
(
76+
"crz",
77+
(4, Some(OperationRef::Standard(StandardGate::CZGate))),
78+
),
79+
(
80+
"cp",
81+
(2, Some(OperationRef::Standard(StandardGate::CZGate))),
82+
),
7083
])
7184
});
7285

@@ -636,12 +649,14 @@ fn map_rotation<'a>(
636649
tol: f64,
637650
) -> (&'a OperationRef<'a>, &'a [Param], bool) {
638651
let name = op.name();
639-
if let Some(generator) = SUPPORTED_ROTATIONS.get(name) {
652+
653+
if let Some((pi_multiple, generator)) = SUPPORTED_ROTATIONS.get(name) {
640654
// If the rotation angle is below the tolerance, the gate is assumed to
641655
// commute with everything, and we simply return the operation with the flag that
642656
// it commutes trivially.
643657
if let Param::Float(angle) = params[0] {
644-
if (angle % TWOPI).abs() < tol {
658+
let periodicity = (*pi_multiple as f64) * ::std::f64::consts::PI;
659+
if (angle % periodicity).abs() < tol {
645660
return (op, params, true);
646661
};
647662
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
fixes:
3+
- |
4+
The :class:`.CommutationChecker` did not handle commutations of the :class:`.CRXGate`,
5+
:class:`.CRYGate` and :class:`.CRZGate` correctly for angles
6+
:math:`\pi(4k + 2)` for :math:`k \in \mathbb Z`.
7+
In these cases, the controlled rotations were falsely assumed to commute with any gate.
8+
Now these gates correctly commute with any gate if the rotation angle is a multiple of
9+
:math:`4\pi`.

test/python/circuit/test_commutation_checker.py

+18-18
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
XGate,
5757
ZGate,
5858
HGate,
59+
UnitaryGate,
5960
)
6061
from qiskit.dagcircuit import DAGOpNode
6162

@@ -64,14 +65,14 @@
6465
RYGate,
6566
RZGate,
6667
PhaseGate,
67-
CRXGate,
68-
CRYGate,
69-
CRZGate,
70-
CPhaseGate,
7168
RXXGate,
7269
RYYGate,
7370
RZZGate,
7471
RZXGate,
72+
CRXGate,
73+
CRYGate,
74+
CRZGate,
75+
CPhaseGate,
7576
]
7677

7778

@@ -433,26 +434,25 @@ def test_cutoff_angles(self, gate_cls):
433434
self.assertFalse(scc.commute(generic_gate, [0, 1], [], gate, qargs, []))
434435

435436
@idata(ROTATION_GATES)
436-
def test_rotation_mod_2pi(self, gate_cls):
437-
"""Test the rotations modulo 2pi commute with any gate."""
437+
def test_controlled_rotation_mod_4pi(self, gate_cls):
438+
"""Test the rotations modulo 2pi (4pi for controlled-rx/y/z) commute with any gate."""
438439
generic_gate = HGate() # does not commute with any rotation gate
439-
even = np.arange(-6, 7, 2)
440+
multiples = np.arange(-6, 7)
440441

441-
with self.subTest(msg="even multiples"):
442-
for multiple in even:
442+
for multiple in multiples:
443+
with self.subTest(multiple=multiple):
443444
gate = gate_cls(multiple * np.pi)
444-
self.assertTrue(
445-
scc.commute(generic_gate, [0], [], gate, list(range(gate.num_qubits)), [])
446-
)
445+
numeric = UnitaryGate(gate.to_matrix())
447446

448-
odd = np.arange(-5, 6, 2)
449-
with self.subTest(msg="odd multiples"):
450-
for multiple in odd:
451-
gate = gate_cls(multiple * np.pi)
452-
self.assertFalse(
453-
scc.commute(generic_gate, [0], [], gate, list(range(gate.num_qubits)), [])
447+
# compute a numeric reference, that doesn't go through any special cases and
448+
# uses a matrix-based commutation check
449+
expected = scc.commute(
450+
generic_gate, [0], [], numeric, list(range(gate.num_qubits)), []
454451
)
455452

453+
result = scc.commute(generic_gate, [0], [], gate, list(range(gate.num_qubits)), [])
454+
self.assertEqual(expected, result)
455+
456456
def test_custom_gate(self):
457457
"""Test a custom gate."""
458458
my_cx = NewGateCX()

0 commit comments

Comments
 (0)