Skip to content

Commit 0eab4c2

Browse files
authored
Optimize PhasedXZGate._has_stabilizer_effect_ a bit more (#7852)
- avoid creating a temporary canonical instance of PhasedXZGate - avoid np.allclose and its argument-checking logic - return early if any exponent cannot match Clifford values Partially implements #7797
1 parent ca1c21a commit 0eab4c2

2 files changed

Lines changed: 44 additions & 7 deletions

File tree

cirq-core/cirq/ops/phased_x_z_gate.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import math
1919
import numbers
2020
from collections.abc import Iterator, Sequence, Set
21-
from typing import Any, TYPE_CHECKING
21+
from typing import Any, Final, TYPE_CHECKING
2222

2323
import numpy as np
2424
import sympy
@@ -331,13 +331,34 @@ def _json_dict_(self) -> dict[str, Any]:
331331
def _has_stabilizer_effect_(self) -> bool:
332332
if not self._has_unitary_():
333333
return False
334-
c = self._canonical()
335-
actual = (c._x_exponent, c._z_exponent, c._axis_phase_exponent)
336-
rounded = tuple(round(v, 2) for v in actual) # for numerical stability.
337-
return (
338-
np.allclose(actual, rounded)
339-
and tuple(v % 2 for v in rounded) in _clifford_as_phasedzx_params()
334+
tol: Final = 1e-8
335+
result = (
336+
abs((x := round(self._x_exponent, 2)) - self._x_exponent) <= tol
337+
and abs((z := round(self._z_exponent, 2)) - self._z_exponent) <= tol
338+
and abs((a := round(self._axis_phase_exponent, 2)) - self._axis_phase_exponent) <= tol
339+
and _canonical_xza_mod_2(x, z, a) in _clifford_as_phasedzx_params()
340340
)
341+
return result
342+
343+
344+
def _canonical_xza_mod_2(
345+
x_exponent: float, z_exponent: float, axis_phase_exponent: float
346+
) -> tuple[float, float, float]:
347+
"""Return canonical values of PhasedXZGate parameters modulo 2.
348+
349+
Optimized helper for `PhasedXZGate._has_stabilizer_effect_`.
350+
"""
351+
# The result must be consistent with PhasedXZGate._canonical
352+
x = x_exponent % 2
353+
a = 0.0 if x == 0 else axis_phase_exponent % 2
354+
z = z_exponent % 2
355+
if x == 1 and z != 0:
356+
a = (a + z / 2) % 2
357+
z = 0
358+
if 0.5 < a <= 1.5:
359+
a = (a - 1) % 2
360+
x = 2 - x if x else x
361+
return (x, z, a)
341362

342363

343364
@functools.cache

cirq-core/cirq/ops/phased_x_z_gate_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,19 @@ def test_has_stabilizer_effect_false_for_non_cliffords(gate: cirq.PhasedXZGate)
366366
def test_has_stabilizer_effect_returns_false_for_symbolic_unitary() -> None:
367367
gate = cirq.PhasedXZGate(x_exponent=0, z_exponent=0, axis_phase_exponent=sympy.Symbol('a'))
368368
assert not cirq.has_stabilizer_effect(gate)
369+
370+
371+
@pytest.mark.parametrize(['x', 'z', 'a'], np.random.uniform(-3, 3, (100, 3)), ids=range(100))
372+
def test_canonical_xza_mod_2_matches_canonical(x: float, z: float, a: float) -> None:
373+
gate = cirq.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a)._canonical()
374+
xza_expected = (gate.x_exponent % 2, gate.z_exponent % 2, gate.axis_phase_exponent % 2)
375+
xza_actual = cirq.ops.phased_x_z_gate._canonical_xza_mod_2(x, z, a)
376+
assert xza_actual == xza_expected
377+
# check at boundary values
378+
xb = round(4 * x) / 4
379+
zb = round(4 * z) / 4
380+
ab = round(4 * a) / 4
381+
gateb = cirq.PhasedXZGate(x_exponent=xb, z_exponent=zb, axis_phase_exponent=ab)._canonical()
382+
xzab_expected = (gateb.x_exponent % 2, gateb.z_exponent % 2, gateb.axis_phase_exponent % 2)
383+
xzab_actual = cirq.ops.phased_x_z_gate._canonical_xza_mod_2(xb, zb, ab)
384+
assert xzab_actual == xzab_expected

0 commit comments

Comments
 (0)