diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 55be1ad268d..b3c303bd473 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -20,7 +20,7 @@ from cirq import _compat, protocols, value, linalg, qis from cirq._import import LazyLoader -from cirq.ops import common_gates, identity, named_qubit, raw_types, pauli_gates, phased_x_z_gate +from cirq.ops import common_gates, named_qubit, raw_types, pauli_gates, phased_x_z_gate from cirq.ops.pauli_gates import Pauli from cirq.type_workarounds import NotImplementedType @@ -158,121 +158,131 @@ class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf): # Note that in python 3.9+ @classmethod can be used with @property, so these # can be moved to CommonCliffordGates. + @property + def all_single_qubit_cliffords(cls) -> Sequence['cirq.SingleQubitCliffordGate']: + """All 24 single-qubit Clifford gates.""" + if not hasattr(cls, '_all_single_qubit_cliffords'): + pX = (pauli_gates.X, False) + mX = (pauli_gates.X, True) + pY = (pauli_gates.Y, False) + mY = (pauli_gates.Y, True) + pZ = (pauli_gates.Z, False) + mZ = (pauli_gates.Z, True) + + def from_xz(x_to, z_to): + return SingleQubitCliffordGate.from_clifford_tableau( + _to_clifford_tableau(x_to=x_to, z_to=z_to) + ) + + # Order in is relied on in properties that retrieve a specific Clifford below. + cls._all_single_qubit_cliffords = ( + # 0: Identity + from_xz(x_to=pX, z_to=pZ), # I + # 1..3: Paulis + from_xz(x_to=pX, z_to=mZ), # X + from_xz(x_to=mX, z_to=mZ), # Y + from_xz(x_to=mX, z_to=pZ), # Z + # 4..6: Square roots of Paulis + from_xz(x_to=pX, z_to=mY), # I-iX + from_xz(x_to=mZ, z_to=pX), # I-iY + from_xz(x_to=pY, z_to=pZ), # I-iZ aka S + # 7..9: Negative square roots of Paulis + from_xz(x_to=pX, z_to=pY), # I+iX + from_xz(x_to=pZ, z_to=mX), # I+iY + from_xz(x_to=mY, z_to=pZ), # I+iZ + # 10..15: Hadamards + from_xz(x_to=pZ, z_to=pX), # Z+X aka H + from_xz(x_to=pY, z_to=mZ), # X+Y + from_xz(x_to=mX, z_to=pY), # Y+Z + from_xz(x_to=mZ, z_to=mX), # Z-X + from_xz(x_to=mY, z_to=mZ), # X-Y + from_xz(x_to=mX, z_to=mY), # Y-Z + # 16..23: Order-3 Cliffords + from_xz(x_to=pY, z_to=pX), # I-i(+X+Y+Z) + from_xz(x_to=mZ, z_to=mY), # I-i(+X+Y-Z) + from_xz(x_to=pZ, z_to=mY), # I-i(+X-Y+Z) + from_xz(x_to=mY, z_to=mX), # I-i(+X-Y-Z) + from_xz(x_to=mZ, z_to=pY), # I-i(-X+Y+Z) + from_xz(x_to=mY, z_to=pX), # I-i(-X+Y-Z) + from_xz(x_to=pY, z_to=mX), # I-i(-X-Y+Z) + from_xz(x_to=pZ, z_to=pY), # I-i(-X-Y-Z) + ) + return cls._all_single_qubit_cliffords + @property def I(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_I'): - cls._I = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, identity.I)) - return cls._I + return cls.all_single_qubit_cliffords[0] @property def X(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_X'): - cls._X = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, pauli_gates.X)) - return cls._X + return cls.all_single_qubit_cliffords[1] @property def Y(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_Y'): - cls._Y = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, pauli_gates.Y)) - return cls._Y + return cls.all_single_qubit_cliffords[2] @property def Z(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_Z'): - cls._Z = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, pauli_gates.Z)) - return cls._Z + return cls.all_single_qubit_cliffords[3] @property def H(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_H'): - cls._H = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, common_gates.H)) - return cls._H + return cls.all_single_qubit_cliffords[10] @property def S(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_S'): - cls._S = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, common_gates.S)) - return cls._S + return cls.all_single_qubit_cliffords[6] @property def CNOT(cls) -> 'cirq.CliffordGate': if not hasattr(cls, '_CNOT'): - cls._CNOT = CliffordGate.from_clifford_tableau(_gate_tableau(2, common_gates.CNOT)) + t = qis.CliffordTableau(num_qubits=2) + t.xs = [[1, 1], [0, 1], [0, 0], [0, 0]] + t.zs = [[0, 0], [0, 0], [1, 0], [1, 1]] + cls._CNOT = CliffordGate.from_clifford_tableau(t) return cls._CNOT @property def CZ(cls) -> 'cirq.CliffordGate': if not hasattr(cls, '_CZ'): - cls._CZ = CliffordGate.from_clifford_tableau(_gate_tableau(2, common_gates.CZ)) + t = qis.CliffordTableau(num_qubits=2) + t.xs = [[1, 0], [0, 1], [0, 0], [0, 0]] + t.zs = [[0, 1], [1, 0], [1, 0], [0, 1]] + cls._CZ = CliffordGate.from_clifford_tableau(t) return cls._CZ @property def SWAP(cls) -> 'cirq.CliffordGate': if not hasattr(cls, '_SWAP'): - cls._SWAP = CliffordGate.from_clifford_tableau(_gate_tableau(2, common_gates.SWAP)) + t = qis.CliffordTableau(num_qubits=2) + t.xs = [[0, 1], [1, 0], [0, 0], [0, 0]] + t.zs = [[0, 0], [0, 0], [0, 1], [1, 0]] + cls._SWAP = CliffordGate.from_clifford_tableau(t) return cls._SWAP @property def X_sqrt(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_X_sqrt'): - # Unfortunately, due the code style, the matrix should be viewed transposed. - # Note xs, zs, and rs are column vector. - # Transformation: X -> X, Z -> -Y - clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[0, 1], xs=[[1], [1]], zs=[[0], [1]] - ) - cls._X_sqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau) - return cls._X_sqrt + return cls.all_single_qubit_cliffords[4] @property def X_nsqrt(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_X_nsqrt'): - # Transformation: X->X, Z->Y - clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[0, 0], xs=[[1], [1]], zs=[[0], [1]] - ) - cls._X_nsqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau) - return cls._X_nsqrt + return cls.all_single_qubit_cliffords[7] @property def Y_sqrt(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_Y_sqrt'): - # Transformation: X -> -Z, Z -> X - clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[1, 0], xs=[[0], [1]], zs=[[1], [0]] - ) - cls._Y_sqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau) - return cls._Y_sqrt + return cls.all_single_qubit_cliffords[5] @property def Y_nsqrt(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_Y_nsqrt'): - # Transformation: X -> Z, Z -> -X - clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[0, 1], xs=[[0], [1]], zs=[[1], [0]] - ) - cls._Y_nsqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau) - return cls._Y_nsqrt + return cls.all_single_qubit_cliffords[8] @property def Z_sqrt(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_Z_sqrt'): - # Transformation: X -> Y, Z -> Z - _clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[0, 0], xs=[[1], [0]], zs=[[1], [1]] - ) - cls._Z_sqrt = SingleQubitCliffordGate.from_clifford_tableau(_clifford_tableau) - return cls._Z_sqrt + return cls.all_single_qubit_cliffords[6] @property def Z_nsqrt(cls) -> 'cirq.SingleQubitCliffordGate': - if not hasattr(cls, '_Z_nsqrt'): - # Transformation: X -> -Y, Z -> Z - _clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[1, 0], xs=[[1], [0]], zs=[[1], [1]] - ) - cls._Z_nsqrt = SingleQubitCliffordGate.from_clifford_tableau(_clifford_tableau) - return cls._Z_nsqrt + return cls.all_single_qubit_cliffords[9] class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 500d7aa8332..3158cd2ccf7 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -535,22 +535,7 @@ def test_text_diagram_info(gate, sym, exp): ) -@pytest.mark.parametrize( - "clifford_gate", - ( - cirq.SingleQubitCliffordGate.I, - cirq.SingleQubitCliffordGate.H, - cirq.SingleQubitCliffordGate.X, - cirq.SingleQubitCliffordGate.Y, - cirq.SingleQubitCliffordGate.Z, - cirq.SingleQubitCliffordGate.X_sqrt, - cirq.SingleQubitCliffordGate.Y_sqrt, - cirq.SingleQubitCliffordGate.Z_sqrt, - cirq.SingleQubitCliffordGate.X_nsqrt, - cirq.SingleQubitCliffordGate.Y_nsqrt, - cirq.SingleQubitCliffordGate.Z_nsqrt, - ), -) +@pytest.mark.parametrize("clifford_gate", cirq.SingleQubitCliffordGate.all_single_qubit_cliffords) def test_from_unitary(clifford_gate): u = cirq.unitary(clifford_gate) result_gate = cirq.SingleQubitCliffordGate.from_unitary(u) @@ -589,22 +574,7 @@ def test_from_unitary_not_clifford(): assert cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u) is None -@pytest.mark.parametrize( - "clifford_gate", - ( - cirq.SingleQubitCliffordGate.I, - cirq.SingleQubitCliffordGate.H, - cirq.SingleQubitCliffordGate.X, - cirq.SingleQubitCliffordGate.Y, - cirq.SingleQubitCliffordGate.Z, - cirq.SingleQubitCliffordGate.X_sqrt, - cirq.SingleQubitCliffordGate.Y_sqrt, - cirq.SingleQubitCliffordGate.Z_sqrt, - cirq.SingleQubitCliffordGate.X_nsqrt, - cirq.SingleQubitCliffordGate.Y_nsqrt, - cirq.SingleQubitCliffordGate.Z_nsqrt, - ), -) +@pytest.mark.parametrize("clifford_gate", cirq.SingleQubitCliffordGate.all_single_qubit_cliffords) def test_decompose_gate(clifford_gate): gates = clifford_gate.decompose_gate() u = functools.reduce(np.dot, [np.eye(2), *(cirq.unitary(gate) for gate in reversed(gates))]) @@ -659,12 +629,12 @@ def test_common_clifford_gate(clifford_gate, standard_gate): cirq.testing.assert_allclose_up_to_global_phase(u_c, u_s, atol=1e-8) -@pytest.mark.parametrize('clifford_gate_name', ("I", "X", "Y", "Z", "H", "S", "CNOT", "CZ", "SWAP")) -def test_common_clifford_gate_caching(clifford_gate_name): - cache_name = f"_{clifford_gate_name}" +@pytest.mark.parametrize('property_name', ("all_single_qubit_cliffords", "CNOT", "CZ", "SWAP")) +def test_common_clifford_gate_caching(property_name): + cache_name = f"_{property_name}" delattr(cirq.CliffordGate, cache_name) assert not hasattr(cirq.CliffordGate, cache_name) - _ = getattr(cirq.CliffordGate, clifford_gate_name) + _ = getattr(cirq.CliffordGate, property_name) assert hasattr(cirq.CliffordGate, cache_name) @@ -912,3 +882,43 @@ def test_clifford_gate_act_on_ch_form(): def test_clifford_gate_act_on_fail(): with pytest.raises(TypeError, match="Failed to act"): cirq.act_on(cirq.CliffordGate.X, DummySimulationState(), qubits=()) + + +def test_all_single_qubit_clifford_unitaries(): + i = np.eye(2) + x = np.array([[0, 1], [1, 0]]) + y = np.array([[0, -1j], [1j, 0]]) + z = np.diag([1, -1]) + + cs = [cirq.unitary(c) for c in cirq.CliffordGate.all_single_qubit_cliffords] + + # Identity + assert cirq.equal_up_to_global_phase(cs[0], i) + # Paulis + assert cirq.equal_up_to_global_phase(cs[1], x) + assert cirq.equal_up_to_global_phase(cs[2], y) + assert cirq.equal_up_to_global_phase(cs[3], z) + # Square roots of Paulis + assert cirq.equal_up_to_global_phase(cs[4], (i - 1j * x) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[5], (i - 1j * y) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[6], (i - 1j * z) / np.sqrt(2)) + # Negative square roots of Paulis + assert cirq.equal_up_to_global_phase(cs[7], (i + 1j * x) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[8], (i + 1j * y) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[9], (i + 1j * z) / np.sqrt(2)) + # Hadamards + assert cirq.equal_up_to_global_phase(cs[10], (z + x) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[11], (x + y) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[12], (y + z) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[13], (z - x) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[14], (x - y) / np.sqrt(2)) + assert cirq.equal_up_to_global_phase(cs[15], (y - z) / np.sqrt(2)) + # Order-3 Cliffords + assert cirq.equal_up_to_global_phase(cs[16], (i - 1j * (x + y + z)) / 2) + assert cirq.equal_up_to_global_phase(cs[17], (i - 1j * (x + y - z)) / 2) + assert cirq.equal_up_to_global_phase(cs[18], (i - 1j * (x - y + z)) / 2) + assert cirq.equal_up_to_global_phase(cs[19], (i - 1j * (x - y - z)) / 2) + assert cirq.equal_up_to_global_phase(cs[20], (i - 1j * (-x + y + z)) / 2) + assert cirq.equal_up_to_global_phase(cs[21], (i - 1j * (-x + y - z)) / 2) + assert cirq.equal_up_to_global_phase(cs[22], (i - 1j * (-x - y + z)) / 2) + assert cirq.equal_up_to_global_phase(cs[23], (i - 1j * (-x - y - z)) / 2)