Skip to content

Commit

Permalink
Fix 4-pi periodicity of controlled rotations in the `CommutationCheck…
Browse files Browse the repository at this point in the history
…er` (#13670)

* 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
  • Loading branch information
Cryoris authored and mergify[bot] committed Jan 16, 2025
1 parent 5c6cfe1 commit 9030922
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 32 deletions.
60 changes: 46 additions & 14 deletions crates/accelerate/src/commutation_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ use qiskit_circuit::{BitType, Clbit, Qubit};
use crate::unitary_compose;
use crate::QiskitError;

<<<<<<< HEAD
=======
// These gates do not commute with other gates, we do not check them.
>>>>>>> dffc2df6 (Fix 4-pi periodicity of controlled rotations in the `CommutationChecker` (#13670))
static SKIPPED_NAMES: [&str; 4] = ["measure", "reset", "delay", "initialize"];
static NO_CACHE_NAMES: [&str; 2] = ["annotated", "linear_function"];
static SUPPORTED_OP: Lazy<HashSet<&str>> = Lazy::new(|| {
Expand All @@ -43,25 +47,46 @@ static SUPPORTED_OP: Lazy<HashSet<&str>> = Lazy::new(|| {
])
});

<<<<<<< HEAD
const TWOPI: f64 = 2.0 * std::f64::consts::PI;

// map rotation gates to their generators, or to ``None`` if we cannot currently efficiently
// represent the generator in Rust and store the commutation relation in the commutation dictionary
static SUPPORTED_ROTATIONS: Lazy<HashMap<&str, Option<OperationRef>>> = Lazy::new(|| {
=======
// Map rotation gates to their generators (or to ``None`` if we cannot currently efficiently
// represent the generator in Rust and store the commutation relation in the commutation dictionary)
// and their pi-periodicity. Here we mean a gate is n-pi periodic, if for angles that are
// multiples of n*pi, the gate is equal to the identity up to a global phase.
// E.g. RX is generated by X and 2-pi periodic, while CRX is generated by CX and 4-pi periodic.
static SUPPORTED_ROTATIONS: Lazy<HashMap<&str, (u8, Option<OperationRef>)>> = Lazy::new(|| {
>>>>>>> dffc2df6 (Fix 4-pi periodicity of controlled rotations in the `CommutationChecker` (#13670))
HashMap::from([
("rx", Some(OperationRef::Standard(StandardGate::XGate))),
("ry", Some(OperationRef::Standard(StandardGate::YGate))),
("rz", Some(OperationRef::Standard(StandardGate::ZGate))),
("p", Some(OperationRef::Standard(StandardGate::ZGate))),
("u1", Some(OperationRef::Standard(StandardGate::ZGate))),
("crx", Some(OperationRef::Standard(StandardGate::CXGate))),
("cry", Some(OperationRef::Standard(StandardGate::CYGate))),
("crz", Some(OperationRef::Standard(StandardGate::CZGate))),
("cp", Some(OperationRef::Standard(StandardGate::CZGate))),
("rxx", None), // None means the gate is in the commutation dictionary
("ryy", None),
("rzx", None),
("rzz", None),
("rx", (2, Some(OperationRef::Standard(StandardGate::XGate)))),
("ry", (2, Some(OperationRef::Standard(StandardGate::YGate)))),
("rz", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
("p", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
("u1", (2, Some(OperationRef::Standard(StandardGate::ZGate)))),
("rxx", (2, None)), // None means the gate is in the commutation dictionary
("ryy", (2, None)),
("rzx", (2, None)),
("rzz", (2, None)),
(
"crx",
(4, Some(OperationRef::Standard(StandardGate::CXGate))),
),
(
"cry",
(4, Some(OperationRef::Standard(StandardGate::CYGate))),
),
(
"crz",
(4, Some(OperationRef::Standard(StandardGate::CZGate))),
),
(
"cp",
(2, Some(OperationRef::Standard(StandardGate::CZGate))),
),
])
});

Expand Down Expand Up @@ -629,12 +654,19 @@ fn map_rotation<'a>(
tol: f64,
) -> (&'a OperationRef<'a>, &'a [Param], bool) {
let name = op.name();
<<<<<<< HEAD
if let Some(generator) = SUPPORTED_ROTATIONS.get(name) {
// if the rotation angle is below the tolerance, the gate is assumed to
=======

if let Some((pi_multiple, generator)) = SUPPORTED_ROTATIONS.get(name) {
// If the rotation angle is below the tolerance, the gate is assumed to
>>>>>>> dffc2df6 (Fix 4-pi periodicity of controlled rotations in the `CommutationChecker` (#13670))
// commute with everything, and we simply return the operation with the flag that
// it commutes trivially
if let Param::Float(angle) = params[0] {
if (angle % TWOPI).abs() < tol {
let periodicity = (*pi_multiple as f64) * ::std::f64::consts::PI;
if (angle % periodicity).abs() < tol {
return (op, params, true);
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
fixes:
- |
The :class:`.CommutationChecker` did not handle commutations of the :class:`.CRXGate`,
:class:`.CRYGate` and :class:`.CRZGate` correctly for angles
:math:`\pi(4k + 2)` for :math:`k \in \mathbb Z`.
In these cases, the controlled rotations were falsely assumed to commute with any gate.
Now these gates correctly commute with any gate if the rotation angle is a multiple of
:math:`4\pi`.
36 changes: 18 additions & 18 deletions test/python/circuit/test_commutation_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
XGate,
ZGate,
HGate,
UnitaryGate,
)
from qiskit.dagcircuit import DAGOpNode

Expand All @@ -61,14 +62,14 @@
RYGate,
RZGate,
PhaseGate,
CRXGate,
CRYGate,
CRZGate,
CPhaseGate,
RXXGate,
RYYGate,
RZZGate,
RZXGate,
CRXGate,
CRYGate,
CRZGate,
CPhaseGate,
]


Expand Down Expand Up @@ -410,26 +411,25 @@ def test_cutoff_angles(self, gate_cls):
self.assertFalse(scc.commute(generic_gate, [0, 1], [], gate, qargs, []))

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

with self.subTest(msg="even multiples"):
for multiple in even:
for multiple in multiples:
with self.subTest(multiple=multiple):
gate = gate_cls(multiple * np.pi)
self.assertTrue(
scc.commute(generic_gate, [0], [], gate, list(range(gate.num_qubits)), [])
)
numeric = UnitaryGate(gate.to_matrix())

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

result = scc.commute(generic_gate, [0], [], gate, list(range(gate.num_qubits)), [])
self.assertEqual(expected, result)


if __name__ == "__main__":
unittest.main()

0 comments on commit 9030922

Please sign in to comment.