From b2d9269699d1e771f2b5b62a7e7273a59f0f6bc7 Mon Sep 17 00:00:00 2001 From: ybc Date: Fri, 28 May 2021 23:29:29 -0700 Subject: [PATCH 01/21] Fix the typo in clifford_gate::test_commutes_pauli --- cirq-core/cirq/ops/clifford_gate_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 7b8b9a1f5e9..d585b719294 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -445,7 +445,7 @@ def test_commutes_single_qubit_gate(gate, other): @pytest.mark.parametrize( 'gate,pauli,half_turns', - itertools.product(_all_clifford_gates(), _paulis, (0.1, 0.25, 0.5, -0.5)), + itertools.product(_all_clifford_gates(), _paulis, (1.0, 0.25, 0.5, -0.5)), ) def test_commutes_pauli(gate, pauli, half_turns): pauli_gate = pauli ** half_turns @@ -458,7 +458,7 @@ def test_commutes_pauli(gate, pauli, half_turns): pauli_gate(q0), gate(q0), ).unitary() - commutes = cirq.commutes(gate, pauli) + commutes = cirq.commutes(pauli_gate, pauli) commutes_check = cirq.allclose_up_to_global_phase(mat, mat_swap) assert commutes == commutes_check From 0234dda2426921ade40ededd7811b919f995c67d Mon Sep 17 00:00:00 2001 From: ybc Date: Fri, 28 May 2021 23:30:06 -0700 Subject: [PATCH 02/21] Revert "Fix the typo in clifford_gate::test_commutes_pauli" This reverts commit b2d9269699d1e771f2b5b62a7e7273a59f0f6bc7. --- cirq-core/cirq/ops/clifford_gate_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index d585b719294..7b8b9a1f5e9 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -445,7 +445,7 @@ def test_commutes_single_qubit_gate(gate, other): @pytest.mark.parametrize( 'gate,pauli,half_turns', - itertools.product(_all_clifford_gates(), _paulis, (1.0, 0.25, 0.5, -0.5)), + itertools.product(_all_clifford_gates(), _paulis, (0.1, 0.25, 0.5, -0.5)), ) def test_commutes_pauli(gate, pauli, half_turns): pauli_gate = pauli ** half_turns @@ -458,7 +458,7 @@ def test_commutes_pauli(gate, pauli, half_turns): pauli_gate(q0), gate(q0), ).unitary() - commutes = cirq.commutes(pauli_gate, pauli) + commutes = cirq.commutes(gate, pauli) commutes_check = cirq.allclose_up_to_global_phase(mat, mat_swap) assert commutes == commutes_check From 00586839914e9f1d3cf083e4333746f89cac36be Mon Sep 17 00:00:00 2001 From: ybc Date: Fri, 31 Dec 2021 15:00:42 -0800 Subject: [PATCH 03/21] Init add multi-qubit clifford gates --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/ops/__init__.py | 1 + cirq-core/cirq/ops/clifford_gate.py | 230 ++++++++++++++++++++++- cirq-core/cirq/ops/clifford_gate_test.py | 28 +++ 4 files changed, 259 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index a39a3e3b391..fb6a93b84d8 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -189,6 +189,7 @@ CCXPowGate, CCZ, CCZPowGate, + CliffordGate, CCNOT, CCNotPowGate, ClassicallyControlledOperation, diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index 056013e5ad4..386280cc997 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -19,6 +19,7 @@ ) from cirq.ops.clifford_gate import ( + CliffordGate, PauliTransform, SingleQubitCliffordGate, ) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 1fe8e6419f1..a716796ab1f 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -18,7 +18,14 @@ from cirq import protocols, value, linalg, qis from cirq._doc import document -from cirq.ops import common_gates, gate_features, named_qubit, pauli_gates, phased_x_z_gate +from cirq.ops import ( + common_gates, + gate_features, + named_qubit, + raw_types, + pauli_gates, + phased_x_z_gate, +) from cirq.ops.pauli_gates import Pauli from cirq.type_workarounds import NotImplementedType @@ -107,6 +114,7 @@ def _validate_map_input( return {frm: PauliTransform(to, flip) for frm, (to, flip) in pauli_map_to.items()} +# TODO Make SingleQubitCliffordGate as derived class of CliffordGate since it contains more functions. @value.value_equality class SingleQubitCliffordGate(gate_features.SingleQubitGate): """Any single qubit Clifford rotation.""" @@ -571,3 +579,223 @@ def _circuit_diagram_info_( SingleQubitCliffordGate.Z: SingleQubitCliffordGate.Z_nsqrt, }, } + + +class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf): + """A metaclass used to lazy initialize several common Clifford Gate as class attributes.""" + + @property + def X(cls): + if getattr(cls, '_X', None) is None: + cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.X) + return cls._Z + + @property + def Y(cls): + if getattr(cls, '_X', None) is None: + cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.Y) + return cls._Z + + @property + def Z(cls): + if getattr(cls, '_X', None) is None: + cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.Z) + return cls._Z + + @property + def H(cls): + if getattr(cls, '_H', None) is None: + cls._H = cls._generate_clifford_from_known_gate(1, common_gates.H) + return cls._H + + @property + def S(cls): + if getattr(cls, '_S', None) is None: + cls._S = cls._generate_clifford_from_known_gate(1, pauli_gates.S) + return cls._S + + @property + def CNOT(cls): + if getattr(cls, '_CNOT', None) is None: + cls._CNOT = cls._generate_clifford_from_known_gate(2, common_gates.CNOT) + return cls._CNOT + + @property + def CZ(cls): + if getattr(cls, '_CZ', None) is None: + cls._CZ = cls._generate_clifford_from_known_gate(2, common_gates.CZ) + return cls._CZ + + +class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): + + # We need to use the lazy initialization of these common gates since they need to use + # cirq.sim, which can not be imported when + @classmethod + def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': + from cirq import LineQubit, sim + + qubits = LineQubit.range(num_qubits) + t = qis.CliffordTableau(num_qubits=num_qubits) + args = sim.ActOnCliffordTableauArgs( + tableau=t, qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={} + ) + + protocols.act_on(gate, args, qubits, allow_decompose=False) + return CliffordGate.from_clifford_tableau(args.tableau) + + +@value.value_equality +class CliffordGate(raw_types.Gate, CommonCliffordGates): + """Clifford rotation for N-qubit.""" + + def __init__( + self, + *, + _clifford_tableau: qis.CliffordTableau, + ) -> None: + if not _clifford_tableau._validate(): + raise ValueError('Input is not a valid Clifford tableau.') + self._clifford_tableau = _clifford_tableau + + @property + def clifford_tableau(self): + return self._clifford_tableau + + def _swap_qubit_order(self, i: int, j: int): + """The qubit order of Clifford Tableau is important for the tablue. + If we want to swap the application on qubit i and j, we need to + + X Z sign + from X [ X_x Z_x | r_x ] + from Z [ X_z Z_z | r_z ] + """ + assert i >= 0 and i < self.clifford_tableau.n + assert j >= 0 and j < self.clifford_tableau.n + assert i != j + n = self.clifford_tableau.n + # Swap the columns + self._clifford_tableau.xs[:, [i, j]] = self._clifford_tableau.xs[:, [j, i]] + self._clifford_tableau.zs[:, [i, j]] = self._clifford_tableau.zs[:, [j, i]] + self._clifford_tableau.xs[:, [n + i, n + j]] = self._clifford_tableau.xs[:, [n + j, n + i]] + self._clifford_tableau.zs[:, [n + i, n + j]] = self._clifford_tableau.zs[:, [n + j, n + i]] + + # Swap the rows + self._clifford_tableau.xs[[i, j], :] = self._clifford_tableau.xs[[j, i], :] + self._clifford_tableau.zs[[i, j], :] = self._clifford_tableau.zs[[j, i], :] + self._clifford_tableau.xs[[n + i, n + j], :] = self._clifford_tableau.xs[[n + j, n + i], :] + self._clifford_tableau.zs[[n + i, n + j], :] = self._clifford_tableau.zs[[n + j, n + i], :] + self._clifford_tableau.rs[[i, j]] = self._clifford_tableau.rs[[j, i]] + self._clifford_tableau.rs[[n + i, n + j]] = self._clifford_tableau.rs[[n + j, n + i]] + + @classmethod + def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': + assert isinstance(tableau, qis.CliffordTableau) + if not tableau._validate(): + raise ValueError('It is not a valid Clifford tableau.') + return CliffordGate(_clifford_tableau=tableau) + + @classmethod + def from_op_list( + cls, + *, + num_qubits, + ): + """ + Take a list like + h 0 + cnot 3, 4 + cz 1, 0 + x 2 + return the complied Clifford gates + """ + pass + + def _value_equality_values_(self): + return self.clifford_tableau + + def _num_qubits_(self): + return self.clifford_tableau.n + + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': + from cirq.optimizers import clifford_decomposition + + return clifford_decomposition.decompose_clifford_tableau_to_operations( + list(qubits), self.clifford_tableau + ) + + def __repr__(self) -> str: + return f"Clifford Gate with Tableau {self.clifford_tableau.__repr__()}" + + def _unitary_(self) -> np.ndarray: + # This is not efficient. + state = np.eye(2 ** self.clifford_tableau.n, dtype=np.complex128) + buffer = np.empty_like(state) + qubits = tuple( + [named_qubit.NamedQubit(f'arbitrary_{i}') for i in range(self._num_qubits_())] + ) + return protocols.apply_unitaries( + self._decompose_(qubits), qubits, + protocols.ApplyUnitaryArgs(state, buffer, range(self.clifford_tableau.n))) + + def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: + # Note here we assume two CLifford define the tabluea based on the same qubit order! + if not isinstance(other, CliffordGate): + return NotImplemented + return self.clifford_tableau.then(other.clifford_tableau) == other.clifford_tableau.then( + self.clifford_tableau + ) + + # def merged_with(self, second: 'CliffordGate') -> 'CliffordGate': + # """Returns a CliffordGate such that the circuits + # --output-- and --self--second-- + # are equivalent up to global phase. + + # # !!!!! We need to care about the order of Qubit!!!! + # # Here we assume two Clifford Gates are defined under same order! + # # For exaample, CliffordGate.CZ.merged_with(CliffordGate.CNOT) are equivalent to + # # CNOT(0, 1) followed by CZ(0, 1). + # # Therefore this function signature is not sufficient! + # """ + + # return CliffordGate.from_clifford_tableau( + # self.clifford_tableau.then(second.clifford_tableau) + # ) + + def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: + from cirq.sim import clifford + + # Note there are two tricky things here + # 1. The clifford tableau under the ActOnArgs can be larger than self.clifford_tableau + # 2. The application order. self should be the second one. + + # Here we assume the argument `qubits` provided the order of the self.clifford_tableau. + # Idea is we extract the corresponding partial tableau out of ActOnArgs, next we apply the + # clifford_tableau.then() method with the self.clifford_tableau, last inject this + # new output clifford tableau back ot the ActOnArgs. + if isinstance(args, clifford.ActOnCliffordTableauArgs): + if not protocols.has_stabilizer_effect(self): # Do we need it???? + return NotImplemented + # TODO(ybc) + + pass + + # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? + + return NotImplemented + + def __pow__(self, exponent) -> 'CliffordGate': + if exponent == -1: + return CliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) + if exponent > 0 and int(exponent) == exponent: + base_tableau = self.clifford_tableau.copy() + for _ in range(int(exponent) - 1): + base_tableau = base_tableau.then(self.clifford_tableau) + return CliffordGate.from_clifford_tableau(base_tableau) + if exponent < 0 and int(exponent) == exponent: + base_tableau = self.clifford_tableau.copy() + for _ in range(int(-exponent) - 1): + base_tableau = base_tableau.then(self.clifford_tableau) + return CliffordGate.from_clifford_tableau(base_tableau.inverse()) + + return NotImplemented diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 6967692fdf6..6e717558bb1 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -584,3 +584,31 @@ def test_from_xz_to_clifford_tableau(): # Should not have any duplication. assert len(set(seen_tableau)) == 24 + + +def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': + + t = cirq.CliffordTableau(num_qubits=num_qubits) + args = sim.ActOnCliffordTableauArgs( + tableau=t, prng=np.random.RandomState(), log_of_measurement_results={} + ) + + cirq.act_on(gate, args, allow_decompose=False) + return CliffordGate.from_clifford_tableau(args.tableau) + +def test_common_clifford_gate(): + qubits = cirq.LineQubit.range(2) + print('\n', cirq.unitary(cirq.CliffordGate.X(qubits[0]))) + print('\n', cirq.unitary(cirq.CliffordGate.X(qubits[0]))) + print('\n', cirq.unitary(cirq.CliffordGate.Y(qubits[0]))) + print('\n', cirq.unitary(cirq.CliffordGate.Z(qubits[0]))) + print('\n', cirq.unitary(cirq.CliffordGate.CNOT(*qubits))) + # print('\n', cirq.unitary(cirq.CliffordGate.CZ(*qubits))) + + print('\n', cirq.unitary(cirq.Y(qubits[0]))) + + print('\n', cirq.decompose_once(cirq.CliffordGate.CZ(*qubits))) + + clifford_gate = cirq.CliffordGate.CNOT(*qubits) + +test_common_clifford_gate() \ No newline at end of file From a93a8741f9e2f6887afed4e4081670e8f5c3d6eb Mon Sep 17 00:00:00 2001 From: ybc Date: Sat, 1 Jan 2022 21:36:13 -0800 Subject: [PATCH 04/21] Add more functionality to clifford gate --- cirq-core/cirq/ops/clifford_gate.py | 243 ++++++++++++----------- cirq-core/cirq/ops/clifford_gate_test.py | 130 +++++++++--- 2 files changed, 239 insertions(+), 134 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index a716796ab1f..32691300348 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -12,7 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, cast, Dict, NamedTuple, Optional, Sequence, Tuple, TYPE_CHECKING, Union +from typing import ( + Any, + cast, + Dict, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + TYPE_CHECKING, + Union, +) import numpy as np @@ -114,7 +125,6 @@ def _validate_map_input( return {frm: PauliTransform(to, flip) for frm, (to, flip) in pauli_map_to.items()} -# TODO Make SingleQubitCliffordGate as derived class of CliffordGate since it contains more functions. @value.value_equality class SingleQubitCliffordGate(gate_features.SingleQubitGate): """Any single qubit Clifford rotation.""" @@ -611,7 +621,7 @@ def H(cls): @property def S(cls): if getattr(cls, '_S', None) is None: - cls._S = cls._generate_clifford_from_known_gate(1, pauli_gates.S) + cls._S = cls._generate_clifford_from_known_gate(1, common_gates.S) return cls._S @property @@ -626,13 +636,21 @@ def CZ(cls): cls._CZ = cls._generate_clifford_from_known_gate(2, common_gates.CZ) return cls._CZ + @property + def SWAP(cls): + if getattr(cls, '_SWAP', None) is None: + cls._SWAP = cls._generate_clifford_from_known_gate(2, common_gates.SWAP) + return cls._SWAP + class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): # We need to use the lazy initialization of these common gates since they need to use # cirq.sim, which can not be imported when @classmethod - def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': + def _generate_clifford_from_known_gate( + cls, num_qubits: int, gate: raw_types.Gate + ) -> 'CliffordGate': from cirq import LineQubit, sim qubits = LineQubit.range(num_qubits) @@ -645,6 +663,30 @@ def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': return CliffordGate.from_clifford_tableau(args.tableau) +def _pad_tableau( + clifford_tableau: qis.CliffordTableau, num_qubits_after_padding: int, axes: List[int] +) -> qis.CliffordTableau: + """Roughly, this function copies self.tabluea into the "identity" matrix.""" + # Sanity check + if len(set(axes)) != clifford_tableau.n: + raise ValueError( + "Input axes of padding should match with the number of qubits in the input tableau." + ) + if clifford_tableau.n > num_qubits_after_padding: + raise ValueError( + "The number of qubits in the input tableau should not be larger than " + "num_qubits_after_padding." + ) + + padded_tableau = qis.CliffordTableau(num_qubits_after_padding) + v_index = np.concatenate((np.asarray(axes), num_qubits_after_padding + np.asarray(axes))) + + padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs.copy() + padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs.copy() + padded_tableau.rs[v_index] = clifford_tableau.rs.copy() + return padded_tableau + + @value.value_equality class CliffordGate(raw_types.Gate, CommonCliffordGates): """Clifford rotation for N-qubit.""" @@ -656,38 +698,31 @@ def __init__( ) -> None: if not _clifford_tableau._validate(): raise ValueError('Input is not a valid Clifford tableau.') - self._clifford_tableau = _clifford_tableau + # We use the Clifford tableau to represent a Clifford gate. + # It is crucial to note that the meaning of tableau here is different + # from the one used to represent a Clifford state (Of course, they are related). + # A) We have to use the full 2n * (2n + 1) matrix + # B) The meaning of tableau here is + # X Z sign + # from X [ X_x Z_x | r_x ] + # from Z [ X_z Z_z | r_z ] + # Each row in the Clifford tableau means the transformation of original Pauli gates. + # For example, take a 2 * (2+1) tableau as example: + # X Z r + # XI [ 1 0 | 1 0 | 0 ] + # IX [ 0 0 | 1 1 | 0 ] + # ZI [ 0 0 | 1 0 | 1 ] + # IZ [ 1 0 | 1 1 | 0 ] + # Take the third row as example: this means the ZI gate after the this gate, + # more precisely the conjugate transformation of ZI by this gate, becomes -ZI. + # (Note the real clifford tableau has to satify the Symplectic property. + # here is just for illustration) + self._clifford_tableau = _clifford_tableau.copy() @property def clifford_tableau(self): return self._clifford_tableau - def _swap_qubit_order(self, i: int, j: int): - """The qubit order of Clifford Tableau is important for the tablue. - If we want to swap the application on qubit i and j, we need to - - X Z sign - from X [ X_x Z_x | r_x ] - from Z [ X_z Z_z | r_z ] - """ - assert i >= 0 and i < self.clifford_tableau.n - assert j >= 0 and j < self.clifford_tableau.n - assert i != j - n = self.clifford_tableau.n - # Swap the columns - self._clifford_tableau.xs[:, [i, j]] = self._clifford_tableau.xs[:, [j, i]] - self._clifford_tableau.zs[:, [i, j]] = self._clifford_tableau.zs[:, [j, i]] - self._clifford_tableau.xs[:, [n + i, n + j]] = self._clifford_tableau.xs[:, [n + j, n + i]] - self._clifford_tableau.zs[:, [n + i, n + j]] = self._clifford_tableau.zs[:, [n + j, n + i]] - - # Swap the rows - self._clifford_tableau.xs[[i, j], :] = self._clifford_tableau.xs[[j, i], :] - self._clifford_tableau.zs[[i, j], :] = self._clifford_tableau.zs[[j, i], :] - self._clifford_tableau.xs[[n + i, n + j], :] = self._clifford_tableau.xs[[n + j, n + i], :] - self._clifford_tableau.zs[[n + i, n + j], :] = self._clifford_tableau.zs[[n + j, n + i], :] - self._clifford_tableau.rs[[i, j]] = self._clifford_tableau.rs[[j, i]] - self._clifford_tableau.rs[[n + i, n + j]] = self._clifford_tableau.rs[[n + j, n + i]] - @classmethod def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': assert isinstance(tableau, qis.CliffordTableau) @@ -697,92 +732,36 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': @classmethod def from_op_list( - cls, - *, - num_qubits, - ): - """ - Take a list like - h 0 - cnot 3, 4 - cz 1, 0 - x 2 - return the complied Clifford gates - """ - pass - - def _value_equality_values_(self): - return self.clifford_tableau - - def _num_qubits_(self): - return self.clifford_tableau.n - - def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': - from cirq.optimizers import clifford_decomposition - - return clifford_decomposition.decompose_clifford_tableau_to_operations( - list(qubits), self.clifford_tableau - ) - - def __repr__(self) -> str: - return f"Clifford Gate with Tableau {self.clifford_tableau.__repr__()}" - - def _unitary_(self) -> np.ndarray: - # This is not efficient. - state = np.eye(2 ** self.clifford_tableau.n, dtype=np.complex128) - buffer = np.empty_like(state) - qubits = tuple( - [named_qubit.NamedQubit(f'arbitrary_{i}') for i in range(self._num_qubits_())] - ) - return protocols.apply_unitaries( - self._decompose_(qubits), qubits, - protocols.ApplyUnitaryArgs(state, buffer, range(self.clifford_tableau.n))) - - def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: - # Note here we assume two CLifford define the tabluea based on the same qubit order! - if not isinstance(other, CliffordGate): - return NotImplemented - return self.clifford_tableau.then(other.clifford_tableau) == other.clifford_tableau.then( - self.clifford_tableau - ) - - # def merged_with(self, second: 'CliffordGate') -> 'CliffordGate': - # """Returns a CliffordGate such that the circuits - # --output-- and --self--second-- - # are equivalent up to global phase. - - # # !!!!! We need to care about the order of Qubit!!!! - # # Here we assume two Clifford Gates are defined under same order! - # # For exaample, CliffordGate.CZ.merged_with(CliffordGate.CNOT) are equivalent to - # # CNOT(0, 1) followed by CZ(0, 1). - # # Therefore this function signature is not sufficient! - # """ - - # return CliffordGate.from_clifford_tableau( - # self.clifford_tableau.then(second.clifford_tableau) - # ) - - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: + cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] + ) -> 'CliffordGate': + """Construct a new Clifford gates from several known operations.""" from cirq.sim import clifford - # Note there are two tricky things here - # 1. The clifford tableau under the ActOnArgs can be larger than self.clifford_tableau - # 2. The application order. self should be the second one. + for op in operations: + if op.gate and op.gate._has_stabilizer_effect_: + continue + raise ValueError( + "Clifford Gate can only be constructed from the " + "operations that has stabilizer effect." + ) - # Here we assume the argument `qubits` provided the order of the self.clifford_tableau. - # Idea is we extract the corresponding partial tableau out of ActOnArgs, next we apply the - # clifford_tableau.then() method with the self.clifford_tableau, last inject this - # new output clifford tableau back ot the ActOnArgs. - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): # Do we need it???? - return NotImplemented - # TODO(ybc) + base_tableau = qis.CliffordTableau(len(qubit_order)) + args = clifford.ActOnCliffordTableauArgs( + tableau=base_tableau, + qubits=qubit_order, + prng=np.random.RandomState(0), # unused + log_of_measurement_results={}, # unused + ) + for op in operations: + protocols.act_on(op, args, allow_decompose=True) - pass + return CliffordGate.from_clifford_tableau(args.tableau) - # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? + def _value_equality_values_(self): + return self.clifford_tableau - return NotImplemented + def _num_qubits_(self): + return self.clifford_tableau.n def __pow__(self, exponent) -> 'CliffordGate': if exponent == -1: @@ -799,3 +778,45 @@ def __pow__(self, exponent) -> 'CliffordGate': return CliffordGate.from_clifford_tableau(base_tableau.inverse()) return NotImplemented + + def __repr__(self) -> str: + return f"Clifford Gate with Tableau:\n {self.clifford_tableau._str_full_()}" + + def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: + # Note even if we assume two gates define the tabluea based on the same qubit order, + # the following approach cannot judge it: + # self.clifford_tableau.then(other.clifford_tableau) == other.clifford_tableau.then( + # self.clifford_tableau + # ) + # For example: X.then(Z) and Z.then(X) both return same tableau + # it is because Clifford tableau ignores the global phase information. + return NotImplemented + + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation]: + from cirq.optimizers import clifford_decomposition + + return clifford_decomposition.decompose_clifford_tableau_to_operations( + list(qubits), self.clifford_tableau + ) + + def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: + from cirq.sim import clifford + + # Note the computation complexity difference between _decompose_ and _act_on_. + # Suppose this Gate has `m` qubits, args has `n` qubits, and the decomposition of + # this operation into `k` operations: + # 1. Direct act_on is O(n^3) -- two matrices multiplication + # 2. Decomposition is O(m^3)+O(k*n^2) -- Decomposition complexity + k * One/two-qubits Ops + # So when m << n, the decomposition is more efficient. + if isinstance(args, clifford.ActOnCliffordTableauArgs): + axes = args.get_axes(qubits) + # This padding is important and cannot be omitted. + padded_tableau = _pad_tableau(self._clifford_tableau, len(args.qubits), axes) + args.tableau = args.tableau.then(padded_tableau) + return True + + if isinstance(args, clifford.ActOnStabilizerCHFormArgs): + # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? + return NotImplemented + + return NotImplemented diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 6e717558bb1..1f01db0d209 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -415,6 +415,10 @@ def test_commutes_notimplemented_type(): cirq.commutes(cirq.SingleQubitCliffordGate.X, 'X') assert cirq.commutes(cirq.SingleQubitCliffordGate.X, 'X', default='default') == 'default' + with pytest.raises(TypeError): + cirq.commutes(cirq.CliffordGate.X, 'X') + assert cirq.commutes(cirq.CliffordGate.X, 'X', default='default') == 'default' + @pytest.mark.parametrize( 'gate,other', itertools.product(_all_clifford_gates(), _all_clifford_gates()) @@ -586,29 +590,109 @@ def test_from_xz_to_clifford_tableau(): assert len(set(seen_tableau)) == 24 -def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': +######################## Start the tests for multi-qubits clifford gate ######################## +@pytest.mark.parametrize( + 'clifford_gate,standard_gate', + [ + (cirq.CliffordGate.X, cirq.X), + (cirq.CliffordGate.Y, cirq.Y), + (cirq.CliffordGate.Z, cirq.Z), + (cirq.CliffordGate.H, cirq.H), + (cirq.CliffordGate.S, cirq.S), + (cirq.CliffordGate.CNOT, cirq.CNOT), + (cirq.CliffordGate.CZ, cirq.CZ), + (cirq.CliffordGate.SWAP, cirq.SWAP), + ], +) +def test_common_clifford_gate(clifford_gate, standard_gate): + # cirq.unitary is relied on the _decompose_ methods. + u_c = cirq.unitary(clifford_gate) + u_s = cirq.unitary(standard_gate) + cirq.testing.assert_allclose_up_to_global_phase(u_c, u_s, atol=1e-8) + + +def test_multi_qubit_clifford_pow(): + assert cirq.CliffordGate.X ** -1 == cirq.CliffordGate.X + assert cirq.CliffordGate.H ** -1 == cirq.CliffordGate.H + assert cirq.CliffordGate.S ** 2 == cirq.CliffordGate.Z + assert cirq.CliffordGate.S ** -1 == cirq.CliffordGate.S ** 3 + assert cirq.CliffordGate.S ** -3 == cirq.CliffordGate.S + assert cirq.CliffordGate.CNOT ** 3 == cirq.CliffordGate.CNOT + assert cirq.CliffordGate.CNOT ** -3 == cirq.CliffordGate.CNOT + with pytest.raises(TypeError): + _ = cirq.SingleQubitCliffordGate.Z ** 0.25 - t = cirq.CliffordTableau(num_qubits=num_qubits) - args = sim.ActOnCliffordTableauArgs( - tableau=t, prng=np.random.RandomState(), log_of_measurement_results={} - ) - cirq.act_on(gate, args, allow_decompose=False) - return CliffordGate.from_clifford_tableau(args.tableau) - -def test_common_clifford_gate(): +def test_clifford_gate_from_op_list(): + # Since from_op_list() ==> _act_on_() ==> tableau.then() and then() has already covered + # lots of random circuit cases, here we just test a few well-known relationships. + qubit = cirq.NamedQubit('test') + gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Z(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + + gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + + gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Y(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Z # The tableau ignores the global phase + + gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + + # Two qubits gates qubits = cirq.LineQubit.range(2) - print('\n', cirq.unitary(cirq.CliffordGate.X(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.X(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.Y(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.Z(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.CNOT(*qubits))) - # print('\n', cirq.unitary(cirq.CliffordGate.CZ(*qubits))) - - print('\n', cirq.unitary(cirq.Y(qubits[0]))) - - print('\n', cirq.decompose_once(cirq.CliffordGate.CZ(*qubits))) - - clifford_gate = cirq.CliffordGate.CNOT(*qubits) - -test_common_clifford_gate() \ No newline at end of file + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits + ) + assert gate == cirq.CliffordGate.CNOT + + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[1]), cirq.CNOT(*qubits), cirq.H(qubits[1])], qubits + ) + assert gate == cirq.CliffordGate.CZ + + # Note the order of qubits matters + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits + ) + assert gate != cirq.CliffordGate.CNOT + # But if we reverse the qubit_order again, it will equal again. + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits[::-1] + ) + assert gate == cirq.CliffordGate.CNOT + + +def test_multi_clifford_decompose_by_unitary(): + # Construct a random clifford gate: + n, num_ops = 5, 20 # because we relied on unitary cannot test large-scale qubits + gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] + for seed in range(100): + prng = np.random.RandomState(seed) + qubits = cirq.LineQubit.range(n) + ops = [] + for _ in range(num_ops): + g = prng.randint(len(gate_candidate)) + indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) + ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) + gate = cirq.CliffordGate.from_op_list(ops, qubits) + decomposed_ops = cirq.decompose(gate.on(*qubits)) + circ = cirq.Circuit(decomposed_ops) + circ.append(cirq.I.on_each(qubits)) # make sure the dimension aligned. + cirq.testing.assert_allclose_up_to_global_phase( + cirq.unitary(gate), cirq.unitary(circ), atol=1e-7 + ) + + +def test_pad_clifford_gate_bad_input(): + with pytest.raises(ValueError): + tableau = cirq.CliffordTableau(num_qubits=3) + cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=4, qubits=cirq.LineQubit.range(2) + ) + + with pytest.raises(ValueError): + tableau = cirq.CliffordTableau(num_qubits=3) + cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=2, qubits=cirq.LineQubit.range(3) + ) From 6701aaf0ee2d054ec819c4c80fac0a53c57d0a8c Mon Sep 17 00:00:00 2001 From: ybc Date: Sat, 1 Jan 2022 21:36:13 -0800 Subject: [PATCH 05/21] Add more functionality to clifford gate --- cirq-core/cirq/ops/clifford_gate.py | 243 ++++++++++++----------- cirq-core/cirq/ops/clifford_gate_test.py | 130 +++++++++--- 2 files changed, 239 insertions(+), 134 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index a716796ab1f..32691300348 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -12,7 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, cast, Dict, NamedTuple, Optional, Sequence, Tuple, TYPE_CHECKING, Union +from typing import ( + Any, + cast, + Dict, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + TYPE_CHECKING, + Union, +) import numpy as np @@ -114,7 +125,6 @@ def _validate_map_input( return {frm: PauliTransform(to, flip) for frm, (to, flip) in pauli_map_to.items()} -# TODO Make SingleQubitCliffordGate as derived class of CliffordGate since it contains more functions. @value.value_equality class SingleQubitCliffordGate(gate_features.SingleQubitGate): """Any single qubit Clifford rotation.""" @@ -611,7 +621,7 @@ def H(cls): @property def S(cls): if getattr(cls, '_S', None) is None: - cls._S = cls._generate_clifford_from_known_gate(1, pauli_gates.S) + cls._S = cls._generate_clifford_from_known_gate(1, common_gates.S) return cls._S @property @@ -626,13 +636,21 @@ def CZ(cls): cls._CZ = cls._generate_clifford_from_known_gate(2, common_gates.CZ) return cls._CZ + @property + def SWAP(cls): + if getattr(cls, '_SWAP', None) is None: + cls._SWAP = cls._generate_clifford_from_known_gate(2, common_gates.SWAP) + return cls._SWAP + class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): # We need to use the lazy initialization of these common gates since they need to use # cirq.sim, which can not be imported when @classmethod - def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': + def _generate_clifford_from_known_gate( + cls, num_qubits: int, gate: raw_types.Gate + ) -> 'CliffordGate': from cirq import LineQubit, sim qubits = LineQubit.range(num_qubits) @@ -645,6 +663,30 @@ def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': return CliffordGate.from_clifford_tableau(args.tableau) +def _pad_tableau( + clifford_tableau: qis.CliffordTableau, num_qubits_after_padding: int, axes: List[int] +) -> qis.CliffordTableau: + """Roughly, this function copies self.tabluea into the "identity" matrix.""" + # Sanity check + if len(set(axes)) != clifford_tableau.n: + raise ValueError( + "Input axes of padding should match with the number of qubits in the input tableau." + ) + if clifford_tableau.n > num_qubits_after_padding: + raise ValueError( + "The number of qubits in the input tableau should not be larger than " + "num_qubits_after_padding." + ) + + padded_tableau = qis.CliffordTableau(num_qubits_after_padding) + v_index = np.concatenate((np.asarray(axes), num_qubits_after_padding + np.asarray(axes))) + + padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs.copy() + padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs.copy() + padded_tableau.rs[v_index] = clifford_tableau.rs.copy() + return padded_tableau + + @value.value_equality class CliffordGate(raw_types.Gate, CommonCliffordGates): """Clifford rotation for N-qubit.""" @@ -656,38 +698,31 @@ def __init__( ) -> None: if not _clifford_tableau._validate(): raise ValueError('Input is not a valid Clifford tableau.') - self._clifford_tableau = _clifford_tableau + # We use the Clifford tableau to represent a Clifford gate. + # It is crucial to note that the meaning of tableau here is different + # from the one used to represent a Clifford state (Of course, they are related). + # A) We have to use the full 2n * (2n + 1) matrix + # B) The meaning of tableau here is + # X Z sign + # from X [ X_x Z_x | r_x ] + # from Z [ X_z Z_z | r_z ] + # Each row in the Clifford tableau means the transformation of original Pauli gates. + # For example, take a 2 * (2+1) tableau as example: + # X Z r + # XI [ 1 0 | 1 0 | 0 ] + # IX [ 0 0 | 1 1 | 0 ] + # ZI [ 0 0 | 1 0 | 1 ] + # IZ [ 1 0 | 1 1 | 0 ] + # Take the third row as example: this means the ZI gate after the this gate, + # more precisely the conjugate transformation of ZI by this gate, becomes -ZI. + # (Note the real clifford tableau has to satify the Symplectic property. + # here is just for illustration) + self._clifford_tableau = _clifford_tableau.copy() @property def clifford_tableau(self): return self._clifford_tableau - def _swap_qubit_order(self, i: int, j: int): - """The qubit order of Clifford Tableau is important for the tablue. - If we want to swap the application on qubit i and j, we need to - - X Z sign - from X [ X_x Z_x | r_x ] - from Z [ X_z Z_z | r_z ] - """ - assert i >= 0 and i < self.clifford_tableau.n - assert j >= 0 and j < self.clifford_tableau.n - assert i != j - n = self.clifford_tableau.n - # Swap the columns - self._clifford_tableau.xs[:, [i, j]] = self._clifford_tableau.xs[:, [j, i]] - self._clifford_tableau.zs[:, [i, j]] = self._clifford_tableau.zs[:, [j, i]] - self._clifford_tableau.xs[:, [n + i, n + j]] = self._clifford_tableau.xs[:, [n + j, n + i]] - self._clifford_tableau.zs[:, [n + i, n + j]] = self._clifford_tableau.zs[:, [n + j, n + i]] - - # Swap the rows - self._clifford_tableau.xs[[i, j], :] = self._clifford_tableau.xs[[j, i], :] - self._clifford_tableau.zs[[i, j], :] = self._clifford_tableau.zs[[j, i], :] - self._clifford_tableau.xs[[n + i, n + j], :] = self._clifford_tableau.xs[[n + j, n + i], :] - self._clifford_tableau.zs[[n + i, n + j], :] = self._clifford_tableau.zs[[n + j, n + i], :] - self._clifford_tableau.rs[[i, j]] = self._clifford_tableau.rs[[j, i]] - self._clifford_tableau.rs[[n + i, n + j]] = self._clifford_tableau.rs[[n + j, n + i]] - @classmethod def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': assert isinstance(tableau, qis.CliffordTableau) @@ -697,92 +732,36 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': @classmethod def from_op_list( - cls, - *, - num_qubits, - ): - """ - Take a list like - h 0 - cnot 3, 4 - cz 1, 0 - x 2 - return the complied Clifford gates - """ - pass - - def _value_equality_values_(self): - return self.clifford_tableau - - def _num_qubits_(self): - return self.clifford_tableau.n - - def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': - from cirq.optimizers import clifford_decomposition - - return clifford_decomposition.decompose_clifford_tableau_to_operations( - list(qubits), self.clifford_tableau - ) - - def __repr__(self) -> str: - return f"Clifford Gate with Tableau {self.clifford_tableau.__repr__()}" - - def _unitary_(self) -> np.ndarray: - # This is not efficient. - state = np.eye(2 ** self.clifford_tableau.n, dtype=np.complex128) - buffer = np.empty_like(state) - qubits = tuple( - [named_qubit.NamedQubit(f'arbitrary_{i}') for i in range(self._num_qubits_())] - ) - return protocols.apply_unitaries( - self._decompose_(qubits), qubits, - protocols.ApplyUnitaryArgs(state, buffer, range(self.clifford_tableau.n))) - - def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: - # Note here we assume two CLifford define the tabluea based on the same qubit order! - if not isinstance(other, CliffordGate): - return NotImplemented - return self.clifford_tableau.then(other.clifford_tableau) == other.clifford_tableau.then( - self.clifford_tableau - ) - - # def merged_with(self, second: 'CliffordGate') -> 'CliffordGate': - # """Returns a CliffordGate such that the circuits - # --output-- and --self--second-- - # are equivalent up to global phase. - - # # !!!!! We need to care about the order of Qubit!!!! - # # Here we assume two Clifford Gates are defined under same order! - # # For exaample, CliffordGate.CZ.merged_with(CliffordGate.CNOT) are equivalent to - # # CNOT(0, 1) followed by CZ(0, 1). - # # Therefore this function signature is not sufficient! - # """ - - # return CliffordGate.from_clifford_tableau( - # self.clifford_tableau.then(second.clifford_tableau) - # ) - - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: + cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] + ) -> 'CliffordGate': + """Construct a new Clifford gates from several known operations.""" from cirq.sim import clifford - # Note there are two tricky things here - # 1. The clifford tableau under the ActOnArgs can be larger than self.clifford_tableau - # 2. The application order. self should be the second one. + for op in operations: + if op.gate and op.gate._has_stabilizer_effect_: + continue + raise ValueError( + "Clifford Gate can only be constructed from the " + "operations that has stabilizer effect." + ) - # Here we assume the argument `qubits` provided the order of the self.clifford_tableau. - # Idea is we extract the corresponding partial tableau out of ActOnArgs, next we apply the - # clifford_tableau.then() method with the self.clifford_tableau, last inject this - # new output clifford tableau back ot the ActOnArgs. - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): # Do we need it???? - return NotImplemented - # TODO(ybc) + base_tableau = qis.CliffordTableau(len(qubit_order)) + args = clifford.ActOnCliffordTableauArgs( + tableau=base_tableau, + qubits=qubit_order, + prng=np.random.RandomState(0), # unused + log_of_measurement_results={}, # unused + ) + for op in operations: + protocols.act_on(op, args, allow_decompose=True) - pass + return CliffordGate.from_clifford_tableau(args.tableau) - # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? + def _value_equality_values_(self): + return self.clifford_tableau - return NotImplemented + def _num_qubits_(self): + return self.clifford_tableau.n def __pow__(self, exponent) -> 'CliffordGate': if exponent == -1: @@ -799,3 +778,45 @@ def __pow__(self, exponent) -> 'CliffordGate': return CliffordGate.from_clifford_tableau(base_tableau.inverse()) return NotImplemented + + def __repr__(self) -> str: + return f"Clifford Gate with Tableau:\n {self.clifford_tableau._str_full_()}" + + def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: + # Note even if we assume two gates define the tabluea based on the same qubit order, + # the following approach cannot judge it: + # self.clifford_tableau.then(other.clifford_tableau) == other.clifford_tableau.then( + # self.clifford_tableau + # ) + # For example: X.then(Z) and Z.then(X) both return same tableau + # it is because Clifford tableau ignores the global phase information. + return NotImplemented + + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation]: + from cirq.optimizers import clifford_decomposition + + return clifford_decomposition.decompose_clifford_tableau_to_operations( + list(qubits), self.clifford_tableau + ) + + def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: + from cirq.sim import clifford + + # Note the computation complexity difference between _decompose_ and _act_on_. + # Suppose this Gate has `m` qubits, args has `n` qubits, and the decomposition of + # this operation into `k` operations: + # 1. Direct act_on is O(n^3) -- two matrices multiplication + # 2. Decomposition is O(m^3)+O(k*n^2) -- Decomposition complexity + k * One/two-qubits Ops + # So when m << n, the decomposition is more efficient. + if isinstance(args, clifford.ActOnCliffordTableauArgs): + axes = args.get_axes(qubits) + # This padding is important and cannot be omitted. + padded_tableau = _pad_tableau(self._clifford_tableau, len(args.qubits), axes) + args.tableau = args.tableau.then(padded_tableau) + return True + + if isinstance(args, clifford.ActOnStabilizerCHFormArgs): + # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? + return NotImplemented + + return NotImplemented diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 6e717558bb1..86e6419ad57 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -415,6 +415,10 @@ def test_commutes_notimplemented_type(): cirq.commutes(cirq.SingleQubitCliffordGate.X, 'X') assert cirq.commutes(cirq.SingleQubitCliffordGate.X, 'X', default='default') == 'default' + with pytest.raises(TypeError): + cirq.commutes(cirq.CliffordGate.X, 'X') + assert cirq.commutes(cirq.CliffordGate.X, 'X', default='default') == 'default' + @pytest.mark.parametrize( 'gate,other', itertools.product(_all_clifford_gates(), _all_clifford_gates()) @@ -586,29 +590,109 @@ def test_from_xz_to_clifford_tableau(): assert len(set(seen_tableau)) == 24 -def _generate_clifford_from_known_gate(cls, num_qubits, gate) -> 'CliffordGate': +######################## Start the tests for multi-qubits clifford gate ######################## +@pytest.mark.parametrize( + 'clifford_gate,standard_gate', + [ + (cirq.CliffordGate.X, cirq.X), + (cirq.CliffordGate.Y, cirq.Y), + (cirq.CliffordGate.Z, cirq.Z), + (cirq.CliffordGate.H, cirq.H), + (cirq.CliffordGate.S, cirq.S), + (cirq.CliffordGate.CNOT, cirq.CNOT), + (cirq.CliffordGate.CZ, cirq.CZ), + (cirq.CliffordGate.SWAP, cirq.SWAP), + ], +) +def test_common_clifford_gate(clifford_gate, standard_gate): + # cirq.unitary is relied on the _decompose_ methods. + u_c = cirq.unitary(clifford_gate) + u_s = cirq.unitary(standard_gate) + cirq.testing.assert_allclose_up_to_global_phase(u_c, u_s, atol=1e-8) + + +def test_multi_qubit_clifford_pow(): + assert cirq.CliffordGate.X ** -1 == cirq.CliffordGate.X + assert cirq.CliffordGate.H ** -1 == cirq.CliffordGate.H + assert cirq.CliffordGate.S ** 2 == cirq.CliffordGate.Z + assert cirq.CliffordGate.S ** -1 == cirq.CliffordGate.S ** 3 + assert cirq.CliffordGate.S ** -3 == cirq.CliffordGate.S + assert cirq.CliffordGate.CNOT ** 3 == cirq.CliffordGate.CNOT + assert cirq.CliffordGate.CNOT ** -3 == cirq.CliffordGate.CNOT + with pytest.raises(TypeError): + _ = cirq.SingleQubitCliffordGate.Z ** 0.25 - t = cirq.CliffordTableau(num_qubits=num_qubits) - args = sim.ActOnCliffordTableauArgs( - tableau=t, prng=np.random.RandomState(), log_of_measurement_results={} - ) - cirq.act_on(gate, args, allow_decompose=False) - return CliffordGate.from_clifford_tableau(args.tableau) - -def test_common_clifford_gate(): +def test_clifford_gate_from_op_list(): + # Since from_op_list() ==> _act_on_() ==> tableau.then() and then() has already covered + # lots of random circuit cases, here we just test a few well-known relationships. + qubit = cirq.NamedQubit('test') + gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Z(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + + gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + + gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Y(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Z # The tableau ignores the global phase + + gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + + # Two qubits gates qubits = cirq.LineQubit.range(2) - print('\n', cirq.unitary(cirq.CliffordGate.X(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.X(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.Y(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.Z(qubits[0]))) - print('\n', cirq.unitary(cirq.CliffordGate.CNOT(*qubits))) - # print('\n', cirq.unitary(cirq.CliffordGate.CZ(*qubits))) - - print('\n', cirq.unitary(cirq.Y(qubits[0]))) - - print('\n', cirq.decompose_once(cirq.CliffordGate.CZ(*qubits))) - - clifford_gate = cirq.CliffordGate.CNOT(*qubits) - -test_common_clifford_gate() \ No newline at end of file + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits + ) + assert gate == cirq.CliffordGate.CNOT + + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[1]), cirq.CNOT(*qubits), cirq.H(qubits[1])], qubits + ) + assert gate == cirq.CliffordGate.CZ + + # Note the order of qubits matters + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits + ) + assert gate != cirq.CliffordGate.CNOT + # But if we reverse the qubit_order, they will equal again. + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits[::-1] + ) + assert gate == cirq.CliffordGate.CNOT + + +def test_multi_clifford_decompose_by_unitary(): + # Construct a random clifford gate: + n, num_ops = 5, 20 # because we relied on unitary cannot test large-scale qubits + gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] + for seed in range(100): + prng = np.random.RandomState(seed) + qubits = cirq.LineQubit.range(n) + ops = [] + for _ in range(num_ops): + g = prng.randint(len(gate_candidate)) + indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) + ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) + gate = cirq.CliffordGate.from_op_list(ops, qubits) + decomposed_ops = cirq.decompose(gate.on(*qubits)) + circ = cirq.Circuit(decomposed_ops) + circ.append(cirq.I.on_each(qubits)) # make sure the dimension aligned. + cirq.testing.assert_allclose_up_to_global_phase( + cirq.unitary(gate), cirq.unitary(circ), atol=1e-7 + ) + + +def test_pad_clifford_gate_bad_input(): + with pytest.raises(ValueError): + tableau = cirq.CliffordTableau(num_qubits=3) + cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=4, qubits=cirq.LineQubit.range(2) + ) + + with pytest.raises(ValueError): + tableau = cirq.CliffordTableau(num_qubits=3) + cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=2, qubits=cirq.LineQubit.range(3) + ) From 35203c1884265e676f4cf462f5a7bbe59e6578bd Mon Sep 17 00:00:00 2001 From: ybc Date: Sat, 1 Jan 2022 22:35:26 -0800 Subject: [PATCH 06/21] Add json serialization and fix the test --- cirq-core/cirq/json_resolver_cache.py | 1 + cirq-core/cirq/ops/clifford_gate.py | 18 +++++++++ cirq-core/cirq/ops/clifford_gate_test.py | 29 ++++++++++---- .../json_test_data/CliffordGate.json | 38 +++++++++++++++++++ .../json_test_data/CliffordGate.repr | 1 + 5 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 cirq-core/cirq/protocols/json_test_data/CliffordGate.json create mode 100644 cirq-core/cirq/protocols/json_test_data/CliffordGate.repr diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 6a017320dad..830a31c9f45 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -64,6 +64,7 @@ def _parallel_gate_op(gate, qubits): 'Circuit': cirq.Circuit, 'CircuitOperation': cirq.CircuitOperation, 'ClassicallyControlledOperation': cirq.ClassicallyControlledOperation, + 'CliffordGate': cirq.CliffordGate, 'CliffordState': cirq.CliffordState, 'CliffordTableau': cirq.CliffordTableau, 'CNotPowGate': cirq.CNotPowGate, diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 32691300348..30086cb3336 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -757,12 +757,30 @@ def from_op_list( return CliffordGate.from_clifford_tableau(args.tableau) + @classmethod + def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n, + rs, + xs, + zs, + ) + return cls(_clifford_tableau=_clifford_tableau) + + def _json_dict_(self) -> Dict[str, Any]: + json_dict = self._clifford_tableau._json_dict_() + return json_dict + def _value_equality_values_(self): return self.clifford_tableau def _num_qubits_(self): return self.clifford_tableau.n + def _has_stabilizer_effect_(self) -> Optional[bool]: + # By definition, Clifford Gate should always return True. + return True + def __pow__(self, exponent) -> 'CliffordGate': if exponent == -1: return CliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 86e6419ad57..40ffd98add2 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -685,14 +685,27 @@ def test_multi_clifford_decompose_by_unitary(): def test_pad_clifford_gate_bad_input(): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="Input axes of padding should match with the number of qubits" + ): tableau = cirq.CliffordTableau(num_qubits=3) - cirq.ops.clifford_gate._pad_tableau( - tableau, num_qubits_after_padding=4, qubits=cirq.LineQubit.range(2) - ) + cirq.ops.clifford_gate._pad_tableau(tableau, num_qubits_after_padding=4, axes=[1, 2]) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match='The number of qubits in the input tableau should not be larger than' + ): tableau = cirq.CliffordTableau(num_qubits=3) - cirq.ops.clifford_gate._pad_tableau( - tableau, num_qubits_after_padding=2, qubits=cirq.LineQubit.range(3) - ) + cirq.ops.clifford_gate._pad_tableau(tableau, num_qubits_after_padding=2, axes=[0, 1, 2]) + + +def test_stabilizer_effec(): + assert cirq.has_stabilizer_effect(cirq.CliffordGate.X) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.H) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.S) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.CNOT) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.CZ) + qubits = cirq.LineQubit.range(2) + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits + ) + assert cirq.has_stabilizer_effect(gate) diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.json b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json new file mode 100644 index 00000000000..edd56d62d86 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json @@ -0,0 +1,38 @@ +{ + "cirq_type": "CliffordGate", + "n": 2, + "rs": [ + false, + false, + false, + false + ], + "xs": [ + [ + true, false + ], + [ + false, true + ], + [ + false, false + ], + [ + false, false + ] + ], + "zs": [ + [ + false, false + ], + [ + false, false + ], + [ + true, false + ], + [ + false, true + ] + ] + } \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr b/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr new file mode 100644 index 00000000000..dd7ce8c3a06 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr @@ -0,0 +1 @@ +cirq.CliffordGate(_clifford_tableau=cirq.CliffordTableau(num_qubits=2)) From 2fcbb13c25b0efc66eb7aefb72b6ba1ce00bc693 Mon Sep 17 00:00:00 2001 From: ybc Date: Sat, 1 Jan 2022 23:57:20 -0800 Subject: [PATCH 07/21] Add more tests for coverage --- cirq-core/cirq/ops/clifford_gate.py | 4 +- cirq-core/cirq/ops/clifford_gate_test.py | 177 +++++++++++++++++++++-- 2 files changed, 166 insertions(+), 15 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 30086cb3336..ee4f2d940d5 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -696,8 +696,6 @@ def __init__( *, _clifford_tableau: qis.CliffordTableau, ) -> None: - if not _clifford_tableau._validate(): - raise ValueError('Input is not a valid Clifford tableau.') # We use the Clifford tableau to represent a Clifford gate. # It is crucial to note that the meaning of tableau here is different # from the one used to represent a Clifford state (Of course, they are related). @@ -738,7 +736,7 @@ def from_op_list( from cirq.sim import clifford for op in operations: - if op.gate and op.gate._has_stabilizer_effect_: + if op.gate and op.gate._has_stabilizer_effect_(): continue raise ValueError( "Clifford Gate can only be constructed from the " diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 40ffd98add2..e529276a082 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -620,7 +620,20 @@ def test_multi_qubit_clifford_pow(): assert cirq.CliffordGate.CNOT ** 3 == cirq.CliffordGate.CNOT assert cirq.CliffordGate.CNOT ** -3 == cirq.CliffordGate.CNOT with pytest.raises(TypeError): - _ = cirq.SingleQubitCliffordGate.Z ** 0.25 + _ = cirq.CliffordGate.Z ** 0.25 + + +def test_stabilizer_effec(): + assert cirq.has_stabilizer_effect(cirq.CliffordGate.X) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.H) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.S) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.CNOT) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.CZ) + qubits = cirq.LineQubit.range(2) + gate = cirq.CliffordGate.from_op_list( + [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits + ) + assert cirq.has_stabilizer_effect(gate) def test_clifford_gate_from_op_list(): @@ -662,6 +675,28 @@ def test_clifford_gate_from_op_list(): ) assert gate == cirq.CliffordGate.CNOT + with pytest.raises( + ValueError, match="only be constructed from the operations that has stabilizer effect" + ): + cirq.CliffordGate.from_op_list([cirq.T(qubit)], [qubit]) + + +def test_clifford_gate_from_tableau(): + t = cirq.CliffordGate.X.clifford_tableau + assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.X + + t = cirq.CliffordGate.H.clifford_tableau + assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.H + + t = cirq.CliffordGate.CNOT.clifford_tableau + assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.CNOT + + with pytest.raises(ValueError): + t = cirq.CliffordTableau(num_qubits=1) + t.xs = np.array([1, 1]).reshape(2, 1) + t.zs = np.array([1, 1]).reshape(2, 1) # This violates the sympletic property. + cirq.CliffordGate.from_clifford_tableau(t) + def test_multi_clifford_decompose_by_unitary(): # Construct a random clifford gate: @@ -684,7 +719,7 @@ def test_multi_clifford_decompose_by_unitary(): ) -def test_pad_clifford_gate_bad_input(): +def test_pad_tableau_bad_input(): with pytest.raises( ValueError, match="Input axes of padding should match with the number of qubits" ): @@ -698,14 +733,132 @@ def test_pad_clifford_gate_bad_input(): cirq.ops.clifford_gate._pad_tableau(tableau, num_qubits_after_padding=2, axes=[0, 1, 2]) -def test_stabilizer_effec(): - assert cirq.has_stabilizer_effect(cirq.CliffordGate.X) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.H) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.S) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.CNOT) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.CZ) - qubits = cirq.LineQubit.range(2) - gate = cirq.CliffordGate.from_op_list( - [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits +def test_pad_tableau(): + tableau = cirq.CliffordTableau(num_qubits=1) + padded_tableau = cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=2, axes=[0] ) - assert cirq.has_stabilizer_effect(gate) + assert padded_tableau == cirq.CliffordTableau(num_qubits=2) + + tableau = cirq.CliffordTableau(num_qubits=1, initial_state=1) + padded_tableau = cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=1, axes=[0] + ) + assert padded_tableau == cirq.CliffordGate.X.clifford_tableau + + # Tableau for H + # [0 1 0] + # [1 0 0] + tableau = cirq.CliffordGate.H.clifford_tableau + padded_tableau = cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=2, axes=[0] + ) + np.testing.assert_equal( + padded_tableau.matrix().astype(np.int64), + np.array( + [ + [0, 0, 1, 0], + [0, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 1], + ] + ), + ) + np.testing.assert_equal(padded_tableau.rs.astype(np.int64), np.zeros(4)) + # The tableau of H again but pad for another ax + tableau = cirq.CliffordGate.H.clifford_tableau + padded_tableau = cirq.ops.clifford_gate._pad_tableau( + tableau, num_qubits_after_padding=2, axes=[1] + ) + np.testing.assert_equal( + padded_tableau.matrix().astype(np.int64), + np.array( + [ + [1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [0, 1, 0, 0], + ] + ), + ) + np.testing.assert_equal(padded_tableau.rs.astype(np.int64), np.zeros(4)) + + +def test_clifford_gate_act_on_small_case(): + # Note this is also covered by the `from_op_list` one, etc. + + n, num_ops = 5, 20 + qubits = cirq.LineQubit.range(n) + args = cirq.ActOnCliffordTableauArgs( + tableau=cirq.CliffordTableau(num_qubits=n), + qubits=qubits, + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + expected_args = cirq.ActOnCliffordTableauArgs( + tableau=cirq.CliffordTableau(num_qubits=n), + qubits=qubits, + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) + assert args.tableau == expected_args.tableau + + cirq.act_on(cirq.CNOT, expected_args, qubits=[qubits[0], qubits[1]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) + assert args.tableau == expected_args.tableau + + cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) + assert args.tableau == expected_args.tableau + + cirq.act_on(cirq.S, expected_args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.S, args, qubits=[qubits[0]], allow_decompose=False) + assert args.tableau == expected_args.tableau + + cirq.act_on(cirq.X, expected_args, qubits=[qubits[2]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.X, args, qubits=[qubits[2]], allow_decompose=False) + assert args.tableau == expected_args.tableau + + +def test_clifford_gate_act_on_large_case(): + n, num_ops = 50, 1000 # because we don't need unitary, it is fast. + gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] + for seed in range(10): + prng = np.random.RandomState(seed) + t1 = cirq.CliffordTableau(num_qubits=n) + t2 = cirq.CliffordTableau(num_qubits=n) + qubits = cirq.LineQubit.range(n) + args1 = cirq.ActOnCliffordTableauArgs( + tableau=t1, qubits=qubits, prng=prng, log_of_measurement_results={} + ) + args2 = cirq.ActOnCliffordTableauArgs( + tableau=t2, qubits=qubits, prng=prng, log_of_measurement_results={} + ) + ops = [] + for _ in range(num_ops): + g = prng.randint(len(gate_candidate)) + indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) + cirq.act_on( + gate_candidate[g], args1, qubits=[qubits[i] for i in indices], allow_decompose=False + ) + ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) + compiled_gate = cirq.CliffordGate.from_op_list(ops, qubits) + cirq.act_on(compiled_gate, args2, qubits) + + assert args1.tableau == args2.tableau + + +def test_clifford_gate_act_on_ch_form(): + # Although we don't support CH_form from the _act_on_, it will fall back + # to the decomposititon method and apply it through decomposed ops. + # Here we run it for the coverage only. + args = cirq.ActOnStabilizerCHFormArgs( + state=cirq.StabilizerStateChForm(num_qubits=2, initial_state=1), + qubits=cirq.LineQubit.range(2), + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(cirq.CliffordGate.X, args, qubits=cirq.LineQubit.range(1)) + np.testing.assert_allclose(args.state.state_vector(), np.array([0, 0, 0, 1])) From 72495cbee9c112f4a03e12018d50115df439712f Mon Sep 17 00:00:00 2001 From: ybc Date: Sun, 2 Jan 2022 00:03:28 -0800 Subject: [PATCH 08/21] fix the lint --- cirq-core/cirq/ops/clifford_gate_test.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index e529276a082..63ce7f764c7 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -787,16 +787,15 @@ def test_pad_tableau(): def test_clifford_gate_act_on_small_case(): # Note this is also covered by the `from_op_list` one, etc. - n, num_ops = 5, 20 - qubits = cirq.LineQubit.range(n) + qubits = cirq.LineQubit.range(5) args = cirq.ActOnCliffordTableauArgs( - tableau=cirq.CliffordTableau(num_qubits=n), + tableau=cirq.CliffordTableau(num_qubits=5), qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}, ) expected_args = cirq.ActOnCliffordTableauArgs( - tableau=cirq.CliffordTableau(num_qubits=n), + tableau=cirq.CliffordTableau(num_qubits=5), qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}, From 95dfc7d8cf917eb88e58a8f24ff7a80292eb38d9 Mon Sep 17 00:00:00 2001 From: ybc Date: Sun, 2 Jan 2022 00:37:17 -0800 Subject: [PATCH 09/21] Fix the coverage of act_on fail --- cirq-core/cirq/ops/clifford_gate.py | 2 ++ cirq-core/cirq/ops/clifford_gate_test.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index ee4f2d940d5..dd8119f5464 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -833,6 +833,8 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool if isinstance(args, clifford.ActOnStabilizerCHFormArgs): # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? + # It should be unlike because CliffordTableau ignores the global phase but CHForm + # is aimed to fix that. return NotImplemented return NotImplemented diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 63ce7f764c7..376cbe2a259 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -19,6 +19,7 @@ import pytest import cirq +from cirq.protocols.act_on_protocol_test import DummyActOnArgs from cirq.testing import ( EqualsTester, assert_allclose_up_to_global_phase, @@ -861,3 +862,8 @@ def test_clifford_gate_act_on_ch_form(): ) cirq.act_on(cirq.CliffordGate.X, args, qubits=cirq.LineQubit.range(1)) np.testing.assert_allclose(args.state.state_vector(), np.array([0, 0, 0, 1])) + + +def test_clifford_gate_act_on_fail(): + with pytest.raises(TypeError, match="Failed to act"): + cirq.act_on(cirq.X, DummyActOnArgs(), qubits=()) From ac5659af18ab1e64c8de3355d23dfbe34277fbde Mon Sep 17 00:00:00 2001 From: ybc Date: Sun, 2 Jan 2022 01:09:07 -0800 Subject: [PATCH 10/21] Update clifford_gate_test.py --- cirq-core/cirq/ops/clifford_gate_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 376cbe2a259..3ab6973a2a1 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -866,4 +866,4 @@ 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.X, DummyActOnArgs(), qubits=()) + cirq.act_on(cirq.CliffordGate.X, DummyActOnArgs(), qubits=()) From 5f3a62a6962fa00407bb985ec0a946c97de92c21 Mon Sep 17 00:00:00 2001 From: ybc Date: Tue, 4 Jan 2022 18:59:19 -0800 Subject: [PATCH 11/21] Update decompose_clifford_tableau_to_operations into new path --- cirq-core/cirq/ops/clifford_gate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index dd8119f5464..569a99c94cc 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -809,9 +809,9 @@ def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, return NotImplemented def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation]: - from cirq.optimizers import clifford_decomposition + from cirq.transformers import analytical_decompositions - return clifford_decomposition.decompose_clifford_tableau_to_operations( + return analytical_decompositions.decompose_clifford_tableau_to_operations( list(qubits), self.clifford_tableau ) From e6c1c63aff3d9cf20c38d49de669ff6e53112b4e Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 15 Feb 2022 21:03:17 -0800 Subject: [PATCH 12/21] Rename the CliffordGate to MultipleQubitCliffordGate --- cirq-core/cirq/__init__.py | 2 +- cirq-core/cirq/json_resolver_cache.py | 2 +- cirq-core/cirq/ops/__init__.py | 2 +- cirq-core/cirq/ops/clifford_gate.py | 22 ++-- cirq-core/cirq/ops/clifford_gate_test.py | 121 +++++++++--------- .../json_test_data/CliffordGate.json | 2 +- .../json_test_data/CliffordGate.repr | 2 +- 7 files changed, 76 insertions(+), 77 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index bb4421c1d5c..80cc552385a 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -189,7 +189,6 @@ CCXPowGate, CCZ, CCZPowGate, - CliffordGate, CCNOT, CCNotPowGate, ClassicallyControlledOperation, @@ -244,6 +243,7 @@ measure_single_paulistring, MeasurementGate, Moment, + MultipleCliffordGate, MutableDensePauliString, MutablePauliString, NamedQubit, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 830a31c9f45..e5e859339d5 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -64,7 +64,6 @@ def _parallel_gate_op(gate, qubits): 'Circuit': cirq.Circuit, 'CircuitOperation': cirq.CircuitOperation, 'ClassicallyControlledOperation': cirq.ClassicallyControlledOperation, - 'CliffordGate': cirq.CliffordGate, 'CliffordState': cirq.CliffordState, 'CliffordTableau': cirq.CliffordTableau, 'CNotPowGate': cirq.CNotPowGate, @@ -107,6 +106,7 @@ def _parallel_gate_op(gate, qubits): 'MeasurementGate': cirq.MeasurementGate, '_MeasurementSpec': cirq.work._MeasurementSpec, 'Moment': cirq.Moment, + 'MultipleCliffordGate': cirq.MultipleCliffordGate, 'MutableDensePauliString': cirq.MutableDensePauliString, 'MutablePauliString': cirq.MutablePauliString, '_NoNoiseModel': _NoNoiseModel, diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index a0c4f86c588..0d874374545 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -19,7 +19,7 @@ ) from cirq.ops.clifford_gate import ( - CliffordGate, + MultipleCliffordGate, PauliTransform, SingleQubitCliffordGate, ) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 569a99c94cc..33bc21ec4c7 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -650,7 +650,7 @@ class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): @classmethod def _generate_clifford_from_known_gate( cls, num_qubits: int, gate: raw_types.Gate - ) -> 'CliffordGate': + ) -> 'MultipleCliffordGate': from cirq import LineQubit, sim qubits = LineQubit.range(num_qubits) @@ -660,7 +660,7 @@ def _generate_clifford_from_known_gate( ) protocols.act_on(gate, args, qubits, allow_decompose=False) - return CliffordGate.from_clifford_tableau(args.tableau) + return MultipleCliffordGate.from_clifford_tableau(args.tableau) def _pad_tableau( @@ -688,7 +688,7 @@ def _pad_tableau( @value.value_equality -class CliffordGate(raw_types.Gate, CommonCliffordGates): +class MultipleCliffordGate(raw_types.Gate, CommonCliffordGates): """Clifford rotation for N-qubit.""" def __init__( @@ -722,16 +722,16 @@ def clifford_tableau(self): return self._clifford_tableau @classmethod - def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': + def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffordGate': assert isinstance(tableau, qis.CliffordTableau) if not tableau._validate(): raise ValueError('It is not a valid Clifford tableau.') - return CliffordGate(_clifford_tableau=tableau) + return MultipleCliffordGate(_clifford_tableau=tableau) @classmethod def from_op_list( cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] - ) -> 'CliffordGate': + ) -> 'MultipleCliffordGate': """Construct a new Clifford gates from several known operations.""" from cirq.sim import clifford @@ -753,7 +753,7 @@ def from_op_list( for op in operations: protocols.act_on(op, args, allow_decompose=True) - return CliffordGate.from_clifford_tableau(args.tableau) + return MultipleCliffordGate.from_clifford_tableau(args.tableau) @classmethod def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): @@ -779,19 +779,19 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: # By definition, Clifford Gate should always return True. return True - def __pow__(self, exponent) -> 'CliffordGate': + def __pow__(self, exponent) -> 'MultipleCliffordGate': if exponent == -1: - return CliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) + return MultipleCliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) if exponent > 0 and int(exponent) == exponent: base_tableau = self.clifford_tableau.copy() for _ in range(int(exponent) - 1): base_tableau = base_tableau.then(self.clifford_tableau) - return CliffordGate.from_clifford_tableau(base_tableau) + return MultipleCliffordGate.from_clifford_tableau(base_tableau) if exponent < 0 and int(exponent) == exponent: base_tableau = self.clifford_tableau.copy() for _ in range(int(-exponent) - 1): base_tableau = base_tableau.then(self.clifford_tableau) - return CliffordGate.from_clifford_tableau(base_tableau.inverse()) + return MultipleCliffordGate.from_clifford_tableau(base_tableau.inverse()) return NotImplemented diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 3ab6973a2a1..6563cdce3f0 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -417,8 +417,8 @@ def test_commutes_notimplemented_type(): assert cirq.commutes(cirq.SingleQubitCliffordGate.X, 'X', default='default') == 'default' with pytest.raises(TypeError): - cirq.commutes(cirq.CliffordGate.X, 'X') - assert cirq.commutes(cirq.CliffordGate.X, 'X', default='default') == 'default' + cirq.commutes(cirq.MultipleCliffordGate.X, 'X') + assert cirq.commutes(cirq.MultipleCliffordGate.X, 'X', default='default') == 'default' @pytest.mark.parametrize( @@ -591,18 +591,17 @@ def test_from_xz_to_clifford_tableau(): assert len(set(seen_tableau)) == 24 -######################## Start the tests for multi-qubits clifford gate ######################## @pytest.mark.parametrize( 'clifford_gate,standard_gate', [ - (cirq.CliffordGate.X, cirq.X), - (cirq.CliffordGate.Y, cirq.Y), - (cirq.CliffordGate.Z, cirq.Z), - (cirq.CliffordGate.H, cirq.H), - (cirq.CliffordGate.S, cirq.S), - (cirq.CliffordGate.CNOT, cirq.CNOT), - (cirq.CliffordGate.CZ, cirq.CZ), - (cirq.CliffordGate.SWAP, cirq.SWAP), + (cirq.MultipleCliffordGate.X, cirq.X), + (cirq.MultipleCliffordGate.Y, cirq.Y), + (cirq.MultipleCliffordGate.Z, cirq.Z), + (cirq.MultipleCliffordGate.H, cirq.H), + (cirq.MultipleCliffordGate.S, cirq.S), + (cirq.MultipleCliffordGate.CNOT, cirq.CNOT), + (cirq.MultipleCliffordGate.CZ, cirq.CZ), + (cirq.MultipleCliffordGate.SWAP, cirq.SWAP), ], ) def test_common_clifford_gate(clifford_gate, standard_gate): @@ -613,25 +612,25 @@ def test_common_clifford_gate(clifford_gate, standard_gate): def test_multi_qubit_clifford_pow(): - assert cirq.CliffordGate.X ** -1 == cirq.CliffordGate.X - assert cirq.CliffordGate.H ** -1 == cirq.CliffordGate.H - assert cirq.CliffordGate.S ** 2 == cirq.CliffordGate.Z - assert cirq.CliffordGate.S ** -1 == cirq.CliffordGate.S ** 3 - assert cirq.CliffordGate.S ** -3 == cirq.CliffordGate.S - assert cirq.CliffordGate.CNOT ** 3 == cirq.CliffordGate.CNOT - assert cirq.CliffordGate.CNOT ** -3 == cirq.CliffordGate.CNOT + assert cirq.MultipleCliffordGate.X ** -1 == cirq.MultipleCliffordGate.X + assert cirq.MultipleCliffordGate.H ** -1 == cirq.MultipleCliffordGate.H + assert cirq.MultipleCliffordGate.S ** 2 == cirq.MultipleCliffordGate.Z + assert cirq.MultipleCliffordGate.S ** -1 == cirq.MultipleCliffordGate.S ** 3 + assert cirq.MultipleCliffordGate.S ** -3 == cirq.MultipleCliffordGate.S + assert cirq.MultipleCliffordGate.CNOT ** 3 == cirq.MultipleCliffordGate.CNOT + assert cirq.MultipleCliffordGate.CNOT ** -3 == cirq.MultipleCliffordGate.CNOT with pytest.raises(TypeError): - _ = cirq.CliffordGate.Z ** 0.25 + _ = cirq.MultipleCliffordGate.Z ** 0.25 def test_stabilizer_effec(): - assert cirq.has_stabilizer_effect(cirq.CliffordGate.X) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.H) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.S) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.CNOT) - assert cirq.has_stabilizer_effect(cirq.CliffordGate.CZ) + assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.X) + assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.H) + assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.S) + assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.CNOT) + assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.CZ) qubits = cirq.LineQubit.range(2) - gate = cirq.CliffordGate.from_op_list( + gate = cirq.MultipleCliffordGate.from_op_list( [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits ) assert cirq.has_stabilizer_effect(gate) @@ -641,62 +640,62 @@ def test_clifford_gate_from_op_list(): # Since from_op_list() ==> _act_on_() ==> tableau.then() and then() has already covered # lots of random circuit cases, here we just test a few well-known relationships. qubit = cirq.NamedQubit('test') - gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Z(qubit)], [qubit]) - assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + gate = cirq.MultipleCliffordGate.from_op_list([cirq.X(qubit), cirq.Z(qubit)], [qubit]) + assert gate == cirq.MultipleCliffordGate.Y # The tableau ignores the global phase - gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) - assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + gate = cirq.MultipleCliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.MultipleCliffordGate.Y # The tableau ignores the global phase - gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Y(qubit)], [qubit]) - assert gate == cirq.CliffordGate.Z # The tableau ignores the global phase + gate = cirq.MultipleCliffordGate.from_op_list([cirq.X(qubit), cirq.Y(qubit)], [qubit]) + assert gate == cirq.MultipleCliffordGate.Z # The tableau ignores the global phase - gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) - assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase + gate = cirq.MultipleCliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.MultipleCliffordGate.Y # The tableau ignores the global phase # Two qubits gates qubits = cirq.LineQubit.range(2) - gate = cirq.CliffordGate.from_op_list( + gate = cirq.MultipleCliffordGate.from_op_list( [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits ) - assert gate == cirq.CliffordGate.CNOT + assert gate == cirq.MultipleCliffordGate.CNOT - gate = cirq.CliffordGate.from_op_list( + gate = cirq.MultipleCliffordGate.from_op_list( [cirq.H(qubits[1]), cirq.CNOT(*qubits), cirq.H(qubits[1])], qubits ) - assert gate == cirq.CliffordGate.CZ + assert gate == cirq.MultipleCliffordGate.CZ # Note the order of qubits matters - gate = cirq.CliffordGate.from_op_list( + gate = cirq.MultipleCliffordGate.from_op_list( [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits ) - assert gate != cirq.CliffordGate.CNOT + assert gate != cirq.MultipleCliffordGate.CNOT # But if we reverse the qubit_order, they will equal again. - gate = cirq.CliffordGate.from_op_list( + gate = cirq.MultipleCliffordGate.from_op_list( [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits[::-1] ) - assert gate == cirq.CliffordGate.CNOT + assert gate == cirq.MultipleCliffordGate.CNOT with pytest.raises( ValueError, match="only be constructed from the operations that has stabilizer effect" ): - cirq.CliffordGate.from_op_list([cirq.T(qubit)], [qubit]) + cirq.MultipleCliffordGate.from_op_list([cirq.T(qubit)], [qubit]) def test_clifford_gate_from_tableau(): - t = cirq.CliffordGate.X.clifford_tableau - assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.X + t = cirq.MultipleCliffordGate.X.clifford_tableau + assert cirq.MultipleCliffordGate.from_clifford_tableau(t) == cirq.MultipleCliffordGate.X - t = cirq.CliffordGate.H.clifford_tableau - assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.H + t = cirq.MultipleCliffordGate.H.clifford_tableau + assert cirq.MultipleCliffordGate.from_clifford_tableau(t) == cirq.MultipleCliffordGate.H - t = cirq.CliffordGate.CNOT.clifford_tableau - assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.CNOT + t = cirq.MultipleCliffordGate.CNOT.clifford_tableau + assert cirq.MultipleCliffordGate.from_clifford_tableau(t) == cirq.MultipleCliffordGate.CNOT with pytest.raises(ValueError): t = cirq.CliffordTableau(num_qubits=1) t.xs = np.array([1, 1]).reshape(2, 1) t.zs = np.array([1, 1]).reshape(2, 1) # This violates the sympletic property. - cirq.CliffordGate.from_clifford_tableau(t) + cirq.MultipleCliffordGate.from_clifford_tableau(t) def test_multi_clifford_decompose_by_unitary(): @@ -711,7 +710,7 @@ def test_multi_clifford_decompose_by_unitary(): g = prng.randint(len(gate_candidate)) indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) - gate = cirq.CliffordGate.from_op_list(ops, qubits) + gate = cirq.MultipleCliffordGate.from_op_list(ops, qubits) decomposed_ops = cirq.decompose(gate.on(*qubits)) circ = cirq.Circuit(decomposed_ops) circ.append(cirq.I.on_each(qubits)) # make sure the dimension aligned. @@ -745,12 +744,12 @@ def test_pad_tableau(): padded_tableau = cirq.ops.clifford_gate._pad_tableau( tableau, num_qubits_after_padding=1, axes=[0] ) - assert padded_tableau == cirq.CliffordGate.X.clifford_tableau + assert padded_tableau == cirq.MultipleCliffordGate.X.clifford_tableau # Tableau for H # [0 1 0] # [1 0 0] - tableau = cirq.CliffordGate.H.clifford_tableau + tableau = cirq.MultipleCliffordGate.H.clifford_tableau padded_tableau = cirq.ops.clifford_gate._pad_tableau( tableau, num_qubits_after_padding=2, axes=[0] ) @@ -767,7 +766,7 @@ def test_pad_tableau(): ) np.testing.assert_equal(padded_tableau.rs.astype(np.int64), np.zeros(4)) # The tableau of H again but pad for another ax - tableau = cirq.CliffordGate.H.clifford_tableau + tableau = cirq.MultipleCliffordGate.H.clifford_tableau padded_tableau = cirq.ops.clifford_gate._pad_tableau( tableau, num_qubits_after_padding=2, axes=[1] ) @@ -802,23 +801,23 @@ def test_clifford_gate_act_on_small_case(): log_of_measurement_results={}, ) cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) - cirq.act_on(cirq.CliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.MultipleCliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.CNOT, expected_args, qubits=[qubits[0], qubits[1]], allow_decompose=False) - cirq.act_on(cirq.CliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) + cirq.act_on(cirq.MultipleCliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) - cirq.act_on(cirq.CliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.MultipleCliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.S, expected_args, qubits=[qubits[0]], allow_decompose=False) - cirq.act_on(cirq.CliffordGate.S, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.MultipleCliffordGate.S, args, qubits=[qubits[0]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.X, expected_args, qubits=[qubits[2]], allow_decompose=False) - cirq.act_on(cirq.CliffordGate.X, args, qubits=[qubits[2]], allow_decompose=False) + cirq.act_on(cirq.MultipleCliffordGate.X, args, qubits=[qubits[2]], allow_decompose=False) assert args.tableau == expected_args.tableau @@ -844,7 +843,7 @@ def test_clifford_gate_act_on_large_case(): gate_candidate[g], args1, qubits=[qubits[i] for i in indices], allow_decompose=False ) ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) - compiled_gate = cirq.CliffordGate.from_op_list(ops, qubits) + compiled_gate = cirq.MultipleCliffordGate.from_op_list(ops, qubits) cirq.act_on(compiled_gate, args2, qubits) assert args1.tableau == args2.tableau @@ -860,10 +859,10 @@ def test_clifford_gate_act_on_ch_form(): prng=np.random.RandomState(), log_of_measurement_results={}, ) - cirq.act_on(cirq.CliffordGate.X, args, qubits=cirq.LineQubit.range(1)) + cirq.act_on(cirq.MultipleCliffordGate.X, args, qubits=cirq.LineQubit.range(1)) np.testing.assert_allclose(args.state.state_vector(), np.array([0, 0, 0, 1])) def test_clifford_gate_act_on_fail(): with pytest.raises(TypeError, match="Failed to act"): - cirq.act_on(cirq.CliffordGate.X, DummyActOnArgs(), qubits=()) + cirq.act_on(cirq.MultipleCliffordGate.X, DummyActOnArgs(), qubits=()) diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.json b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json index edd56d62d86..bb18908ca1b 100644 --- a/cirq-core/cirq/protocols/json_test_data/CliffordGate.json +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json @@ -1,5 +1,5 @@ { - "cirq_type": "CliffordGate", + "cirq_type": "MultipleCliffordGate", "n": 2, "rs": [ false, diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr b/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr index dd7ce8c3a06..6d859de9a50 100644 --- a/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr @@ -1 +1 @@ -cirq.CliffordGate(_clifford_tableau=cirq.CliffordTableau(num_qubits=2)) +cirq.MultipleCliffordGate(_clifford_tableau=cirq.CliffordTableau(num_qubits=2)) From 876e96615ac31ebcfcd005c5f05f5b639e0d24ef Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 15 Feb 2022 21:20:20 -0800 Subject: [PATCH 13/21] Update the Clifford Args Usage and format --- cirq-core/cirq/__init__.py | 3 +- cirq-core/cirq/ops/clifford_gate.py | 11 ++- cirq-core/cirq/ops/clifford_gate_test.py | 77 ++++++++++--------- .../json_test_data/CliffordGate.json | 68 ++++++++-------- 4 files changed, 84 insertions(+), 75 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 18a8d66ad7d..b5819703cd6 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -701,8 +701,7 @@ contrib, ) -# deprecate cirq.ops. -and related attributes +# deprecate cirq.ops and related attributes from cirq import _compat diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 33bc21ec4c7..5dba58205fb 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -32,6 +32,7 @@ from cirq.ops import ( common_gates, gate_features, + identity, named_qubit, raw_types, pauli_gates, @@ -480,7 +481,7 @@ def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQub """Returns a SingleQubitCliffordGate such that the circuits --output--self-- and --self--gate-- are equivalent up to global phase.""" - return self.merged_with(after).merged_with(self ** -1) + return self.merged_with(after).merged_with(self**-1) def __repr__(self) -> str: x = self.transform(pauli_gates.X) @@ -594,6 +595,12 @@ def _circuit_diagram_info_( class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf): """A metaclass used to lazy initialize several common Clifford Gate as class attributes.""" + @property + def I(cls): + if getattr(cls, '_I', None) is None: + cls._I = cls._generate_clifford_from_known_gate(1, identity.I) + return cls._I + @property def X(cls): if getattr(cls, '_X', None) is None: @@ -828,7 +835,7 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool axes = args.get_axes(qubits) # This padding is important and cannot be omitted. padded_tableau = _pad_tableau(self._clifford_tableau, len(args.qubits), axes) - args.tableau = args.tableau.then(padded_tableau) + args._state = args.tableau.then(padded_tableau) return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 6563cdce3f0..1f66f37dc24 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -148,7 +148,7 @@ def test_init_90rot_from_single(trans, frm): # Check that flipping the transform produces the inverse rotation trans_rev = cirq.PauliTransform(trans.to, not trans.flip) gate_rev = cirq.SingleQubitCliffordGate.from_single_map({frm: trans_rev}) - assert gate ** -1 == gate_rev + assert gate**-1 == gate_rev @pytest.mark.parametrize( @@ -206,20 +206,20 @@ def test_init_from_pauli(pauli, sqrt, expected): def test_pow(): - assert cirq.SingleQubitCliffordGate.X ** -1 == cirq.SingleQubitCliffordGate.X - assert cirq.SingleQubitCliffordGate.H ** -1 == cirq.SingleQubitCliffordGate.H - assert cirq.SingleQubitCliffordGate.X_sqrt == cirq.SingleQubitCliffordGate.X ** 0.5 - assert cirq.SingleQubitCliffordGate.Y_sqrt == cirq.SingleQubitCliffordGate.Y ** 0.5 - assert cirq.SingleQubitCliffordGate.Z_sqrt == cirq.SingleQubitCliffordGate.Z ** 0.5 - assert cirq.SingleQubitCliffordGate.X_nsqrt == cirq.SingleQubitCliffordGate.X ** -0.5 - assert cirq.SingleQubitCliffordGate.Y_nsqrt == cirq.SingleQubitCliffordGate.Y ** -0.5 - assert cirq.SingleQubitCliffordGate.Z_nsqrt == cirq.SingleQubitCliffordGate.Z ** -0.5 - assert cirq.SingleQubitCliffordGate.X_sqrt ** -1 == cirq.SingleQubitCliffordGate.X_nsqrt + assert cirq.SingleQubitCliffordGate.X**-1 == cirq.SingleQubitCliffordGate.X + assert cirq.SingleQubitCliffordGate.H**-1 == cirq.SingleQubitCliffordGate.H + assert cirq.SingleQubitCliffordGate.X_sqrt == cirq.SingleQubitCliffordGate.X**0.5 + assert cirq.SingleQubitCliffordGate.Y_sqrt == cirq.SingleQubitCliffordGate.Y**0.5 + assert cirq.SingleQubitCliffordGate.Z_sqrt == cirq.SingleQubitCliffordGate.Z**0.5 + assert cirq.SingleQubitCliffordGate.X_nsqrt == cirq.SingleQubitCliffordGate.X**-0.5 + assert cirq.SingleQubitCliffordGate.Y_nsqrt == cirq.SingleQubitCliffordGate.Y**-0.5 + assert cirq.SingleQubitCliffordGate.Z_nsqrt == cirq.SingleQubitCliffordGate.Z**-0.5 + assert cirq.SingleQubitCliffordGate.X_sqrt**-1 == cirq.SingleQubitCliffordGate.X_nsqrt assert cirq.inverse(cirq.SingleQubitCliffordGate.X_nsqrt) == ( cirq.SingleQubitCliffordGate.X_sqrt ) with pytest.raises(TypeError): - _ = cirq.SingleQubitCliffordGate.Z ** 0.25 + _ = cirq.SingleQubitCliffordGate.Z**0.25 def test_init_from_quarter_turns(): @@ -353,17 +353,17 @@ def test_y_rotation(gate, trans_y): @pytest.mark.parametrize( 'gate,gate_equiv', ( - (cirq.SingleQubitCliffordGate.I, cirq.X ** 0), + (cirq.SingleQubitCliffordGate.I, cirq.X**0), (cirq.SingleQubitCliffordGate.H, cirq.H), (cirq.SingleQubitCliffordGate.X, cirq.X), (cirq.SingleQubitCliffordGate.Y, cirq.Y), (cirq.SingleQubitCliffordGate.Z, cirq.Z), - (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X ** 0.5), - (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X ** -0.5), - (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y ** 0.5), - (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y ** -0.5), - (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z ** 0.5), - (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z ** -0.5), + (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X**0.5), + (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X**-0.5), + (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y**0.5), + (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y**-0.5), + (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z**0.5), + (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z**-0.5), ), ) def test_decompose(gate, gate_equiv): @@ -378,17 +378,17 @@ def test_decompose(gate, gate_equiv): @pytest.mark.parametrize( 'gate,gate_equiv', ( - (cirq.SingleQubitCliffordGate.I, cirq.X ** 0), + (cirq.SingleQubitCliffordGate.I, cirq.X**0), (cirq.SingleQubitCliffordGate.H, cirq.H), (cirq.SingleQubitCliffordGate.X, cirq.X), (cirq.SingleQubitCliffordGate.Y, cirq.Y), (cirq.SingleQubitCliffordGate.Z, cirq.Z), - (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X ** 0.5), - (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X ** -0.5), - (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y ** 0.5), - (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y ** -0.5), - (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z ** 0.5), - (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z ** -0.5), + (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X**0.5), + (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X**-0.5), + (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y**0.5), + (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y**-0.5), + (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z**0.5), + (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z**-0.5), ), ) def test_known_matrix(gate, gate_equiv): @@ -459,7 +459,7 @@ def test_parses_single_qubit_gate(gate): ) def test_commutes_pauli(gate, pauli, half_turns): # TODO(#4328) cirq.X**1 should be _PauliX instead of XPowGate - pauli_gate = pauli if half_turns == 1 else pauli ** half_turns + pauli_gate = pauli if half_turns == 1 else pauli**half_turns q0 = cirq.NamedQubit('q0') mat = cirq.Circuit( gate(q0), @@ -581,7 +581,7 @@ def test_from_xz_to_clifford_tableau(): seen_tableau = [] for trans_x, trans_z in _all_rotation_pairs(): tableau = cirq.SingleQubitCliffordGate.from_xz_map(trans_x, trans_z).clifford_tableau - tableau_number = sum(2 ** i * t for i, t in enumerate(tableau.matrix().ravel())) + tableau_number = sum(2**i * t for i, t in enumerate(tableau.matrix().ravel())) tableau_number = tableau_number * 4 + 2 * tableau.rs[0] + tableau.rs[1] seen_tableau.append(tableau_number) # Satisfy the symplectic property @@ -594,6 +594,7 @@ def test_from_xz_to_clifford_tableau(): @pytest.mark.parametrize( 'clifford_gate,standard_gate', [ + (cirq.MultipleCliffordGate.I, cirq.I), (cirq.MultipleCliffordGate.X, cirq.X), (cirq.MultipleCliffordGate.Y, cirq.Y), (cirq.MultipleCliffordGate.Z, cirq.Z), @@ -612,15 +613,15 @@ def test_common_clifford_gate(clifford_gate, standard_gate): def test_multi_qubit_clifford_pow(): - assert cirq.MultipleCliffordGate.X ** -1 == cirq.MultipleCliffordGate.X - assert cirq.MultipleCliffordGate.H ** -1 == cirq.MultipleCliffordGate.H - assert cirq.MultipleCliffordGate.S ** 2 == cirq.MultipleCliffordGate.Z - assert cirq.MultipleCliffordGate.S ** -1 == cirq.MultipleCliffordGate.S ** 3 - assert cirq.MultipleCliffordGate.S ** -3 == cirq.MultipleCliffordGate.S - assert cirq.MultipleCliffordGate.CNOT ** 3 == cirq.MultipleCliffordGate.CNOT - assert cirq.MultipleCliffordGate.CNOT ** -3 == cirq.MultipleCliffordGate.CNOT + assert cirq.MultipleCliffordGate.X**-1 == cirq.MultipleCliffordGate.X + assert cirq.MultipleCliffordGate.H**-1 == cirq.MultipleCliffordGate.H + assert cirq.MultipleCliffordGate.S**2 == cirq.MultipleCliffordGate.Z + assert cirq.MultipleCliffordGate.S**-1 == cirq.MultipleCliffordGate.S**3 + assert cirq.MultipleCliffordGate.S**-3 == cirq.MultipleCliffordGate.S + assert cirq.MultipleCliffordGate.CNOT**3 == cirq.MultipleCliffordGate.CNOT + assert cirq.MultipleCliffordGate.CNOT**-3 == cirq.MultipleCliffordGate.CNOT with pytest.raises(TypeError): - _ = cirq.MultipleCliffordGate.Z ** 0.25 + _ = cirq.MultipleCliffordGate.Z**0.25 def test_stabilizer_effec(): @@ -805,7 +806,9 @@ def test_clifford_gate_act_on_small_case(): assert args.tableau == expected_args.tableau cirq.act_on(cirq.CNOT, expected_args, qubits=[qubits[0], qubits[1]], allow_decompose=False) - cirq.act_on(cirq.MultipleCliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) + cirq.act_on( + cirq.MultipleCliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False + ) assert args.tableau == expected_args.tableau cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) @@ -854,7 +857,7 @@ def test_clifford_gate_act_on_ch_form(): # to the decomposititon method and apply it through decomposed ops. # Here we run it for the coverage only. args = cirq.ActOnStabilizerCHFormArgs( - state=cirq.StabilizerStateChForm(num_qubits=2, initial_state=1), + initial_state=cirq.StabilizerStateChForm(num_qubits=2, initial_state=1), qubits=cirq.LineQubit.range(2), prng=np.random.RandomState(), log_of_measurement_results={}, diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.json b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json index bb18908ca1b..576e78e9765 100644 --- a/cirq-core/cirq/protocols/json_test_data/CliffordGate.json +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json @@ -1,38 +1,38 @@ { - "cirq_type": "MultipleCliffordGate", - "n": 2, - "rs": [ - false, - false, - false, - false + "cirq_type": "MultipleCliffordGate", + "n": 2, + "rs": [ + false, + false, + false, + false + ], + "xs": [ + [ + true, false ], - "xs": [ - [ - true, false - ], - [ - false, true - ], - [ - false, false - ], - [ - false, false - ] + [ + false, true ], - "zs": [ - [ - false, false - ], - [ - false, false - ], - [ - true, false - ], - [ - false, true - ] + [ + false, false + ], + [ + false, false + ] + ], + "zs": [ + [ + false, false + ], + [ + false, false + ], + [ + true, false + ], + [ + false, true ] - } \ No newline at end of file + ] +} From c795cce4591bff04b1706bd9a5abb4ae13280bfa Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 15 Feb 2022 21:29:16 -0800 Subject: [PATCH 14/21] Fix the format and json test --- cirq-core/cirq/ops/clifford_gate.py | 8 +-- cirq-core/cirq/ops/clifford_gate_test.py | 70 +++++++++---------- ...ordGate.json => MultipleCliffordGate.json} | 0 ...ordGate.repr => MultipleCliffordGate.repr} | 0 4 files changed, 39 insertions(+), 39 deletions(-) rename cirq-core/cirq/protocols/json_test_data/{CliffordGate.json => MultipleCliffordGate.json} (100%) rename cirq-core/cirq/protocols/json_test_data/{CliffordGate.repr => MultipleCliffordGate.repr} (100%) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 5dba58205fb..a5edb14d2c5 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -481,7 +481,7 @@ def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQub """Returns a SingleQubitCliffordGate such that the circuits --output--self-- and --self--gate-- are equivalent up to global phase.""" - return self.merged_with(after).merged_with(self**-1) + return self.merged_with(after).merged_with(self ** -1) def __repr__(self) -> str: x = self.transform(pauli_gates.X) @@ -688,9 +688,9 @@ def _pad_tableau( padded_tableau = qis.CliffordTableau(num_qubits_after_padding) v_index = np.concatenate((np.asarray(axes), num_qubits_after_padding + np.asarray(axes))) - padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs.copy() - padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs.copy() - padded_tableau.rs[v_index] = clifford_tableau.rs.copy() + padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs + padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs + padded_tableau.rs[v_index] = clifford_tableau.rs return padded_tableau diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 1f66f37dc24..09f63584b53 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -148,7 +148,7 @@ def test_init_90rot_from_single(trans, frm): # Check that flipping the transform produces the inverse rotation trans_rev = cirq.PauliTransform(trans.to, not trans.flip) gate_rev = cirq.SingleQubitCliffordGate.from_single_map({frm: trans_rev}) - assert gate**-1 == gate_rev + assert gate ** -1 == gate_rev @pytest.mark.parametrize( @@ -206,20 +206,20 @@ def test_init_from_pauli(pauli, sqrt, expected): def test_pow(): - assert cirq.SingleQubitCliffordGate.X**-1 == cirq.SingleQubitCliffordGate.X - assert cirq.SingleQubitCliffordGate.H**-1 == cirq.SingleQubitCliffordGate.H - assert cirq.SingleQubitCliffordGate.X_sqrt == cirq.SingleQubitCliffordGate.X**0.5 - assert cirq.SingleQubitCliffordGate.Y_sqrt == cirq.SingleQubitCliffordGate.Y**0.5 - assert cirq.SingleQubitCliffordGate.Z_sqrt == cirq.SingleQubitCliffordGate.Z**0.5 - assert cirq.SingleQubitCliffordGate.X_nsqrt == cirq.SingleQubitCliffordGate.X**-0.5 - assert cirq.SingleQubitCliffordGate.Y_nsqrt == cirq.SingleQubitCliffordGate.Y**-0.5 - assert cirq.SingleQubitCliffordGate.Z_nsqrt == cirq.SingleQubitCliffordGate.Z**-0.5 - assert cirq.SingleQubitCliffordGate.X_sqrt**-1 == cirq.SingleQubitCliffordGate.X_nsqrt + assert cirq.SingleQubitCliffordGate.X ** -1 == cirq.SingleQubitCliffordGate.X + assert cirq.SingleQubitCliffordGate.H ** -1 == cirq.SingleQubitCliffordGate.H + assert cirq.SingleQubitCliffordGate.X_sqrt == cirq.SingleQubitCliffordGate.X ** 0.5 + assert cirq.SingleQubitCliffordGate.Y_sqrt == cirq.SingleQubitCliffordGate.Y ** 0.5 + assert cirq.SingleQubitCliffordGate.Z_sqrt == cirq.SingleQubitCliffordGate.Z ** 0.5 + assert cirq.SingleQubitCliffordGate.X_nsqrt == cirq.SingleQubitCliffordGate.X ** -0.5 + assert cirq.SingleQubitCliffordGate.Y_nsqrt == cirq.SingleQubitCliffordGate.Y ** -0.5 + assert cirq.SingleQubitCliffordGate.Z_nsqrt == cirq.SingleQubitCliffordGate.Z ** -0.5 + assert cirq.SingleQubitCliffordGate.X_sqrt ** -1 == cirq.SingleQubitCliffordGate.X_nsqrt assert cirq.inverse(cirq.SingleQubitCliffordGate.X_nsqrt) == ( cirq.SingleQubitCliffordGate.X_sqrt ) with pytest.raises(TypeError): - _ = cirq.SingleQubitCliffordGate.Z**0.25 + _ = cirq.SingleQubitCliffordGate.Z ** 0.25 def test_init_from_quarter_turns(): @@ -353,17 +353,17 @@ def test_y_rotation(gate, trans_y): @pytest.mark.parametrize( 'gate,gate_equiv', ( - (cirq.SingleQubitCliffordGate.I, cirq.X**0), + (cirq.SingleQubitCliffordGate.I, cirq.X ** 0), (cirq.SingleQubitCliffordGate.H, cirq.H), (cirq.SingleQubitCliffordGate.X, cirq.X), (cirq.SingleQubitCliffordGate.Y, cirq.Y), (cirq.SingleQubitCliffordGate.Z, cirq.Z), - (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X**0.5), - (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X**-0.5), - (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y**0.5), - (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y**-0.5), - (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z**0.5), - (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z**-0.5), + (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X ** 0.5), + (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X ** -0.5), + (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y ** 0.5), + (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y ** -0.5), + (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z ** 0.5), + (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z ** -0.5), ), ) def test_decompose(gate, gate_equiv): @@ -378,17 +378,17 @@ def test_decompose(gate, gate_equiv): @pytest.mark.parametrize( 'gate,gate_equiv', ( - (cirq.SingleQubitCliffordGate.I, cirq.X**0), + (cirq.SingleQubitCliffordGate.I, cirq.X ** 0), (cirq.SingleQubitCliffordGate.H, cirq.H), (cirq.SingleQubitCliffordGate.X, cirq.X), (cirq.SingleQubitCliffordGate.Y, cirq.Y), (cirq.SingleQubitCliffordGate.Z, cirq.Z), - (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X**0.5), - (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X**-0.5), - (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y**0.5), - (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y**-0.5), - (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z**0.5), - (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z**-0.5), + (cirq.SingleQubitCliffordGate.X_sqrt, cirq.X ** 0.5), + (cirq.SingleQubitCliffordGate.X_nsqrt, cirq.X ** -0.5), + (cirq.SingleQubitCliffordGate.Y_sqrt, cirq.Y ** 0.5), + (cirq.SingleQubitCliffordGate.Y_nsqrt, cirq.Y ** -0.5), + (cirq.SingleQubitCliffordGate.Z_sqrt, cirq.Z ** 0.5), + (cirq.SingleQubitCliffordGate.Z_nsqrt, cirq.Z ** -0.5), ), ) def test_known_matrix(gate, gate_equiv): @@ -459,7 +459,7 @@ def test_parses_single_qubit_gate(gate): ) def test_commutes_pauli(gate, pauli, half_turns): # TODO(#4328) cirq.X**1 should be _PauliX instead of XPowGate - pauli_gate = pauli if half_turns == 1 else pauli**half_turns + pauli_gate = pauli if half_turns == 1 else pauli ** half_turns q0 = cirq.NamedQubit('q0') mat = cirq.Circuit( gate(q0), @@ -581,7 +581,7 @@ def test_from_xz_to_clifford_tableau(): seen_tableau = [] for trans_x, trans_z in _all_rotation_pairs(): tableau = cirq.SingleQubitCliffordGate.from_xz_map(trans_x, trans_z).clifford_tableau - tableau_number = sum(2**i * t for i, t in enumerate(tableau.matrix().ravel())) + tableau_number = sum(2 ** i * t for i, t in enumerate(tableau.matrix().ravel())) tableau_number = tableau_number * 4 + 2 * tableau.rs[0] + tableau.rs[1] seen_tableau.append(tableau_number) # Satisfy the symplectic property @@ -613,15 +613,15 @@ def test_common_clifford_gate(clifford_gate, standard_gate): def test_multi_qubit_clifford_pow(): - assert cirq.MultipleCliffordGate.X**-1 == cirq.MultipleCliffordGate.X - assert cirq.MultipleCliffordGate.H**-1 == cirq.MultipleCliffordGate.H - assert cirq.MultipleCliffordGate.S**2 == cirq.MultipleCliffordGate.Z - assert cirq.MultipleCliffordGate.S**-1 == cirq.MultipleCliffordGate.S**3 - assert cirq.MultipleCliffordGate.S**-3 == cirq.MultipleCliffordGate.S - assert cirq.MultipleCliffordGate.CNOT**3 == cirq.MultipleCliffordGate.CNOT - assert cirq.MultipleCliffordGate.CNOT**-3 == cirq.MultipleCliffordGate.CNOT + assert cirq.MultipleCliffordGate.X ** -1 == cirq.MultipleCliffordGate.X + assert cirq.MultipleCliffordGate.H ** -1 == cirq.MultipleCliffordGate.H + assert cirq.MultipleCliffordGate.S ** 2 == cirq.MultipleCliffordGate.Z + assert cirq.MultipleCliffordGate.S ** -1 == cirq.MultipleCliffordGate.S ** 3 + assert cirq.MultipleCliffordGate.S ** -3 == cirq.MultipleCliffordGate.S + assert cirq.MultipleCliffordGate.CNOT ** 3 == cirq.MultipleCliffordGate.CNOT + assert cirq.MultipleCliffordGate.CNOT ** -3 == cirq.MultipleCliffordGate.CNOT with pytest.raises(TypeError): - _ = cirq.MultipleCliffordGate.Z**0.25 + _ = cirq.MultipleCliffordGate.Z ** 0.25 def test_stabilizer_effec(): diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.json b/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.json similarity index 100% rename from cirq-core/cirq/protocols/json_test_data/CliffordGate.json rename to cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.json diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr b/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.repr similarity index 100% rename from cirq-core/cirq/protocols/json_test_data/CliffordGate.repr rename to cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.repr From be25af0c328978b18e153cf376cf65cc111918a4 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 15 Feb 2022 21:59:02 -0800 Subject: [PATCH 15/21] Add more docstring --- cirq-core/cirq/ops/clifford_gate.py | 38 +++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index a5edb14d2c5..5fd68644cc7 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -730,7 +730,27 @@ def clifford_tableau(self): @classmethod def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffordGate': - assert isinstance(tableau, qis.CliffordTableau) + """Create the MultipleCliffordGate instance from Clifford Tableau. + + Args: + tableau: A CliffordTableau to define the effect of Clifford Gate applying on + the stabilizer state or Pauli group. The meaning of tableau here is + To X Z sign + from X [ X_x Z_x | r_x ] + from Z [ X_z Z_z | r_z ] + Each row in the Clifford tableau indicates how the transformation of original + Pauli gates to the new gates after applying this Clifford Gate. + + Returns: + A MultipleCliffordGate instance, which has the transformation defined by + the input tableau. + + Raises: + ValueError: When input tableau is wrong type or the tableau does not + satisfy the symplectic property. + """ + if not isinstance(tableau, qis.CliffordTableau): + raise ValueError('Input tableau has to be CliffordTableau.') if not tableau._validate(): raise ValueError('It is not a valid Clifford tableau.') return MultipleCliffordGate(_clifford_tableau=tableau) @@ -739,7 +759,21 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffor def from_op_list( cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] ) -> 'MultipleCliffordGate': - """Construct a new Clifford gates from several known operations.""" + """Construct a new Clifford gates from several known operations. + + Args: + operations: A list of cirq operations to construct the Clifford gate. + The combination order is the first element in the list applies the transformation + on the stabilizer state first. + qubit_order: Determines how qubits are ordered when decomposite the operations. + + Returns: + A MultipleCliffordGate instance, which has the transformation on the stabilizer + state equivalent to the composition of operations. + + Raises: + ValueError: When one or more operations do not have stabilizer effect. + """ from cirq.sim import clifford for op in operations: From bb3fa2858e51aa882022c3404c3ede5e2e77bbb5 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 15 Feb 2022 22:02:20 -0800 Subject: [PATCH 16/21] Move class methods to the lazyload class --- cirq-core/cirq/ops/clifford_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 5fd68644cc7..5e1348d1367 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -738,7 +738,7 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffor To X Z sign from X [ X_x Z_x | r_x ] from Z [ X_z Z_z | r_z ] - Each row in the Clifford tableau indicates how the transformation of original + Each row in the Clifford tableau indicates how the transformation of original Pauli gates to the new gates after applying this Clifford Gate. Returns: From 89d504789ca640de9501504f9a254fcc7a3fe697 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 15 Feb 2022 22:31:10 -0800 Subject: [PATCH 17/21] Fix the coverage --- cirq-core/cirq/ops/clifford_gate.py | 120 +++++++++++------------ cirq-core/cirq/ops/clifford_gate_test.py | 3 + 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 5e1348d1367..bacb91827eb 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -669,65 +669,6 @@ def _generate_clifford_from_known_gate( protocols.act_on(gate, args, qubits, allow_decompose=False) return MultipleCliffordGate.from_clifford_tableau(args.tableau) - -def _pad_tableau( - clifford_tableau: qis.CliffordTableau, num_qubits_after_padding: int, axes: List[int] -) -> qis.CliffordTableau: - """Roughly, this function copies self.tabluea into the "identity" matrix.""" - # Sanity check - if len(set(axes)) != clifford_tableau.n: - raise ValueError( - "Input axes of padding should match with the number of qubits in the input tableau." - ) - if clifford_tableau.n > num_qubits_after_padding: - raise ValueError( - "The number of qubits in the input tableau should not be larger than " - "num_qubits_after_padding." - ) - - padded_tableau = qis.CliffordTableau(num_qubits_after_padding) - v_index = np.concatenate((np.asarray(axes), num_qubits_after_padding + np.asarray(axes))) - - padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs - padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs - padded_tableau.rs[v_index] = clifford_tableau.rs - return padded_tableau - - -@value.value_equality -class MultipleCliffordGate(raw_types.Gate, CommonCliffordGates): - """Clifford rotation for N-qubit.""" - - def __init__( - self, - *, - _clifford_tableau: qis.CliffordTableau, - ) -> None: - # We use the Clifford tableau to represent a Clifford gate. - # It is crucial to note that the meaning of tableau here is different - # from the one used to represent a Clifford state (Of course, they are related). - # A) We have to use the full 2n * (2n + 1) matrix - # B) The meaning of tableau here is - # X Z sign - # from X [ X_x Z_x | r_x ] - # from Z [ X_z Z_z | r_z ] - # Each row in the Clifford tableau means the transformation of original Pauli gates. - # For example, take a 2 * (2+1) tableau as example: - # X Z r - # XI [ 1 0 | 1 0 | 0 ] - # IX [ 0 0 | 1 1 | 0 ] - # ZI [ 0 0 | 1 0 | 1 ] - # IZ [ 1 0 | 1 1 | 0 ] - # Take the third row as example: this means the ZI gate after the this gate, - # more precisely the conjugate transformation of ZI by this gate, becomes -ZI. - # (Note the real clifford tableau has to satify the Symplectic property. - # here is just for illustration) - self._clifford_tableau = _clifford_tableau.copy() - - @property - def clifford_tableau(self): - return self._clifford_tableau - @classmethod def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffordGate': """Create the MultipleCliffordGate instance from Clifford Tableau. @@ -750,7 +691,7 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffor satisfy the symplectic property. """ if not isinstance(tableau, qis.CliffordTableau): - raise ValueError('Input tableau has to be CliffordTableau.') + raise ValueError('Input argument has to be a CliffordTableau instance.') if not tableau._validate(): raise ValueError('It is not a valid Clifford tableau.') return MultipleCliffordGate(_clifford_tableau=tableau) @@ -806,6 +747,65 @@ def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): ) return cls(_clifford_tableau=_clifford_tableau) + +def _pad_tableau( + clifford_tableau: qis.CliffordTableau, num_qubits_after_padding: int, axes: List[int] +) -> qis.CliffordTableau: + """Roughly, this function copies self.tabluea into the "identity" matrix.""" + # Sanity check + if len(set(axes)) != clifford_tableau.n: + raise ValueError( + "Input axes of padding should match with the number of qubits in the input tableau." + ) + if clifford_tableau.n > num_qubits_after_padding: + raise ValueError( + "The number of qubits in the input tableau should not be larger than " + "num_qubits_after_padding." + ) + + padded_tableau = qis.CliffordTableau(num_qubits_after_padding) + v_index = np.concatenate((np.asarray(axes), num_qubits_after_padding + np.asarray(axes))) + + padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs + padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs + padded_tableau.rs[v_index] = clifford_tableau.rs + return padded_tableau + + +@value.value_equality +class MultipleCliffordGate(raw_types.Gate, CommonCliffordGates): + """Clifford rotation for N-qubit.""" + + def __init__( + self, + *, + _clifford_tableau: qis.CliffordTableau, + ) -> None: + # We use the Clifford tableau to represent a Clifford gate. + # It is crucial to note that the meaning of tableau here is different + # from the one used to represent a Clifford state (Of course, they are related). + # A) We have to use the full 2n * (2n + 1) matrix + # B) The meaning of tableau here is + # X Z sign + # from X [ X_x Z_x | r_x ] + # from Z [ X_z Z_z | r_z ] + # Each row in the Clifford tableau means the transformation of original Pauli gates. + # For example, take a 2 * (2+1) tableau as example: + # X Z r + # XI [ 1 0 | 1 0 | 0 ] + # IX [ 0 0 | 1 1 | 0 ] + # ZI [ 0 0 | 1 0 | 1 ] + # IZ [ 1 0 | 1 1 | 0 ] + # Take the third row as example: this means the ZI gate after the this gate, + # more precisely the conjugate transformation of ZI by this gate, becomes -ZI. + # (Note the real clifford tableau has to satify the Symplectic property. + # here is just for illustration) + self._clifford_tableau = _clifford_tableau.copy() + + @property + def clifford_tableau(self): + return self._clifford_tableau + def _json_dict_(self) -> Dict[str, Any]: json_dict = self._clifford_tableau._json_dict_() return json_dict diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 09f63584b53..ea685027885 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -698,6 +698,9 @@ def test_clifford_gate_from_tableau(): t.zs = np.array([1, 1]).reshape(2, 1) # This violates the sympletic property. cirq.MultipleCliffordGate.from_clifford_tableau(t) + with pytest.raises(ValueError, match="Input argument has to be a CliffordTableau instance."): + cirq.MultipleCliffordGate.from_clifford_tableau(1) + def test_multi_clifford_decompose_by_unitary(): # Construct a random clifford gate: From c030a890c112a54217a0f16c23aaa8fe3a388591 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 16 Feb 2022 09:39:58 -0800 Subject: [PATCH 18/21] Use LazyLoader --- cirq-core/cirq/ops/clifford_gate.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index bacb91827eb..84f98405cc2 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -29,6 +29,7 @@ from cirq import protocols, value, linalg, qis from cirq._doc import document +from cirq._import import LazyLoader from cirq.ops import ( common_gates, gate_features, @@ -44,6 +45,11 @@ if TYPE_CHECKING: import cirq +# Lazy imports to break circular dependencies. +devices = LazyLoader("devices", globals(), "cirq.devices") +sim = LazyLoader("sim", globals(), "cirq.sim") +transformers = LazyLoader("transformers", globals(), cirq.transformers) + PauliTransform = NamedTuple('PauliTransform', [('to', Pauli), ('flip', bool)]) document(PauliTransform, """+X, -X, +Y, -Y, +Z, or -Z.""") @@ -658,9 +664,7 @@ class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): def _generate_clifford_from_known_gate( cls, num_qubits: int, gate: raw_types.Gate ) -> 'MultipleCliffordGate': - from cirq import LineQubit, sim - - qubits = LineQubit.range(num_qubits) + qubits = devices.LineQubit.range(num_qubits) t = qis.CliffordTableau(num_qubits=num_qubits) args = sim.ActOnCliffordTableauArgs( tableau=t, qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={} @@ -715,8 +719,6 @@ def from_op_list( Raises: ValueError: When one or more operations do not have stabilizer effect. """ - from cirq.sim import clifford - for op in operations: if op.gate and op.gate._has_stabilizer_effect_(): continue @@ -726,7 +728,7 @@ def from_op_list( ) base_tableau = qis.CliffordTableau(len(qubit_order)) - args = clifford.ActOnCliffordTableauArgs( + args = sim.clifford.ActOnCliffordTableauArgs( tableau=base_tableau, qubits=qubit_order, prng=np.random.RandomState(0), # unused @@ -850,14 +852,11 @@ def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, return NotImplemented def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation]: - from cirq.transformers import analytical_decompositions - - return analytical_decompositions.decompose_clifford_tableau_to_operations( + return transformers.analytical_decompositions.decompose_clifford_tableau_to_operations( list(qubits), self.clifford_tableau ) def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: - from cirq.sim import clifford # Note the computation complexity difference between _decompose_ and _act_on_. # Suppose this Gate has `m` qubits, args has `n` qubits, and the decomposition of @@ -865,14 +864,14 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool # 1. Direct act_on is O(n^3) -- two matrices multiplication # 2. Decomposition is O(m^3)+O(k*n^2) -- Decomposition complexity + k * One/two-qubits Ops # So when m << n, the decomposition is more efficient. - if isinstance(args, clifford.ActOnCliffordTableauArgs): + if isinstance(args, sim.clifford.ActOnCliffordTableauArgs): axes = args.get_axes(qubits) # This padding is important and cannot be omitted. padded_tableau = _pad_tableau(self._clifford_tableau, len(args.qubits), axes) args._state = args.tableau.then(padded_tableau) return True - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): + if isinstance(args, sim.clifford.ActOnStabilizerCHFormArgs): # Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs? # It should be unlike because CliffordTableau ignores the global phase but CHForm # is aimed to fix that. From 51933a3ba807c449338a08f78de0d52a273a247d Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 16 Feb 2022 21:30:16 -0800 Subject: [PATCH 19/21] Fix the missing quote in the lazyloader --- cirq-core/cirq/ops/clifford_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 84f98405cc2..67c9b8a8ea8 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -48,7 +48,7 @@ # Lazy imports to break circular dependencies. devices = LazyLoader("devices", globals(), "cirq.devices") sim = LazyLoader("sim", globals(), "cirq.sim") -transformers = LazyLoader("transformers", globals(), cirq.transformers) +transformers = LazyLoader("transformers", globals(), "cirq.transformers") PauliTransform = NamedTuple('PauliTransform', [('to', Pauli), ('flip', bool)]) document(PauliTransform, """+X, -X, +Y, -Y, +Z, or -Z.""") From f19794c35445b754778450710abb7244828b8b3b Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 16 Feb 2022 21:33:41 -0800 Subject: [PATCH 20/21] Rename MultipleCliffordGate Back to CliffordGate --- cirq-core/cirq/__init__.py | 2 +- cirq-core/cirq/json_resolver_cache.py | 2 +- cirq-core/cirq/ops/__init__.py | 2 +- cirq-core/cirq/ops/clifford_gate.py | 28 ++-- cirq-core/cirq/ops/clifford_gate_test.py | 124 +++++++++--------- ...pleCliffordGate.json => CliffordGate.json} | 2 +- .../json_test_data/CliffordGate.repr | 1 + .../json_test_data/MultipleCliffordGate.repr | 1 - 8 files changed, 81 insertions(+), 81 deletions(-) rename cirq-core/cirq/protocols/json_test_data/{MultipleCliffordGate.json => CliffordGate.json} (90%) create mode 100644 cirq-core/cirq/protocols/json_test_data/CliffordGate.repr delete mode 100644 cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index a0324d8376f..c7d34a6cf8a 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -203,6 +203,7 @@ CCNOT, CCNotPowGate, ClassicallyControlledOperation, + CliffordGate, CNOT, CNotPowGate, ControlledGate, @@ -253,7 +254,6 @@ measure_paulistring_terms, measure_single_paulistring, MeasurementGate, - MultipleCliffordGate, MutableDensePauliString, MutablePauliString, NamedQubit, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 37ab79c8b1b..77130207722 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -66,6 +66,7 @@ def _parallel_gate_op(gate, qubits): 'CircuitOperation': cirq.CircuitOperation, 'ClassicallyControlledOperation': cirq.ClassicallyControlledOperation, 'ClassicalDataDictionaryStore': cirq.ClassicalDataDictionaryStore, + 'CliffordGate': cirq.CliffordGate, 'CliffordState': cirq.CliffordState, 'CliffordTableau': cirq.CliffordTableau, 'CNotPowGate': cirq.CNotPowGate, @@ -111,7 +112,6 @@ def _parallel_gate_op(gate, qubits): 'MeasurementType': cirq.MeasurementType, '_MeasurementSpec': cirq.work._MeasurementSpec, 'Moment': cirq.Moment, - 'MultipleCliffordGate': cirq.MultipleCliffordGate, 'MutableDensePauliString': cirq.MutableDensePauliString, 'MutablePauliString': cirq.MutablePauliString, '_NoNoiseModel': _NoNoiseModel, diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index 6d14bdba4df..552b5a7119e 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -19,7 +19,7 @@ ) from cirq.ops.clifford_gate import ( - MultipleCliffordGate, + CliffordGate, PauliTransform, SingleQubitCliffordGate, ) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 67c9b8a8ea8..3dee8a5ddd5 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -663,7 +663,7 @@ class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): @classmethod def _generate_clifford_from_known_gate( cls, num_qubits: int, gate: raw_types.Gate - ) -> 'MultipleCliffordGate': + ) -> 'CliffordGate': qubits = devices.LineQubit.range(num_qubits) t = qis.CliffordTableau(num_qubits=num_qubits) args = sim.ActOnCliffordTableauArgs( @@ -671,11 +671,11 @@ def _generate_clifford_from_known_gate( ) protocols.act_on(gate, args, qubits, allow_decompose=False) - return MultipleCliffordGate.from_clifford_tableau(args.tableau) + return CliffordGate.from_clifford_tableau(args.tableau) @classmethod - def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffordGate': - """Create the MultipleCliffordGate instance from Clifford Tableau. + def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': + """Create the CliffordGate instance from Clifford Tableau. Args: tableau: A CliffordTableau to define the effect of Clifford Gate applying on @@ -687,7 +687,7 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffor Pauli gates to the new gates after applying this Clifford Gate. Returns: - A MultipleCliffordGate instance, which has the transformation defined by + A CliffordGate instance, which has the transformation defined by the input tableau. Raises: @@ -698,12 +698,12 @@ def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'MultipleCliffor raise ValueError('Input argument has to be a CliffordTableau instance.') if not tableau._validate(): raise ValueError('It is not a valid Clifford tableau.') - return MultipleCliffordGate(_clifford_tableau=tableau) + return CliffordGate(_clifford_tableau=tableau) @classmethod def from_op_list( cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] - ) -> 'MultipleCliffordGate': + ) -> 'CliffordGate': """Construct a new Clifford gates from several known operations. Args: @@ -713,7 +713,7 @@ def from_op_list( qubit_order: Determines how qubits are ordered when decomposite the operations. Returns: - A MultipleCliffordGate instance, which has the transformation on the stabilizer + A CliffordGate instance, which has the transformation on the stabilizer state equivalent to the composition of operations. Raises: @@ -737,7 +737,7 @@ def from_op_list( for op in operations: protocols.act_on(op, args, allow_decompose=True) - return MultipleCliffordGate.from_clifford_tableau(args.tableau) + return CliffordGate.from_clifford_tableau(args.tableau) @classmethod def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): @@ -775,7 +775,7 @@ def _pad_tableau( @value.value_equality -class MultipleCliffordGate(raw_types.Gate, CommonCliffordGates): +class CliffordGate(raw_types.Gate, CommonCliffordGates): """Clifford rotation for N-qubit.""" def __init__( @@ -822,19 +822,19 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: # By definition, Clifford Gate should always return True. return True - def __pow__(self, exponent) -> 'MultipleCliffordGate': + def __pow__(self, exponent) -> 'CliffordGate': if exponent == -1: - return MultipleCliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) + return CliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) if exponent > 0 and int(exponent) == exponent: base_tableau = self.clifford_tableau.copy() for _ in range(int(exponent) - 1): base_tableau = base_tableau.then(self.clifford_tableau) - return MultipleCliffordGate.from_clifford_tableau(base_tableau) + return CliffordGate.from_clifford_tableau(base_tableau) if exponent < 0 and int(exponent) == exponent: base_tableau = self.clifford_tableau.copy() for _ in range(int(-exponent) - 1): base_tableau = base_tableau.then(self.clifford_tableau) - return MultipleCliffordGate.from_clifford_tableau(base_tableau.inverse()) + return CliffordGate.from_clifford_tableau(base_tableau.inverse()) return NotImplemented diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index ea685027885..9cdfa3fc2e7 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -417,8 +417,8 @@ def test_commutes_notimplemented_type(): assert cirq.commutes(cirq.SingleQubitCliffordGate.X, 'X', default='default') == 'default' with pytest.raises(TypeError): - cirq.commutes(cirq.MultipleCliffordGate.X, 'X') - assert cirq.commutes(cirq.MultipleCliffordGate.X, 'X', default='default') == 'default' + cirq.commutes(cirq.CliffordGate.X, 'X') + assert cirq.commutes(cirq.CliffordGate.X, 'X', default='default') == 'default' @pytest.mark.parametrize( @@ -594,15 +594,15 @@ def test_from_xz_to_clifford_tableau(): @pytest.mark.parametrize( 'clifford_gate,standard_gate', [ - (cirq.MultipleCliffordGate.I, cirq.I), - (cirq.MultipleCliffordGate.X, cirq.X), - (cirq.MultipleCliffordGate.Y, cirq.Y), - (cirq.MultipleCliffordGate.Z, cirq.Z), - (cirq.MultipleCliffordGate.H, cirq.H), - (cirq.MultipleCliffordGate.S, cirq.S), - (cirq.MultipleCliffordGate.CNOT, cirq.CNOT), - (cirq.MultipleCliffordGate.CZ, cirq.CZ), - (cirq.MultipleCliffordGate.SWAP, cirq.SWAP), + (cirq.CliffordGate.I, cirq.I), + (cirq.CliffordGate.X, cirq.X), + (cirq.CliffordGate.Y, cirq.Y), + (cirq.CliffordGate.Z, cirq.Z), + (cirq.CliffordGate.H, cirq.H), + (cirq.CliffordGate.S, cirq.S), + (cirq.CliffordGate.CNOT, cirq.CNOT), + (cirq.CliffordGate.CZ, cirq.CZ), + (cirq.CliffordGate.SWAP, cirq.SWAP), ], ) def test_common_clifford_gate(clifford_gate, standard_gate): @@ -613,25 +613,25 @@ def test_common_clifford_gate(clifford_gate, standard_gate): def test_multi_qubit_clifford_pow(): - assert cirq.MultipleCliffordGate.X ** -1 == cirq.MultipleCliffordGate.X - assert cirq.MultipleCliffordGate.H ** -1 == cirq.MultipleCliffordGate.H - assert cirq.MultipleCliffordGate.S ** 2 == cirq.MultipleCliffordGate.Z - assert cirq.MultipleCliffordGate.S ** -1 == cirq.MultipleCliffordGate.S ** 3 - assert cirq.MultipleCliffordGate.S ** -3 == cirq.MultipleCliffordGate.S - assert cirq.MultipleCliffordGate.CNOT ** 3 == cirq.MultipleCliffordGate.CNOT - assert cirq.MultipleCliffordGate.CNOT ** -3 == cirq.MultipleCliffordGate.CNOT + assert cirq.CliffordGate.X ** -1 == cirq.CliffordGate.X + assert cirq.CliffordGate.H ** -1 == cirq.CliffordGate.H + assert cirq.CliffordGate.S ** 2 == cirq.CliffordGate.Z + assert cirq.CliffordGate.S ** -1 == cirq.CliffordGate.S ** 3 + assert cirq.CliffordGate.S ** -3 == cirq.CliffordGate.S + assert cirq.CliffordGate.CNOT ** 3 == cirq.CliffordGate.CNOT + assert cirq.CliffordGate.CNOT ** -3 == cirq.CliffordGate.CNOT with pytest.raises(TypeError): - _ = cirq.MultipleCliffordGate.Z ** 0.25 + _ = cirq.CliffordGate.Z ** 0.25 def test_stabilizer_effec(): - assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.X) - assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.H) - assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.S) - assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.CNOT) - assert cirq.has_stabilizer_effect(cirq.MultipleCliffordGate.CZ) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.X) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.H) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.S) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.CNOT) + assert cirq.has_stabilizer_effect(cirq.CliffordGate.CZ) qubits = cirq.LineQubit.range(2) - gate = cirq.MultipleCliffordGate.from_op_list( + gate = cirq.CliffordGate.from_op_list( [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits ) assert cirq.has_stabilizer_effect(gate) @@ -641,65 +641,65 @@ def test_clifford_gate_from_op_list(): # Since from_op_list() ==> _act_on_() ==> tableau.then() and then() has already covered # lots of random circuit cases, here we just test a few well-known relationships. qubit = cirq.NamedQubit('test') - gate = cirq.MultipleCliffordGate.from_op_list([cirq.X(qubit), cirq.Z(qubit)], [qubit]) - assert gate == cirq.MultipleCliffordGate.Y # The tableau ignores the global phase + gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Z(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase - gate = cirq.MultipleCliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) - assert gate == cirq.MultipleCliffordGate.Y # The tableau ignores the global phase + gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase - gate = cirq.MultipleCliffordGate.from_op_list([cirq.X(qubit), cirq.Y(qubit)], [qubit]) - assert gate == cirq.MultipleCliffordGate.Z # The tableau ignores the global phase + gate = cirq.CliffordGate.from_op_list([cirq.X(qubit), cirq.Y(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Z # The tableau ignores the global phase - gate = cirq.MultipleCliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) - assert gate == cirq.MultipleCliffordGate.Y # The tableau ignores the global phase + gate = cirq.CliffordGate.from_op_list([cirq.Z(qubit), cirq.X(qubit)], [qubit]) + assert gate == cirq.CliffordGate.Y # The tableau ignores the global phase # Two qubits gates qubits = cirq.LineQubit.range(2) - gate = cirq.MultipleCliffordGate.from_op_list( + gate = cirq.CliffordGate.from_op_list( [cirq.H(qubits[1]), cirq.CZ(*qubits), cirq.H(qubits[1])], qubits ) - assert gate == cirq.MultipleCliffordGate.CNOT + assert gate == cirq.CliffordGate.CNOT - gate = cirq.MultipleCliffordGate.from_op_list( + gate = cirq.CliffordGate.from_op_list( [cirq.H(qubits[1]), cirq.CNOT(*qubits), cirq.H(qubits[1])], qubits ) - assert gate == cirq.MultipleCliffordGate.CZ + assert gate == cirq.CliffordGate.CZ # Note the order of qubits matters - gate = cirq.MultipleCliffordGate.from_op_list( + gate = cirq.CliffordGate.from_op_list( [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits ) - assert gate != cirq.MultipleCliffordGate.CNOT + assert gate != cirq.CliffordGate.CNOT # But if we reverse the qubit_order, they will equal again. - gate = cirq.MultipleCliffordGate.from_op_list( + gate = cirq.CliffordGate.from_op_list( [cirq.H(qubits[0]), cirq.CZ(qubits[1], qubits[0]), cirq.H(qubits[0])], qubits[::-1] ) - assert gate == cirq.MultipleCliffordGate.CNOT + assert gate == cirq.CliffordGate.CNOT with pytest.raises( ValueError, match="only be constructed from the operations that has stabilizer effect" ): - cirq.MultipleCliffordGate.from_op_list([cirq.T(qubit)], [qubit]) + cirq.CliffordGate.from_op_list([cirq.T(qubit)], [qubit]) def test_clifford_gate_from_tableau(): - t = cirq.MultipleCliffordGate.X.clifford_tableau - assert cirq.MultipleCliffordGate.from_clifford_tableau(t) == cirq.MultipleCliffordGate.X + t = cirq.CliffordGate.X.clifford_tableau + assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.X - t = cirq.MultipleCliffordGate.H.clifford_tableau - assert cirq.MultipleCliffordGate.from_clifford_tableau(t) == cirq.MultipleCliffordGate.H + t = cirq.CliffordGate.H.clifford_tableau + assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.H - t = cirq.MultipleCliffordGate.CNOT.clifford_tableau - assert cirq.MultipleCliffordGate.from_clifford_tableau(t) == cirq.MultipleCliffordGate.CNOT + t = cirq.CliffordGate.CNOT.clifford_tableau + assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.CNOT with pytest.raises(ValueError): t = cirq.CliffordTableau(num_qubits=1) t.xs = np.array([1, 1]).reshape(2, 1) t.zs = np.array([1, 1]).reshape(2, 1) # This violates the sympletic property. - cirq.MultipleCliffordGate.from_clifford_tableau(t) + cirq.CliffordGate.from_clifford_tableau(t) with pytest.raises(ValueError, match="Input argument has to be a CliffordTableau instance."): - cirq.MultipleCliffordGate.from_clifford_tableau(1) + cirq.CliffordGate.from_clifford_tableau(1) def test_multi_clifford_decompose_by_unitary(): @@ -714,7 +714,7 @@ def test_multi_clifford_decompose_by_unitary(): g = prng.randint(len(gate_candidate)) indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) - gate = cirq.MultipleCliffordGate.from_op_list(ops, qubits) + gate = cirq.CliffordGate.from_op_list(ops, qubits) decomposed_ops = cirq.decompose(gate.on(*qubits)) circ = cirq.Circuit(decomposed_ops) circ.append(cirq.I.on_each(qubits)) # make sure the dimension aligned. @@ -748,12 +748,12 @@ def test_pad_tableau(): padded_tableau = cirq.ops.clifford_gate._pad_tableau( tableau, num_qubits_after_padding=1, axes=[0] ) - assert padded_tableau == cirq.MultipleCliffordGate.X.clifford_tableau + assert padded_tableau == cirq.CliffordGate.X.clifford_tableau # Tableau for H # [0 1 0] # [1 0 0] - tableau = cirq.MultipleCliffordGate.H.clifford_tableau + tableau = cirq.CliffordGate.H.clifford_tableau padded_tableau = cirq.ops.clifford_gate._pad_tableau( tableau, num_qubits_after_padding=2, axes=[0] ) @@ -770,7 +770,7 @@ def test_pad_tableau(): ) np.testing.assert_equal(padded_tableau.rs.astype(np.int64), np.zeros(4)) # The tableau of H again but pad for another ax - tableau = cirq.MultipleCliffordGate.H.clifford_tableau + tableau = cirq.CliffordGate.H.clifford_tableau padded_tableau = cirq.ops.clifford_gate._pad_tableau( tableau, num_qubits_after_padding=2, axes=[1] ) @@ -805,25 +805,25 @@ def test_clifford_gate_act_on_small_case(): log_of_measurement_results={}, ) cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) - cirq.act_on(cirq.MultipleCliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.CNOT, expected_args, qubits=[qubits[0], qubits[1]], allow_decompose=False) cirq.act_on( - cirq.MultipleCliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False + cirq.CliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False ) assert args.tableau == expected_args.tableau cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False) - cirq.act_on(cirq.MultipleCliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.H, args, qubits=[qubits[0]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.S, expected_args, qubits=[qubits[0]], allow_decompose=False) - cirq.act_on(cirq.MultipleCliffordGate.S, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.S, args, qubits=[qubits[0]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.X, expected_args, qubits=[qubits[2]], allow_decompose=False) - cirq.act_on(cirq.MultipleCliffordGate.X, args, qubits=[qubits[2]], allow_decompose=False) + cirq.act_on(cirq.CliffordGate.X, args, qubits=[qubits[2]], allow_decompose=False) assert args.tableau == expected_args.tableau @@ -849,7 +849,7 @@ def test_clifford_gate_act_on_large_case(): gate_candidate[g], args1, qubits=[qubits[i] for i in indices], allow_decompose=False ) ops.append(gate_candidate[g].on(*[qubits[i] for i in indices])) - compiled_gate = cirq.MultipleCliffordGate.from_op_list(ops, qubits) + compiled_gate = cirq.CliffordGate.from_op_list(ops, qubits) cirq.act_on(compiled_gate, args2, qubits) assert args1.tableau == args2.tableau @@ -865,10 +865,10 @@ def test_clifford_gate_act_on_ch_form(): prng=np.random.RandomState(), log_of_measurement_results={}, ) - cirq.act_on(cirq.MultipleCliffordGate.X, args, qubits=cirq.LineQubit.range(1)) + cirq.act_on(cirq.CliffordGate.X, args, qubits=cirq.LineQubit.range(1)) np.testing.assert_allclose(args.state.state_vector(), np.array([0, 0, 0, 1])) def test_clifford_gate_act_on_fail(): with pytest.raises(TypeError, match="Failed to act"): - cirq.act_on(cirq.MultipleCliffordGate.X, DummyActOnArgs(), qubits=()) + cirq.act_on(cirq.CliffordGate.X, DummyActOnArgs(), qubits=()) diff --git a/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.json b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json similarity index 90% rename from cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.json rename to cirq-core/cirq/protocols/json_test_data/CliffordGate.json index 576e78e9765..0e046535873 100644 --- a/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.json +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.json @@ -1,5 +1,5 @@ { - "cirq_type": "MultipleCliffordGate", + "cirq_type": "CliffordGate", "n": 2, "rs": [ false, diff --git a/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr b/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr new file mode 100644 index 00000000000..dd7ce8c3a06 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/CliffordGate.repr @@ -0,0 +1 @@ +cirq.CliffordGate(_clifford_tableau=cirq.CliffordTableau(num_qubits=2)) diff --git a/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.repr b/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.repr deleted file mode 100644 index 6d859de9a50..00000000000 --- a/cirq-core/cirq/protocols/json_test_data/MultipleCliffordGate.repr +++ /dev/null @@ -1 +0,0 @@ -cirq.MultipleCliffordGate(_clifford_tableau=cirq.CliffordTableau(num_qubits=2)) From 9cc461df08bd46da6967112dc129853ca72d8544 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 16 Feb 2022 21:37:55 -0800 Subject: [PATCH 21/21] Fix the format --- cirq-core/cirq/ops/clifford_gate_test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index 9cdfa3fc2e7..8fa12d6fdf2 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -809,9 +809,7 @@ def test_clifford_gate_act_on_small_case(): assert args.tableau == expected_args.tableau cirq.act_on(cirq.CNOT, expected_args, qubits=[qubits[0], qubits[1]], allow_decompose=False) - cirq.act_on( - cirq.CliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False - ) + cirq.act_on(cirq.CliffordGate.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) assert args.tableau == expected_args.tableau cirq.act_on(cirq.H, expected_args, qubits=[qubits[0]], allow_decompose=False)