From b2d9269699d1e771f2b5b62a7e7273a59f0f6bc7 Mon Sep 17 00:00:00 2001 From: ybc Date: Fri, 28 May 2021 23:29:29 -0700 Subject: [PATCH 01/19] 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/19] 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 1b5b1f1cf62d063a819cb4f27832cd4d48cd26ba Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Fri, 11 Mar 2022 12:47:59 -0800 Subject: [PATCH 03/19] Refactor the single qubit clifford gate --- cirq-core/cirq/ops/clifford_gate.py | 1223 ++++++++++++++------------- 1 file changed, 623 insertions(+), 600 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 3dee8a5ddd5..1898354f0bd 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -32,7 +32,6 @@ from cirq._import import LazyLoader from cirq.ops import ( common_gates, - gate_features, identity, named_qubit, raw_types, @@ -132,374 +131,260 @@ def _validate_map_input( return {frm: PauliTransform(to, flip) for frm, (to, flip) in pauli_map_to.items()} -@value.value_equality -class SingleQubitCliffordGate(gate_features.SingleQubitGate): - """Any single qubit Clifford rotation.""" +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." + ) - I = _pretend_initialized() - H = _pretend_initialized() - X = _pretend_initialized() - Y = _pretend_initialized() - Z = _pretend_initialized() - X_sqrt = _pretend_initialized() - Y_sqrt = _pretend_initialized() - Z_sqrt = _pretend_initialized() - X_nsqrt = _pretend_initialized() - Y_nsqrt = _pretend_initialized() - Z_nsqrt = _pretend_initialized() + 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 - def __init__( - self, - *, - _clifford_tableau: qis.CliffordTableau, - ) -> None: - self._clifford_tableau = _clifford_tableau + +class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf): + """A metaclass used to lazy initialize several common Clifford Gate as class attributes.""" @property - def clifford_tableau(self): - return self._clifford_tableau + def I(cls): + if getattr(cls, '_I', None) is None: + cls._I = cls._generate_clifford_from_known_gate(1, identity.I) + return cls._I - @staticmethod - def from_clifford_tableau(tableau: qis.CliffordTableau) -> 'SingleQubitCliffordGate': - assert isinstance(tableau, qis.CliffordTableau) - if not tableau._validate(): - raise ValueError('It is not a valid Clifford Gate.') - return SingleQubitCliffordGate(_clifford_tableau=tableau) + @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 - @staticmethod - def from_xz_map( - x_to: Tuple[Pauli, bool], z_to: Tuple[Pauli, bool] - ) -> 'SingleQubitCliffordGate': - """Returns a SingleQubitCliffordGate for the specified transforms. - The Y transform is derived from the X and 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 - Args: - x_to: Which Pauli to transform X to and if it should negate. - z_to: Which Pauli to transform Z to and if it should negate. - """ - return SingleQubitCliffordGate.from_clifford_tableau( - _to_clifford_tableau(x_to=PauliTransform(*x_to), z_to=PauliTransform(*z_to)) - ) + @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 - @staticmethod - def from_single_map( - pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None, - *, - x_to: Optional[Tuple[Pauli, bool]] = None, - y_to: Optional[Tuple[Pauli, bool]] = None, - z_to: Optional[Tuple[Pauli, bool]] = None, - ) -> 'SingleQubitCliffordGate': - """Returns a SingleQubitCliffordGate for the - specified transform with a 90 or 180 degree rotation. + @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 - The arguments are exclusive, only one may be specified. + @property + def S(cls): + if getattr(cls, '_S', None) is None: + cls._S = cls._generate_clifford_from_known_gate(1, common_gates.S) + return cls._S - Args: - pauli_map_to: A dictionary with a single key value pair describing - the transform. - x_to: The transform from cirq.X - y_to: The transform from cirq.Y - z_to: The transform from cirq.Z - """ - rotation_map = _validate_map_input(1, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to) - ((trans_from, (trans_to, flip)),) = tuple(rotation_map.items()) - if trans_from == trans_to: - trans_from2 = Pauli.by_relative_index(trans_to, 1) # 1 or 2 work - trans_to2 = Pauli.by_relative_index(trans_from, 1) - flip2 = False - else: - trans_from2 = trans_to - trans_to2 = trans_from - flip2 = not flip - rotation_map[trans_from2] = PauliTransform(trans_to2, flip2) - return SingleQubitCliffordGate.from_double_map( - cast(Dict[Pauli, Tuple[Pauli, bool]], rotation_map) - ) + @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 - @staticmethod - def from_double_map( - pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None, - *, - x_to: Optional[Tuple[Pauli, bool]] = None, - y_to: Optional[Tuple[Pauli, bool]] = None, - z_to: Optional[Tuple[Pauli, bool]] = None, - ) -> 'SingleQubitCliffordGate': - """Returns a SingleQubitCliffordGate for the - specified transform with a 90 or 180 degree rotation. + @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 - Either pauli_map_to or two of (x_to, y_to, z_to) may be specified. + @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 - Args: - pauli_map_to: A dictionary with two key value pairs describing - two transforms. - x_to: The transform from cirq.X - y_to: The transform from cirq.Y - z_to: The transform from cirq.Z - """ - rotation_map = _validate_map_input(2, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to) - (from1, trans1), (from2, trans2) = tuple(rotation_map.items()) - from3 = from1.third(from2) - to3 = trans1.to.third(trans2.to) - flip3 = trans1.flip ^ trans2.flip ^ ((from1 < from2) != (trans1.to < trans2.to)) - rotation_map[from3] = PauliTransform(to3, flip3) + @property + def X_sqrt(cls): + if getattr(cls, '_X_sqrt', None) is None: + # Unfortunately, due the code style, the matrix should be viewed transposed. + # Note xs, zs, and rs are column vector. + # Transformation: X -> X, Z -> -Y + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[0, 1], xs=[[1], [1]], zs=[[0], [1]] + ) + cls._X_sqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._X_sqrt - return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map)) + @property + def Y_sqrt(cls): + if getattr(cls, '_Y_sqrt', None) is None: + # Transformation: X -> -Z, Z -> X + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[1, 0], xs=[[0], [1]], zs=[[1], [0]] + ) + cls._Y_sqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._Y_sqrt - @staticmethod - def from_pauli(pauli: Pauli, sqrt: bool = False) -> 'SingleQubitCliffordGate': - prev_pauli = Pauli.by_relative_index(pauli, -1) - next_pauli = Pauli.by_relative_index(pauli, 1) - if sqrt: - rotation_map = { - prev_pauli: PauliTransform(next_pauli, True), - pauli: PauliTransform(pauli, False), - next_pauli: PauliTransform(prev_pauli, False), - } - else: - rotation_map = { - prev_pauli: PauliTransform(prev_pauli, True), - pauli: PauliTransform(pauli, False), - next_pauli: PauliTransform(next_pauli, True), - } - return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map)) + @property + def Z_sqrt(cls): + if getattr(cls, '_Z_sqrt', None) is None: + # Transformation: X -> Y, Z -> Z + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[0, 0], xs=[[1], [0]], zs=[[1], [1]] + ) + cls._Z_sqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._Z_sqrt - @staticmethod - def from_quarter_turns(pauli: Pauli, quarter_turns: int) -> 'SingleQubitCliffordGate': - quarter_turns = quarter_turns % 4 - if quarter_turns == 0: - return SingleQubitCliffordGate.I - if quarter_turns == 1: - return SingleQubitCliffordGate.from_pauli(pauli, True) - if quarter_turns == 2: - return SingleQubitCliffordGate.from_pauli(pauli) + @property + def X_nsqrt(cls): + if getattr(cls, '_X_nsqrt', None) is None: + # Transformation: X->X, Z->Y + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[0, 0], xs=[[1], [1]], zs=[[0], [1]] + ) + cls._X_nsqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._X_nsqrt - return SingleQubitCliffordGate.from_pauli(pauli, True) ** -1 + @property + def Y_nsqrt(cls): + if getattr(cls, '_Y_nsqrt', None) is None: + # Transformation: X -> Z, Z -> -X + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[0, 1], xs=[[0], [1]], zs=[[1], [0]] + ) + cls._Y_nsqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._Y_nsqrt - @staticmethod - def from_unitary(u: np.ndarray) -> Optional['SingleQubitCliffordGate']: - """Creates Clifford gate with given unitary (up to global phase). + @property + def Z_nsqrt(cls): + if getattr(cls, '_Z_nsqrt', None) is None: + # Transformation: X -> -Y, Z -> Z + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[1, 0], xs=[[1], [0]], zs=[[1], [1]] + ) + cls._Z_nsqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._Z_nsqrt + + # def _get_sqrt_map(cls): + # _x, _y, _z = cls.X, cls.Y, cls.Z + # _x_sqrt, _y_sqrt, _z_sqrt = cls.X_sqrt, cls.Y_sqrt, cls.Z_sqrt + # _x_nsqrt, _y_nsqrt, _z_nsqrt = cls.X_nsqrt, cls.Y_nsqrt, cls.Z_nsqrt + + # return { + # 0.5: { + # _x: _x_sqrt, _y: _y_sqrt, _z: _z_sqrt + # }, + # -0.5: { + # _x: _x_nsqrt, _y: _y_nsqrt, _z: _z_nsqrt + # }, + # } + # return { + # 0.5: { + # cls.X: cls.X_sqrt, + # cls.Y: cls.Y_sqrt, + # cls.Z: cls.Z_sqrt, + # }, + # -0.5: { + # cls.X: cls.X_nsqrt, + # cls.Y: cls.Y_nsqrt, + # cls.Z: cls.Z_nsqrt, + # }, + # } - Args: - u: 2x2 unitary matrix of a Clifford gate. - Returns: - SingleQubitCliffordGate, whose matrix is equal to given matrix (up - to global phase), or `None` if `u` is not a matrix of a single-qubit - Clifford gate. - """ - if u.shape != (2, 2) or not linalg.is_unitary(u): - return None - x = protocols.unitary(pauli_gates.X) - z = protocols.unitary(pauli_gates.Z) - x_to = _to_pauli_transform(u @ x @ u.conj().T) - z_to = _to_pauli_transform(u @ z @ u.conj().T) - if x_to is None or z_to is None: - return None - return SingleQubitCliffordGate.from_clifford_tableau( - _to_clifford_tableau(x_to=x_to, z_to=z_to) +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: int, gate: raw_types.Gate + ) -> 'CliffordGate': + 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={} ) - def transform(self, pauli: Pauli) -> PauliTransform: - x_to = self._clifford_tableau.destabilizers()[0] - z_to = self._clifford_tableau.stabilizers()[0] - if pauli == pauli_gates.X: - to = x_to - elif pauli == pauli_gates.Z: - to = z_to - else: - to = x_to * z_to # Y = iXZ - to.coefficient *= 1j - # pauli_mask returns a value between 0 and 4 for [I, X, Y, Z]. - to_gate = Pauli._XYZ[to.pauli_mask[0] - 1] - return PauliTransform(to=to_gate, flip=bool(to.coefficient != 1.0)) + protocols.act_on(gate, args, qubits, allow_decompose=False) + if num_qubits == 1: + return SingleQubitCliffordGate.from_clifford_tableau(args.tableau) + return CliffordGate.from_clifford_tableau(args.tableau) - def to_phased_xz_gate(self) -> phased_x_z_gate.PhasedXZGate: - """Convert this gate to a PhasedXZGate instance. + @classmethod + def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate': + """Create the CliffordGate instance from Clifford Tableau. - The rotation can be categorized by {axis} * {degree}: - * Identity: I - * {x, y, z} * {90, 180, 270} --- {X, Y, Z} + 6 Quarter turn gates - * {+/-xy, +/-yz, +/-zx} * 180 --- 6 Hadamard-like gates - * {middle point of xyz in 4 Quadrant} * {120, 240} --- swapping axis - note 1 + 9 + 6 + 8 = 24 in total. + 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. - To associate with Clifford Tableau, it can also be grouped by 4: - * {I,X,Y,Z} is [[1 0], [0, 1]] - * {+/- X_sqrt, 2 Hadamard-like gates acting on the YZ plane} is [[1, 0], [1, 1]] - * {+/- Z_sqrt, 2 Hadamard-like gates acting on the XY plane} is [[1, 1], [0, 1]] - * {+/- Y_sqrt, 2 Hadamard-like gates acting on the XZ plane} is [[0, 1], [1, 0]] - * {middle point of xyz in 4 Quadrant} * 120 is [[0, 1], [1, 1]] - * {middle point of xyz in 4 Quadrant} * 240 is [[1, 1], [1, 0]] - """ - x_to_flip, z_to_flip = self.clifford_tableau.rs - flip_index = int(z_to_flip) * 2 + x_to_flip - a, x, z = 0.0, 0.0, 0.0 - - if np.array_equal(self.clifford_tableau.matrix(), [[1, 0], [0, 1]]): - # I, Z, X, Y cases - to_phased_xz = [(0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0), (0.5, 1.0, 0.0)] - a, x, z = to_phased_xz[flip_index] - elif np.array_equal(self.clifford_tableau.matrix(), [[1, 0], [1, 1]]): - # +/- X_sqrt, 2 Hadamard-like gates acting on the YZ plane - a = 0.0 - x = 0.5 if x_to_flip ^ z_to_flip else -0.5 - z = 1.0 if x_to_flip else 0.0 - elif np.array_equal(self.clifford_tableau.matrix(), [[0, 1], [1, 0]]): - # +/- Y_sqrt, 2 Hadamard-like gates acting on the XZ plane - a = 0.5 - x = 0.5 if x_to_flip else -0.5 - z = 0.0 if x_to_flip ^ z_to_flip else 1.0 - elif np.array_equal(self.clifford_tableau.matrix(), [[1, 1], [0, 1]]): - # +/- Z_sqrt, 2 Hadamard-like gates acting on the XY plane - to_phased_xz = [(0.0, 0.0, 0.5), (0.0, 0.0, -0.5), (0.25, 1.0, 0.0), (-0.25, 1.0, 0.0)] - a, x, z = to_phased_xz[flip_index] - elif np.array_equal(self.clifford_tableau.matrix(), [[0, 1], [1, 1]]): - # axis swapping rotation -- (312) permutation - a = 0.5 - x = 0.5 if x_to_flip else -0.5 - z = 0.5 if x_to_flip ^ z_to_flip else -0.5 - else: - # axis swapping rotation -- (231) permutation. - # This should be the only cases left. - assert np.array_equal(self.clifford_tableau.matrix(), [[1, 1], [1, 0]]) - a = 0.0 - x = -0.5 if x_to_flip ^ z_to_flip else 0.5 - z = -0.5 if x_to_flip else 0.5 - - return phased_x_z_gate.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a) - - def _value_equality_values_(self): - return ( - self.transform(pauli_gates.X), - self.transform(pauli_gates.Y), - self.transform(pauli_gates.Z), - ) - - def __pow__(self, exponent) -> 'SingleQubitCliffordGate': - if exponent == 0.5 or exponent == -0.5: - return SQRT_EXP_MAP[exponent][self] - if exponent != -1: - return NotImplemented - - return SingleQubitCliffordGate.from_clifford_tableau(self.clifford_tableau.inverse()) - - def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType]: - if isinstance(other, SingleQubitCliffordGate): - return self.commutes_with_single_qubit_gate(other) - if isinstance(other, Pauli): - return self.commutes_with_pauli(other) - return NotImplemented - - def commutes_with_single_qubit_gate(self, gate: 'SingleQubitCliffordGate') -> bool: - """Tests if the two circuits would be equivalent up to global phase: - --self--gate-- and --gate--self--""" - self_then_gate = self.clifford_tableau.then(gate.clifford_tableau) - gate_then_self = gate.clifford_tableau.then(self.clifford_tableau) - return self_then_gate == gate_then_self - - def commutes_with_pauli(self, pauli: Pauli) -> bool: - to, flip = self.transform(pauli) - return to == pauli and not flip + Returns: + A CliffordGate instance, which has the transformation defined by + the input tableau. - def merged_with(self, second: 'SingleQubitCliffordGate') -> 'SingleQubitCliffordGate': - """Returns a SingleQubitCliffordGate such that the circuits - --output-- and --self--second-- - are equivalent up to global phase.""" - return SingleQubitCliffordGate.from_clifford_tableau( - self.clifford_tableau.then(second.clifford_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 argument has to be a CliffordTableau instance.') + if not tableau._validate(): + raise ValueError('It is not a valid Clifford tableau.') + return CliffordGate(_clifford_tableau=tableau) - def _has_unitary_(self) -> bool: - return True + @classmethod + def from_op_list( + cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] + ) -> 'CliffordGate': + """Construct a new Clifford gates from several known operations. - def _unitary_(self) -> np.ndarray: - mat = np.eye(2) - qubit = named_qubit.NamedQubit('arbitrary') - for op in protocols.decompose_once_with_qubits(self, (qubit,)): - mat = protocols.unitary(op).dot(mat) - return mat + 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. - def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': - (qubit,) = qubits - if self == SingleQubitCliffordGate.H: - return (common_gates.H(qubit),) - rotations = self.decompose_rotation() - return tuple(r.on(qubit) ** (qt / 2) for r, qt in rotations) + Returns: + A CliffordGate instance, which has the transformation on the stabilizer + state equivalent to the composition of operations. - def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]: - """Returns ((first_rotation_axis, first_rotation_quarter_turns), ...) + Raises: + ValueError: When one or more operations do not have stabilizer effect. + """ + 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." + ) - This is a sequence of zero, one, or two rotations.""" - x_rot = self.transform(pauli_gates.X) - y_rot = self.transform(pauli_gates.Y) - z_rot = self.transform(pauli_gates.Z) - whole_arr = ( - x_rot.to == pauli_gates.X, - y_rot.to == pauli_gates.Y, - z_rot.to == pauli_gates.Z, + base_tableau = qis.CliffordTableau(len(qubit_order)) + args = sim.clifford.ActOnCliffordTableauArgs( + tableau=base_tableau, + qubits=qubit_order, + prng=np.random.RandomState(0), # unused + log_of_measurement_results={}, # unused ) - num_whole = sum(whole_arr) - flip_arr = (x_rot.flip, y_rot.flip, z_rot.flip) - num_flip = sum(flip_arr) - if num_whole == 3: - if num_flip == 0: - # Gate is identity - return [] - - # 180 rotation about some axis - pauli = Pauli.by_index(flip_arr.index(False)) - return [(pauli, 2)] - if num_whole == 1: - index = whole_arr.index(True) - pauli = Pauli.by_index(index) - next_pauli = Pauli.by_index(index + 1) - flip = flip_arr[index] - output = [] - if flip: - # 180 degree rotation - output.append((next_pauli, 2)) - # 90 degree rotation about some axis - if self.transform(next_pauli).flip: - # Negative 90 degree rotation - output.append((pauli, -1)) - else: - # Positive 90 degree rotation - output.append((pauli, 1)) - return output - elif num_whole == 0: - # Gate is a 120 degree rotation - if x_rot.to == pauli_gates.Y: - return [ - (pauli_gates.X, -1 if y_rot.flip else 1), - (pauli_gates.Z, -1 if x_rot.flip else 1), - ] - - return [ - (pauli_gates.Z, 1 if y_rot.flip else -1), - (pauli_gates.X, 1 if z_rot.flip else -1), - ] - # coverage: ignore - assert ( - False - ), 'Impossible condition where this gate only rotates one Pauli to a different Pauli.' - - def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQubitCliffordGate': - """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) + for op in operations: + protocols.act_on(op, args, allow_decompose=True) - def __repr__(self) -> str: - x = self.transform(pauli_gates.X) - y = self.transform(pauli_gates.Y) - z = self.transform(pauli_gates.Z) - x_sign = '-' if x.flip else '+' - y_sign = '-' if y.flip else '+' - z_sign = '-' if z.flip else '+' - return ( - f'cirq.SingleQubitCliffordGate(X:{x_sign}{x.to!s}, ' - f'Y:{y_sign}{y.to!s}, Z:{z_sign}{z.to!s})' - ) + return CliffordGate.from_clifford_tableau(args.tableau) @classmethod def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): @@ -511,272 +396,26 @@ def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): ) return cls(_clifford_tableau=_clifford_tableau) - def _json_dict_(self) -> Dict[str, Any]: - json_dict = self._clifford_tableau._json_dict_() - return json_dict - - def _circuit_diagram_info_( - self, args: 'cirq.CircuitDiagramInfoArgs' - ) -> 'cirq.CircuitDiagramInfo': - well_known_map = { - SingleQubitCliffordGate.I: 'I', - SingleQubitCliffordGate.H: 'H', - SingleQubitCliffordGate.X: 'X', - SingleQubitCliffordGate.Y: 'Y', - SingleQubitCliffordGate.Z: 'Z', - SingleQubitCliffordGate.X_sqrt: 'X', - SingleQubitCliffordGate.Y_sqrt: 'Y', - SingleQubitCliffordGate.Z_sqrt: 'Z', - SingleQubitCliffordGate.X_nsqrt: 'X', - SingleQubitCliffordGate.Y_nsqrt: 'Y', - SingleQubitCliffordGate.Z_nsqrt: 'Z', + @classmethod + def _get_sqrt_map(cls) -> Dict[float, Dict['CliffordGate', 'CliffordGate']]: + """Returns a map containing two keys 0.5 and -0.5 for the sqrt mapping of Pauli gates.""" + return { + 0.5: { + cls.X: cls.X_sqrt, + cls.Y: cls.Y_sqrt, + cls.Z: cls.Z_sqrt, + }, + -0.5: { + cls.X: cls.X_nsqrt, + cls.Y: cls.Y_nsqrt, + cls.Z: cls.Z_nsqrt, + }, } - if self in well_known_map: - symbol = well_known_map[self] - else: - rotations = self.decompose_rotation() - symbol = '-'.join(str(r) + ('^' + str(qt / 2)) * (qt % 4 != 2) for r, qt in rotations) - symbol = f'({symbol})' - return protocols.CircuitDiagramInfo( - wire_symbols=(symbol,), - exponent={ - SingleQubitCliffordGate.X_sqrt: 0.5, - SingleQubitCliffordGate.Y_sqrt: 0.5, - SingleQubitCliffordGate.Z_sqrt: 0.5, - SingleQubitCliffordGate.X_nsqrt: -0.5, - SingleQubitCliffordGate.Y_nsqrt: -0.5, - SingleQubitCliffordGate.Z_nsqrt: -0.5, - }.get(self, 1), - ) - - -SingleQubitCliffordGate.I = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.X, False), (pauli_gates.Z, False) -) -SingleQubitCliffordGate.H = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.Z, False), (pauli_gates.X, False) -) -SingleQubitCliffordGate.X = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.X, False), (pauli_gates.Z, True) -) -SingleQubitCliffordGate.Y = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.X, True), (pauli_gates.Z, True) -) -SingleQubitCliffordGate.Z = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.X, True), (pauli_gates.Z, False) -) -SingleQubitCliffordGate.X_sqrt = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.X, False), (pauli_gates.Y, True) -) -SingleQubitCliffordGate.X_nsqrt = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.X, False), (pauli_gates.Y, False) -) -SingleQubitCliffordGate.Y_sqrt = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.Z, True), (pauli_gates.X, False) -) -SingleQubitCliffordGate.Y_nsqrt = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.Z, False), (pauli_gates.X, True) -) -SingleQubitCliffordGate.Z_sqrt = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.Y, False), (pauli_gates.Z, False) -) -SingleQubitCliffordGate.Z_nsqrt = SingleQubitCliffordGate.from_xz_map( - (pauli_gates.Y, True), (pauli_gates.Z, False) -) -SQRT_EXP_MAP = { - 0.5: { - SingleQubitCliffordGate.X: SingleQubitCliffordGate.X_sqrt, - SingleQubitCliffordGate.Y: SingleQubitCliffordGate.Y_sqrt, - SingleQubitCliffordGate.Z: SingleQubitCliffordGate.Z_sqrt, - }, - -0.5: { - SingleQubitCliffordGate.X: SingleQubitCliffordGate.X_nsqrt, - SingleQubitCliffordGate.Y: SingleQubitCliffordGate.Y_nsqrt, - SingleQubitCliffordGate.Z: SingleQubitCliffordGate.Z_nsqrt, - }, -} - -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: - 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, common_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 - - @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: int, gate: raw_types.Gate - ) -> 'CliffordGate': - 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={} - ) - - protocols.act_on(gate, args, qubits, allow_decompose=False) - return CliffordGate.from_clifford_tableau(args.tableau) - - @classmethod - 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 - 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 CliffordGate 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 argument has to be a CliffordTableau instance.') - if not tableau._validate(): - raise ValueError('It is not a valid Clifford tableau.') - return CliffordGate(_clifford_tableau=tableau) - - @classmethod - def from_op_list( - cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid] - ) -> 'CliffordGate': - """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 CliffordGate 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. - """ - 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." - ) - - base_tableau = qis.CliffordTableau(len(qubit_order)) - args = sim.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) - - 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 _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 CliffordGate(raw_types.Gate, CommonCliffordGates): - """Clifford rotation for N-qubit.""" +@value.value_equality +class CliffordGate(raw_types.Gate, CommonCliffordGates): + """Clifford rotation for N-qubit.""" def __init__( self, @@ -813,7 +452,7 @@ def _json_dict_(self) -> Dict[str, Any]: return json_dict def _value_equality_values_(self): - return self.clifford_tableau + return self._clifford_tableau.matrix().tobytes() + self._clifford_tableau.rs.tobytes() def _num_qubits_(self): return self.clifford_tableau.n @@ -878,3 +517,387 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool return NotImplemented return NotImplemented + + +@value.value_equality(manual_cls=True) +class SingleQubitCliffordGate(CliffordGate): + """Any single qubit Clifford rotation.""" + + def __init__( + self, + *, + _clifford_tableau: qis.CliffordTableau, + ) -> None: + super().__init__(_clifford_tableau=_clifford_tableau) + + def _num_qubits_(self): + return 1 + + @staticmethod + def from_clifford_tableau(tableau: qis.CliffordTableau) -> 'SingleQubitCliffordGate': + if not isinstance(tableau, qis.CliffordTableau): + raise ValueError('Input argument has to be a CliffordTableau instance.') + if not tableau._validate(): + raise ValueError('Input tableau is not a valid Clifford tableau.') + if tableau.n != 1: + raise ValueError('The number of input tableau is not 1.') + return SingleQubitCliffordGate(_clifford_tableau=tableau) + + @staticmethod + def from_xz_map( + x_to: Tuple[Pauli, bool], z_to: Tuple[Pauli, bool] + ) -> 'SingleQubitCliffordGate': + """Returns a SingleQubitCliffordGate for the specified transforms. + The Y transform is derived from the X and Z. + + Args: + x_to: Which Pauli to transform X to and if it should negate. + z_to: Which Pauli to transform Z to and if it should negate. + """ + return SingleQubitCliffordGate.from_clifford_tableau( + _to_clifford_tableau(x_to=PauliTransform(*x_to), z_to=PauliTransform(*z_to)) + ) + + @staticmethod + def from_single_map( + pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None, + *, + x_to: Optional[Tuple[Pauli, bool]] = None, + y_to: Optional[Tuple[Pauli, bool]] = None, + z_to: Optional[Tuple[Pauli, bool]] = None, + ) -> 'SingleQubitCliffordGate': + """Returns a SingleQubitCliffordGate for the + specified transform with a 90 or 180 degree rotation. + + The arguments are exclusive, only one may be specified. + + Args: + pauli_map_to: A dictionary with a single key value pair describing + the transform. + x_to: The transform from cirq.X + y_to: The transform from cirq.Y + z_to: The transform from cirq.Z + """ + rotation_map = _validate_map_input(1, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to) + ((trans_from, (trans_to, flip)),) = tuple(rotation_map.items()) + if trans_from == trans_to: + trans_from2 = Pauli.by_relative_index(trans_to, 1) # 1 or 2 work + trans_to2 = Pauli.by_relative_index(trans_from, 1) + flip2 = False + else: + trans_from2 = trans_to + trans_to2 = trans_from + flip2 = not flip + rotation_map[trans_from2] = PauliTransform(trans_to2, flip2) + return SingleQubitCliffordGate.from_double_map( + cast(Dict[Pauli, Tuple[Pauli, bool]], rotation_map) + ) + + @staticmethod + def from_double_map( + pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None, + *, + x_to: Optional[Tuple[Pauli, bool]] = None, + y_to: Optional[Tuple[Pauli, bool]] = None, + z_to: Optional[Tuple[Pauli, bool]] = None, + ) -> 'SingleQubitCliffordGate': + """Returns a SingleQubitCliffordGate for the + specified transform with a 90 or 180 degree rotation. + + Either pauli_map_to or two of (x_to, y_to, z_to) may be specified. + + Args: + pauli_map_to: A dictionary with two key value pairs describing + two transforms. + x_to: The transform from cirq.X + y_to: The transform from cirq.Y + z_to: The transform from cirq.Z + """ + rotation_map = _validate_map_input(2, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to) + (from1, trans1), (from2, trans2) = tuple(rotation_map.items()) + from3 = from1.third(from2) + to3 = trans1.to.third(trans2.to) + flip3 = trans1.flip ^ trans2.flip ^ ((from1 < from2) != (trans1.to < trans2.to)) + rotation_map[from3] = PauliTransform(to3, flip3) + + return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map)) + + @staticmethod + def from_pauli(pauli: Pauli, sqrt: bool = False) -> 'SingleQubitCliffordGate': + prev_pauli = Pauli.by_relative_index(pauli, -1) + next_pauli = Pauli.by_relative_index(pauli, 1) + if sqrt: + rotation_map = { + prev_pauli: PauliTransform(next_pauli, True), + pauli: PauliTransform(pauli, False), + next_pauli: PauliTransform(prev_pauli, False), + } + else: + rotation_map = { + prev_pauli: PauliTransform(prev_pauli, True), + pauli: PauliTransform(pauli, False), + next_pauli: PauliTransform(next_pauli, True), + } + return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map)) + + @staticmethod + def from_quarter_turns(pauli: Pauli, quarter_turns: int) -> 'SingleQubitCliffordGate': + quarter_turns = quarter_turns % 4 + if quarter_turns == 0: + return SingleQubitCliffordGate.I + if quarter_turns == 1: + return SingleQubitCliffordGate.from_pauli(pauli, True) + if quarter_turns == 2: + return SingleQubitCliffordGate.from_pauli(pauli) + + return SingleQubitCliffordGate.from_pauli(pauli, True) ** -1 + + @staticmethod + def from_unitary(u: np.ndarray) -> Optional['SingleQubitCliffordGate']: + """Creates Clifford gate with given unitary (up to global phase). + + Args: + u: 2x2 unitary matrix of a Clifford gate. + + Returns: + SingleQubitCliffordGate, whose matrix is equal to given matrix (up + to global phase), or `None` if `u` is not a matrix of a single-qubit + Clifford gate. + """ + if u.shape != (2, 2) or not linalg.is_unitary(u): + return None + x = protocols.unitary(pauli_gates.X) + z = protocols.unitary(pauli_gates.Z) + x_to = _to_pauli_transform(u @ x @ u.conj().T) + z_to = _to_pauli_transform(u @ z @ u.conj().T) + if x_to is None or z_to is None: + return None + return SingleQubitCliffordGate.from_clifford_tableau( + _to_clifford_tableau(x_to=x_to, z_to=z_to) + ) + + def transform(self, pauli: Pauli) -> PauliTransform: + x_to = self._clifford_tableau.destabilizers()[0] + z_to = self._clifford_tableau.stabilizers()[0] + if pauli == pauli_gates.X: + to = x_to + elif pauli == pauli_gates.Z: + to = z_to + else: + to = x_to * z_to # Y = iXZ + to.coefficient *= 1j + # pauli_mask returns a value between 0 and 4 for [I, X, Y, Z]. + to_gate = Pauli._XYZ[to.pauli_mask[0] - 1] + return PauliTransform(to=to_gate, flip=bool(to.coefficient != 1.0)) + + def to_phased_xz_gate(self) -> phased_x_z_gate.PhasedXZGate: + """Convert this gate to a PhasedXZGate instance. + + The rotation can be categorized by {axis} * {degree}: + * Identity: I + * {x, y, z} * {90, 180, 270} --- {X, Y, Z} + 6 Quarter turn gates + * {+/-xy, +/-yz, +/-zx} * 180 --- 6 Hadamard-like gates + * {middle point of xyz in 4 Quadrant} * {120, 240} --- swapping axis + note 1 + 9 + 6 + 8 = 24 in total. + + To associate with Clifford Tableau, it can also be grouped by 4: + * {I,X,Y,Z} is [[1 0], [0, 1]] + * {+/- X_sqrt, 2 Hadamard-like gates acting on the YZ plane} is [[1, 0], [1, 1]] + * {+/- Z_sqrt, 2 Hadamard-like gates acting on the XY plane} is [[1, 1], [0, 1]] + * {+/- Y_sqrt, 2 Hadamard-like gates acting on the XZ plane} is [[0, 1], [1, 0]] + * {middle point of xyz in 4 Quadrant} * 120 is [[0, 1], [1, 1]] + * {middle point of xyz in 4 Quadrant} * 240 is [[1, 1], [1, 0]] + """ + x_to_flip, z_to_flip = self.clifford_tableau.rs + flip_index = int(z_to_flip) * 2 + x_to_flip + a, x, z = 0.0, 0.0, 0.0 + + if np.array_equal(self.clifford_tableau.matrix(), [[1, 0], [0, 1]]): + # I, Z, X, Y cases + to_phased_xz = [(0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0), (0.5, 1.0, 0.0)] + a, x, z = to_phased_xz[flip_index] + elif np.array_equal(self.clifford_tableau.matrix(), [[1, 0], [1, 1]]): + # +/- X_sqrt, 2 Hadamard-like gates acting on the YZ plane + a = 0.0 + x = 0.5 if x_to_flip ^ z_to_flip else -0.5 + z = 1.0 if x_to_flip else 0.0 + elif np.array_equal(self.clifford_tableau.matrix(), [[0, 1], [1, 0]]): + # +/- Y_sqrt, 2 Hadamard-like gates acting on the XZ plane + a = 0.5 + x = 0.5 if x_to_flip else -0.5 + z = 0.0 if x_to_flip ^ z_to_flip else 1.0 + elif np.array_equal(self.clifford_tableau.matrix(), [[1, 1], [0, 1]]): + # +/- Z_sqrt, 2 Hadamard-like gates acting on the XY plane + to_phased_xz = [(0.0, 0.0, 0.5), (0.0, 0.0, -0.5), (0.25, 1.0, 0.0), (-0.25, 1.0, 0.0)] + a, x, z = to_phased_xz[flip_index] + elif np.array_equal(self.clifford_tableau.matrix(), [[0, 1], [1, 1]]): + # axis swapping rotation -- (312) permutation + a = 0.5 + x = 0.5 if x_to_flip else -0.5 + z = 0.5 if x_to_flip ^ z_to_flip else -0.5 + else: + # axis swapping rotation -- (231) permutation. + # This should be the only cases left. + assert np.array_equal(self.clifford_tableau.matrix(), [[1, 1], [1, 0]]) + a = 0.0 + x = -0.5 if x_to_flip ^ z_to_flip else 0.5 + z = -0.5 if x_to_flip else 0.5 + + return phased_x_z_gate.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a) + + def __pow__(self, exponent) -> 'SingleQubitCliffordGate': + if exponent == 0.5 or exponent == -0.5: + return self._get_sqrt_map()[exponent][self] + return super().__pow__(exponent) + + def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType]: + if isinstance(other, SingleQubitCliffordGate): + return self.commutes_with_single_qubit_gate(other) + if isinstance(other, Pauli): + return self.commutes_with_pauli(other) + return NotImplemented + + def commutes_with_single_qubit_gate(self, gate: 'SingleQubitCliffordGate') -> bool: + """Tests if the two circuits would be equivalent up to global phase: + --self--gate-- and --gate--self--""" + self_then_gate = self.clifford_tableau.then(gate.clifford_tableau) + gate_then_self = gate.clifford_tableau.then(self.clifford_tableau) + return self_then_gate == gate_then_self + + def commutes_with_pauli(self, pauli: Pauli) -> bool: + to, flip = self.transform(pauli) + return to == pauli and not flip + + def merged_with(self, second: 'SingleQubitCliffordGate') -> 'SingleQubitCliffordGate': + """Returns a SingleQubitCliffordGate such that the circuits + --output-- and --self--second-- + are equivalent up to global phase.""" + return SingleQubitCliffordGate.from_clifford_tableau( + self.clifford_tableau.then(second.clifford_tableau) + ) + + def _has_unitary_(self) -> bool: + return True + + def _unitary_(self) -> np.ndarray: + mat = np.eye(2) + qubit = named_qubit.NamedQubit('arbitrary') + for op in protocols.decompose_once_with_qubits(self, (qubit,)): + mat = protocols.unitary(op).dot(mat) + return mat + + def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]: + """Returns ((first_rotation_axis, first_rotation_quarter_turns), ...) + + This is a sequence of zero, one, or two rotations.""" + x_rot = self.transform(pauli_gates.X) + y_rot = self.transform(pauli_gates.Y) + z_rot = self.transform(pauli_gates.Z) + whole_arr = ( + x_rot.to == pauli_gates.X, + y_rot.to == pauli_gates.Y, + z_rot.to == pauli_gates.Z, + ) + num_whole = sum(whole_arr) + flip_arr = (x_rot.flip, y_rot.flip, z_rot.flip) + num_flip = sum(flip_arr) + if num_whole == 3: + if num_flip == 0: + # Gate is identity + return [] + + # 180 rotation about some axis + pauli = Pauli.by_index(flip_arr.index(False)) + return [(pauli, 2)] + if num_whole == 1: + index = whole_arr.index(True) + pauli = Pauli.by_index(index) + next_pauli = Pauli.by_index(index + 1) + flip = flip_arr[index] + output = [] + if flip: + # 180 degree rotation + output.append((next_pauli, 2)) + # 90 degree rotation about some axis + if self.transform(next_pauli).flip: + # Negative 90 degree rotation + output.append((pauli, -1)) + else: + # Positive 90 degree rotation + output.append((pauli, 1)) + return output + elif num_whole == 0: + # Gate is a 120 degree rotation + if x_rot.to == pauli_gates.Y: + return [ + (pauli_gates.X, -1 if y_rot.flip else 1), + (pauli_gates.Z, -1 if x_rot.flip else 1), + ] + + return [ + (pauli_gates.Z, 1 if y_rot.flip else -1), + (pauli_gates.X, 1 if z_rot.flip else -1), + ] + # coverage: ignore + assert ( + False + ), 'Impossible condition where this gate only rotates one Pauli to a different Pauli.' + + def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQubitCliffordGate': + """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) + + def __repr__(self) -> str: + x = self.transform(pauli_gates.X) + y = self.transform(pauli_gates.Y) + z = self.transform(pauli_gates.Z) + x_sign = '-' if x.flip else '+' + y_sign = '-' if y.flip else '+' + z_sign = '-' if z.flip else '+' + return ( + f'cirq.SingleQubitCliffordGate(X:{x_sign}{x.to!s}, ' + f'Y:{y_sign}{y.to!s}, Z:{z_sign}{z.to!s})' + ) + + def _circuit_diagram_info_( + self, args: 'cirq.CircuitDiagramInfoArgs' + ) -> 'cirq.CircuitDiagramInfo': + well_known_map = { + SingleQubitCliffordGate.I: 'I', + SingleQubitCliffordGate.H: 'H', + SingleQubitCliffordGate.X: 'X', + SingleQubitCliffordGate.Y: 'Y', + SingleQubitCliffordGate.Z: 'Z', + SingleQubitCliffordGate.X_sqrt: 'X', + SingleQubitCliffordGate.Y_sqrt: 'Y', + SingleQubitCliffordGate.Z_sqrt: 'Z', + SingleQubitCliffordGate.X_nsqrt: 'X', + SingleQubitCliffordGate.Y_nsqrt: 'Y', + SingleQubitCliffordGate.Z_nsqrt: 'Z', + } + if self in well_known_map: + symbol = well_known_map[self] + else: + rotations = self.decompose_rotation() + symbol = '-'.join(str(r) + ('^' + str(qt / 2)) * (qt % 4 != 2) for r, qt in rotations) + symbol = f'({symbol})' + return protocols.CircuitDiagramInfo( + wire_symbols=(symbol,), + exponent={ + SingleQubitCliffordGate.X_sqrt: 0.5, + SingleQubitCliffordGate.Y_sqrt: 0.5, + SingleQubitCliffordGate.Z_sqrt: 0.5, + SingleQubitCliffordGate.X_nsqrt: -0.5, + SingleQubitCliffordGate.Y_nsqrt: -0.5, + SingleQubitCliffordGate.Z_nsqrt: -0.5, + }.get(self, 1), + ) + + def _value_equality_values_(self): + return self._clifford_tableau.matrix().tobytes() + self._clifford_tableau.rs.tobytes() + + def _value_equality_values_cls_(self): + """To make it with compatible to compare with clifford gate.""" + return CliffordGate From 389c520e46882f1b69b12887df8102ede945b017 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Fri, 11 Mar 2022 14:21:20 -0800 Subject: [PATCH 04/19] Fix the error of X, Y, Z definition --- cirq-core/cirq/ops/clifford_gate.py | 39 +++++------------------------ 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 1898354f0bd..e594dcf12be 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -167,18 +167,18 @@ def I(cls): @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 + cls._X = cls._generate_clifford_from_known_gate(1, pauli_gates.X) + return cls._X @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 + if getattr(cls, '_Y', None) is None: + cls._Y = cls._generate_clifford_from_known_gate(1, pauli_gates.Y) + return cls._Y @property def Z(cls): - if getattr(cls, '_X', None) is None: + if getattr(cls, '_Z', None) is None: cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.Z) return cls._Z @@ -274,33 +274,6 @@ def Z_nsqrt(cls): cls._Z_nsqrt = cls.from_clifford_tableau(_clifford_tableau) return cls._Z_nsqrt - # def _get_sqrt_map(cls): - # _x, _y, _z = cls.X, cls.Y, cls.Z - # _x_sqrt, _y_sqrt, _z_sqrt = cls.X_sqrt, cls.Y_sqrt, cls.Z_sqrt - # _x_nsqrt, _y_nsqrt, _z_nsqrt = cls.X_nsqrt, cls.Y_nsqrt, cls.Z_nsqrt - - # return { - # 0.5: { - # _x: _x_sqrt, _y: _y_sqrt, _z: _z_sqrt - # }, - # -0.5: { - # _x: _x_nsqrt, _y: _y_nsqrt, _z: _z_nsqrt - # }, - # } - # return { - # 0.5: { - # cls.X: cls.X_sqrt, - # cls.Y: cls.Y_sqrt, - # cls.Z: cls.Z_sqrt, - # }, - # -0.5: { - # cls.X: cls.X_nsqrt, - # cls.Y: cls.Y_nsqrt, - # cls.Z: cls.Z_nsqrt, - # }, - # } - - class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): # We need to use the lazy initialization of these common gates since they need to use From c23ba2fdb804917fb8e9558803c9b3f48e6ff3ea Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Mon, 14 Mar 2022 21:49:36 -0700 Subject: [PATCH 05/19] Format and mypy fix --- cirq-core/cirq/ops/clifford_gate.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index e594dcf12be..0743638c7d9 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -274,6 +274,7 @@ def Z_nsqrt(cls): cls._Z_nsqrt = cls.from_clifford_tableau(_clifford_tableau) return cls._Z_nsqrt + class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): # We need to use the lazy initialization of these common gates since they need to use @@ -281,7 +282,7 @@ class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass): @classmethod def _generate_clifford_from_known_gate( cls, num_qubits: int, gate: raw_types.Gate - ) -> 'CliffordGate': + ) -> Union['SingleQubitCliffordGate', 'CliffordGate']: qubits = devices.LineQubit.range(num_qubits) t = qis.CliffordTableau(num_qubits=num_qubits) args = sim.ActOnCliffordTableauArgs( @@ -468,7 +469,9 @@ def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation] list(qubits), self.clifford_tableau ) - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: + def _act_on_( + self, args: 'cirq.OperationTarget', qubits: Sequence['cirq.Qid'] + ) -> Union[NotImplementedType, bool]: # Note the computation complexity difference between _decompose_ and _act_on_. # Suppose this Gate has `m` qubits, args has `n` qubits, and the decomposition of From 3011fc54ef2a6808c385f9de639c23b9305da0b2 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Mon, 14 Mar 2022 22:04:59 -0700 Subject: [PATCH 06/19] Update the __pow__ signature --- cirq-core/cirq/ops/clifford_gate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 4709fcfd9ab..edcc0aad487 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -724,7 +724,10 @@ def to_phased_xz_gate(self) -> phased_x_z_gate.PhasedXZGate: def __pow__(self, exponent) -> 'SingleQubitCliffordGate': if exponent == 0.5 or exponent == -0.5: return self._get_sqrt_map()[exponent][self] - return super().__pow__(exponent) + ret_gate = super().__pow__(exponent) + if ret_gate is NotImplemented: + return NotImplemented + return SingleQubitCliffordGate.from_clifford_tableau(ret_gate.clifford_tableau) def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType]: if isinstance(other, SingleQubitCliffordGate): From 7d50aaea2bdc5dde11f9555787f48cdb16dd9b05 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sat, 19 Mar 2022 21:32:23 -0700 Subject: [PATCH 07/19] Fix the _decompose_ --- cirq-core/cirq/ops/clifford_gate.py | 54 +++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index edcc0aad487..cf9c9ac0e56 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -224,26 +224,6 @@ def X_sqrt(cls): cls._X_sqrt = cls.from_clifford_tableau(_clifford_tableau) return cls._X_sqrt - @property - def Y_sqrt(cls): - if getattr(cls, '_Y_sqrt', None) is None: - # Transformation: X -> -Z, Z -> X - _clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[1, 0], xs=[[0], [1]], zs=[[1], [0]] - ) - cls._Y_sqrt = cls.from_clifford_tableau(_clifford_tableau) - return cls._Y_sqrt - - @property - def Z_sqrt(cls): - if getattr(cls, '_Z_sqrt', None) is None: - # Transformation: X -> Y, Z -> Z - _clifford_tableau = qis.CliffordTableau._from_json_dict_( - n=1, rs=[0, 0], xs=[[1], [0]], zs=[[1], [1]] - ) - cls._Z_sqrt = cls.from_clifford_tableau(_clifford_tableau) - return cls._Z_sqrt - @property def X_nsqrt(cls): if getattr(cls, '_X_nsqrt', None) is None: @@ -254,6 +234,16 @@ def X_nsqrt(cls): cls._X_nsqrt = cls.from_clifford_tableau(_clifford_tableau) return cls._X_nsqrt + @property + def Y_sqrt(cls): + if getattr(cls, '_Y_sqrt', None) is None: + # Transformation: X -> -Z, Z -> X + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[1, 0], xs=[[0], [1]], zs=[[1], [0]] + ) + cls._Y_sqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._Y_sqrt + @property def Y_nsqrt(cls): if getattr(cls, '_Y_nsqrt', None) is None: @@ -264,6 +254,16 @@ def Y_nsqrt(cls): cls._Y_nsqrt = cls.from_clifford_tableau(_clifford_tableau) return cls._Y_nsqrt + @property + def Z_sqrt(cls): + if getattr(cls, '_Z_sqrt', None) is None: + # Transformation: X -> Y, Z -> Z + _clifford_tableau = qis.CliffordTableau._from_json_dict_( + n=1, rs=[0, 0], xs=[[1], [0]], zs=[[1], [1]] + ) + cls._Z_sqrt = cls.from_clifford_tableau(_clifford_tableau) + return cls._Z_sqrt + @property def Z_nsqrt(cls): if getattr(cls, '_Z_nsqrt', None) is None: @@ -722,13 +722,23 @@ def to_phased_xz_gate(self) -> phased_x_z_gate.PhasedXZGate: return phased_x_z_gate.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a) def __pow__(self, exponent) -> 'SingleQubitCliffordGate': - if exponent == 0.5 or exponent == -0.5: - return self._get_sqrt_map()[exponent][self] + if self._get_sqrt_map().get(exponent, None): + ret_gate = self._get_sqrt_map()[exponent].get(self, None) + if ret_gate: + return ret_gate ret_gate = super().__pow__(exponent) if ret_gate is NotImplemented: return NotImplemented return SingleQubitCliffordGate.from_clifford_tableau(ret_gate.clifford_tableau) + # Single Clifford Gate decomposition can be more efficient than the general Tableau decomposition. + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': + (qubit,) = qubits + if self == SingleQubitCliffordGate.H: + return (common_gates.H(qubit),) + rotations = self.decompose_rotation() + return tuple(r.on(qubit) ** (qt / 2) for r, qt in rotations) + def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType]: if isinstance(other, SingleQubitCliffordGate): return self.commutes_with_single_qubit_gate(other) From 56909f3d03ea046558277bf5be23b99526c1c335 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sun, 20 Mar 2022 16:07:39 -0700 Subject: [PATCH 08/19] Fix the format and lint --- cirq-core/cirq/ops/clifford_gate.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index fc6363340c4..61ff8a2fb6b 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -286,7 +286,9 @@ def _generate_clifford_from_known_gate( 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={} + tableau=t, + qubits=qubits, + prng=np.random.RandomState(), ) protocols.act_on(gate, args, qubits, allow_decompose=False) @@ -353,7 +355,6 @@ def from_op_list( 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) @@ -386,6 +387,7 @@ def _get_sqrt_map(cls) -> Dict[float, Dict['CliffordGate', 'CliffordGate']]: }, } + @value.value_equality class CliffordGate(raw_types.Gate, CommonCliffordGates): """Clifford rotation for N-qubit.""" @@ -463,7 +465,7 @@ def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, # it is because Clifford tableau ignores the global phase information. return NotImplemented - def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation]: + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': return transformers.analytical_decompositions.decompose_clifford_tableau_to_operations( list(qubits), self.clifford_tableau ) @@ -730,7 +732,7 @@ def __pow__(self, exponent) -> 'SingleQubitCliffordGate': return NotImplemented return SingleQubitCliffordGate.from_clifford_tableau(ret_gate.clifford_tableau) - # Single Clifford Gate decomposition can be more efficient than the general Tableau decomposition. + # Single Clifford Gate decomposition is more efficient than the general Tableau decomposition. def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': (qubit,) = qubits if self == SingleQubitCliffordGate.H: From 90ee79643e755dd83f09923a82559d69a75b97a6 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sun, 20 Mar 2022 16:33:38 -0700 Subject: [PATCH 09/19] Type and protocol test --- cirq-core/cirq/ops/clifford_gate.py | 4 +++- cirq-core/cirq/testing/consistent_protocols.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 61ff8a2fb6b..f1afba6e628 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -372,7 +372,9 @@ def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): return cls(_clifford_tableau=_clifford_tableau) @classmethod - def _get_sqrt_map(cls) -> Dict[float, Dict['CliffordGate', 'CliffordGate']]: + def _get_sqrt_map( + cls, + ) -> Dict[float, Dict['SingleQubitCliffordGate', 'SingleQubitCliffordGate']]: """Returns a map containing two keys 0.5 and -0.5 for the sqrt mapping of Pauli gates.""" return { 0.5: { diff --git a/cirq-core/cirq/testing/consistent_protocols.py b/cirq-core/cirq/testing/consistent_protocols.py index 51d44a77416..475e80298ae 100644 --- a/cirq-core/cirq/testing/consistent_protocols.py +++ b/cirq-core/cirq/testing/consistent_protocols.py @@ -49,7 +49,7 @@ def assert_implements_consistent_protocols( val: Any, *, - exponents: Sequence[Any] = (0, 1, -1, 0.25, -0.5, 0.1, sympy.Symbol('s')), + exponents: Sequence[Any] = (0, 1, -1, 0.25, 0.1, sympy.Symbol('s')), qubit_count: Optional[int] = None, ignoring_global_phase: bool = False, setup_code: str = 'import cirq\nimport numpy as np\nimport sympy', From 72a3762496125a7d0ad6d751351aee4b36c1c9c0 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sun, 20 Mar 2022 16:44:33 -0700 Subject: [PATCH 10/19] More types --- cirq-core/cirq/ops/clifford_gate.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index f1afba6e628..49db43f54e5 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -725,10 +725,12 @@ def to_phased_xz_gate(self) -> phased_x_z_gate.PhasedXZGate: return phased_x_z_gate.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a) def __pow__(self, exponent) -> 'SingleQubitCliffordGate': + # First to check if we can get the sqrt and negative sqrt Clifford. if self._get_sqrt_map().get(exponent, None): - ret_gate = self._get_sqrt_map()[exponent].get(self, None) - if ret_gate: - return ret_gate + pow_gate = self._get_sqrt_map()[exponent].get(self, None) + if pow_gate: + return pow_gate + # If not, we try the Clifford Tableau based method. ret_gate = super().__pow__(exponent) if ret_gate is NotImplemented: return NotImplemented From ae63e355634518dc11f6bc93957d640730823d1d Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sun, 20 Mar 2022 17:02:29 -0700 Subject: [PATCH 11/19] Test if we disable allow_decompose will it pass --- cirq-core/cirq/testing/consistent_act_on.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/testing/consistent_act_on.py b/cirq-core/cirq/testing/consistent_act_on.py index f4b617741a3..b0a6755a6be 100644 --- a/cirq-core/cirq/testing/consistent_act_on.py +++ b/cirq-core/cirq/testing/consistent_act_on.py @@ -166,7 +166,7 @@ def _final_clifford_tableau( ) for op in circuit.all_operations(): try: - protocols.act_on(op, args, allow_decompose=True) + protocols.act_on(op, args, allow_decompose=False) except TypeError: return None return tableau From 0bef7e8010f9ac50685e71e9114b3c30b25afee0 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sun, 20 Mar 2022 18:34:51 -0700 Subject: [PATCH 12/19] Test disable _act_on_ --- cirq-core/cirq/ops/clifford_gate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 49db43f54e5..b4f833a7592 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -736,6 +736,14 @@ def __pow__(self, exponent) -> 'SingleQubitCliffordGate': return NotImplemented return SingleQubitCliffordGate.from_clifford_tableau(ret_gate.clifford_tableau) + def _act_on_( + self, + args: 'cirq.OperationTarget', # pylint: disable=unused-argument + qubits: Sequence['cirq.Qid'], # pylint: disable=unused-argument + ): + # TODO + return NotImplemented + # Single Clifford Gate decomposition is more efficient than the general Tableau decomposition. def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': (qubit,) = qubits From 2210c6a05cafb60bf70856d5af89c63f3992c4d6 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 23 Mar 2022 21:03:03 -0700 Subject: [PATCH 13/19] Fix the coverage --- cirq-core/cirq/ops/clifford_gate.py | 6 ++++-- cirq-core/cirq/ops/clifford_gate_test.py | 7 +++++++ cirq-core/cirq/testing/consistent_act_on.py | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index b4f833a7592..120361e7971 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -519,7 +519,9 @@ def from_clifford_tableau(tableau: qis.CliffordTableau) -> 'SingleQubitCliffordG if not tableau._validate(): raise ValueError('Input tableau is not a valid Clifford tableau.') if tableau.n != 1: - raise ValueError('The number of input tableau is not 1.') + raise ValueError( + 'The number of qubit of input tableau should be 1 for SingleQubitCliffordGate.' + ) return SingleQubitCliffordGate(_clifford_tableau=tableau) @staticmethod @@ -741,7 +743,7 @@ def _act_on_( args: 'cirq.OperationTarget', # pylint: disable=unused-argument qubits: Sequence['cirq.Qid'], # pylint: disable=unused-argument ): - # TODO + # TODO(PR number) will create a PR if we agree on adding this. return NotImplemented # Single Clifford Gate decomposition is more efficient than the general Tableau decomposition. diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index f430f987d60..89a5681949d 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -692,6 +692,13 @@ def test_clifford_gate_from_tableau(): t = cirq.CliffordGate.CNOT.clifford_tableau assert cirq.CliffordGate.from_clifford_tableau(t) == cirq.CliffordGate.CNOT + with pytest.raises(ValueError, match='Input argument has to be a CliffordTableau instance.'): + cirq.SingleQubitCliffordGate.from_clifford_tableau(123) + + with pytest.raises(ValueError, match="The number of qubit of input tableau should be 1"): + t = cirq.CliffordTableau(num_qubits=2) + cirq.SingleQubitCliffordGate.from_clifford_tableau(t) + with pytest.raises(ValueError): t = cirq.CliffordTableau(num_qubits=1) t.xs = np.array([1, 1]).reshape(2, 1) diff --git a/cirq-core/cirq/testing/consistent_act_on.py b/cirq-core/cirq/testing/consistent_act_on.py index b0a6755a6be..f4b617741a3 100644 --- a/cirq-core/cirq/testing/consistent_act_on.py +++ b/cirq-core/cirq/testing/consistent_act_on.py @@ -166,7 +166,7 @@ def _final_clifford_tableau( ) for op in circuit.all_operations(): try: - protocols.act_on(op, args, allow_decompose=False) + protocols.act_on(op, args, allow_decompose=True) except TypeError: return None return tableau From 61f5545a1afce532691b7f96aaf622a852122dd5 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 23 Mar 2022 21:34:20 -0700 Subject: [PATCH 14/19] Revert the change in consistent_protocol --- cirq-core/cirq/testing/consistent_protocols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/testing/consistent_protocols.py b/cirq-core/cirq/testing/consistent_protocols.py index 61b67bf8d0d..8a759384b58 100644 --- a/cirq-core/cirq/testing/consistent_protocols.py +++ b/cirq-core/cirq/testing/consistent_protocols.py @@ -50,7 +50,7 @@ def assert_implements_consistent_protocols( val: Any, *, - exponents: Sequence[Any] = (0, 1, -1, 0.25, 0.1, sympy.Symbol('s')), + exponents: Sequence[Any] = (0, 1, -1, 0.25, -0.5, 0.1, sympy.Symbol('s')), qubit_count: Optional[int] = None, ignoring_global_phase: bool = False, setup_code: str = 'import cirq\nimport numpy as np\nimport sympy', From a64849ebcde393b038a43cc8ed7a781a68a9eb77 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 12 Apr 2022 22:08:01 -0700 Subject: [PATCH 15/19] format and add PR # in todo. --- cirq-core/cirq/ops/clifford_gate.py | 2 +- cirq-core/cirq/ops/clifford_gate_test.py | 70 ++++++++++++------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 8af8faae1f6..361fe5a81d9 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -745,7 +745,7 @@ def _act_on_( args: 'cirq.OperationTarget', # pylint: disable=unused-argument qubits: Sequence['cirq.Qid'], # pylint: disable=unused-argument ): - # TODO(PR number) will create a PR if we agree on adding this. + # TODO(#5256) Add the implementation of _act_on_ with ActOnCliffordTableauArgs. return NotImplemented # Single Clifford Gate decomposition is more efficient than the general Tableau decomposition. diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index f9dee75169c..a075d68ec98 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), @@ -589,7 +589,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 @@ -630,15 +630,15 @@ def test_common_clifford_gate_caching(clifford_gate_name): 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.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.CliffordGate.Z**0.25 + _ = cirq.CliffordGate.Z ** 0.25 def test_stabilizer_effec(): From 9f2dee1bc9beddfe20c591b2d543a80ab4c3be2d Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Tue, 12 Apr 2022 23:42:04 -0700 Subject: [PATCH 16/19] format --- cirq-core/cirq/ops/clifford_gate.py | 4 +- cirq-core/cirq/ops/clifford_gate_test.py | 70 ++++++++++++------------ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 361fe5a81d9..87621700e74 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -756,7 +756,7 @@ def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': rotations = self.decompose_rotation() return tuple(r.on(qubit) ** (qt / 2) for r, qt in rotations) - def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType]: + def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: if isinstance(other, SingleQubitCliffordGate): return self.commutes_with_single_qubit_gate(other) if isinstance(other, Pauli): @@ -853,7 +853,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) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index a075d68ec98..f9dee75169c 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), @@ -589,7 +589,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 @@ -630,15 +630,15 @@ def test_common_clifford_gate_caching(clifford_gate_name): 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.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.CliffordGate.Z ** 0.25 + _ = cirq.CliffordGate.Z**0.25 def test_stabilizer_effec(): From f40b4343ffc70b1521e0097d77028b211c911f96 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 13 Apr 2022 23:21:46 -0700 Subject: [PATCH 17/19] Fix the format --- cirq-core/cirq/ops/clifford_gate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index 87621700e74..da3d0fc552f 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -756,7 +756,9 @@ def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': rotations = self.decompose_rotation() return tuple(r.on(qubit) ** (qt / 2) for r, qt in rotations) - def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]: + def _commutes_( + self, other: Any, *, atol: float = 1e-8 + ) -> Union[bool, NotImplementedType, None]: if isinstance(other, SingleQubitCliffordGate): return self.commutes_with_single_qubit_gate(other) if isinstance(other, Pauli): From 6785576c70eb180f5f0e3bfbb17a94f9b4b2c904 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Wed, 13 Apr 2022 23:37:23 -0700 Subject: [PATCH 18/19] Format --- cirq-core/cirq/ops/clifford_gate.py | 50 +++++------------------------ 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py index da3d0fc552f..203031fdc96 100644 --- a/cirq-core/cirq/ops/clifford_gate.py +++ b/cirq-core/cirq/ops/clifford_gate.py @@ -30,14 +30,7 @@ from cirq import protocols, value, linalg, qis from cirq._doc import document from cirq._import import LazyLoader -from cirq.ops import ( - common_gates, - identity, - named_qubit, - raw_types, - pauli_gates, - phased_x_z_gate, -) +from cirq.ops import common_gates, identity, named_qubit, raw_types, pauli_gates, phased_x_z_gate from cirq.ops.pauli_gates import Pauli from cirq.type_workarounds import NotImplementedType @@ -285,11 +278,7 @@ def _generate_clifford_from_known_gate( ) -> Union['SingleQubitCliffordGate', 'CliffordGate']: qubits = devices.LineQubit.range(num_qubits) t = qis.CliffordTableau(num_qubits=num_qubits) - args = sim.ActOnCliffordTableauArgs( - tableau=t, - qubits=qubits, - prng=np.random.RandomState(), - ) + args = sim.ActOnCliffordTableauArgs(tableau=t, qubits=qubits, prng=np.random.RandomState()) protocols.act_on(gate, args, qubits, allow_decompose=False) if num_qubits == 1: @@ -352,9 +341,7 @@ def from_op_list( base_tableau = qis.CliffordTableau(len(qubit_order)) args = sim.clifford.ActOnCliffordTableauArgs( - tableau=base_tableau, - qubits=qubit_order, - prng=np.random.RandomState(0), # unused + tableau=base_tableau, qubits=qubit_order, prng=np.random.RandomState(0) # unused ) for op in operations: protocols.act_on(op, args, allow_decompose=True) @@ -363,12 +350,7 @@ def from_op_list( @classmethod def _from_json_dict_(cls, n, rs, xs, zs, **kwargs): - _clifford_tableau = qis.CliffordTableau._from_json_dict_( - n, - rs, - xs, - zs, - ) + _clifford_tableau = qis.CliffordTableau._from_json_dict_(n, rs, xs, zs) return cls(_clifford_tableau=_clifford_tableau) @classmethod @@ -377,16 +359,8 @@ def _get_sqrt_map( ) -> Dict[float, Dict['SingleQubitCliffordGate', 'SingleQubitCliffordGate']]: """Returns a map containing two keys 0.5 and -0.5 for the sqrt mapping of Pauli gates.""" return { - 0.5: { - cls.X: cls.X_sqrt, - cls.Y: cls.Y_sqrt, - cls.Z: cls.Z_sqrt, - }, - -0.5: { - cls.X: cls.X_nsqrt, - cls.Y: cls.Y_nsqrt, - cls.Z: cls.Z_nsqrt, - }, + 0.5: {cls.X: cls.X_sqrt, cls.Y: cls.Y_sqrt, cls.Z: cls.Z_sqrt}, + -0.5: {cls.X: cls.X_nsqrt, cls.Y: cls.Y_nsqrt, cls.Z: cls.Z_nsqrt}, } @@ -394,11 +368,7 @@ def _get_sqrt_map( class CliffordGate(raw_types.Gate, CommonCliffordGates): """Clifford rotation for N-qubit.""" - def __init__( - self, - *, - _clifford_tableau: qis.CliffordTableau, - ) -> None: + 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). @@ -504,11 +474,7 @@ def _act_on_( class SingleQubitCliffordGate(CliffordGate): """Any single qubit Clifford rotation.""" - def __init__( - self, - *, - _clifford_tableau: qis.CliffordTableau, - ) -> None: + def __init__(self, *, _clifford_tableau: qis.CliffordTableau) -> None: super().__init__(_clifford_tableau=_clifford_tableau) def _num_qubits_(self): From 40fa1ab340a7ff96ea559cbe0cb95a893eecb6b7 Mon Sep 17 00:00:00 2001 From: ybc1991 Date: Sat, 16 Apr 2022 21:31:05 -0700 Subject: [PATCH 19/19] Format with latest black version --- cirq-core/cirq/ops/clifford_gate_test.py | 65 +++++------------------- 1 file changed, 12 insertions(+), 53 deletions(-) diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py index f9dee75169c..e0fa6fc1fe7 100644 --- a/cirq-core/cirq/ops/clifford_gate_test.py +++ b/cirq-core/cirq/ops/clifford_gate_test.py @@ -20,10 +20,7 @@ import cirq from cirq.protocols.act_on_protocol_test import DummyActOnArgs -from cirq.testing import ( - EqualsTester, - assert_allclose_up_to_global_phase, -) +from cirq.testing import EqualsTester, assert_allclose_up_to_global_phase _bools = (False, True) _paulis = (cirq.X, cirq.Y, cirq.Z) @@ -49,10 +46,7 @@ def _assert_no_collision(gate) -> None: def _all_rotations(): - for ( - pauli, - flip, - ) in itertools.product(_paulis, _bools): + for (pauli, flip) in itertools.product(_paulis, _bools): yield cirq.PauliTransform(pauli, flip) @@ -369,9 +363,7 @@ def test_y_rotation(gate, trans_y): def test_decompose(gate, gate_equiv): q0 = cirq.NamedQubit('q0') mat = cirq.Circuit(gate(q0)).unitary() - mat_check = cirq.Circuit( - gate_equiv(q0), - ).unitary() + mat_check = cirq.Circuit(gate_equiv(q0)).unitary() assert_allclose_up_to_global_phase(mat, mat_check, rtol=1e-7, atol=1e-7) @@ -428,23 +420,14 @@ def test_commutes_single_qubit_gate(gate, other): q0 = cirq.NamedQubit('q0') gate_op = gate(q0) other_op = other(q0) - mat = cirq.Circuit( - gate_op, - other_op, - ).unitary() - mat_swap = cirq.Circuit( - other_op, - gate_op, - ).unitary() + mat = cirq.Circuit(gate_op, other_op).unitary() + mat_swap = cirq.Circuit(other_op, gate_op).unitary() commutes = cirq.commutes(gate, other) commutes_check = cirq.allclose_up_to_global_phase(mat, mat_swap) assert commutes == commutes_check # Test after switching order - mat_swap = cirq.Circuit( - gate.equivalent_gate_before(other)(q0), - gate_op, - ).unitary() + mat_swap = cirq.Circuit(gate.equivalent_gate_before(other)(q0), gate_op).unitary() assert_allclose_up_to_global_phase(mat, mat_swap, rtol=1e-7, atol=1e-7) @@ -461,14 +444,8 @@ 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 q0 = cirq.NamedQubit('q0') - mat = cirq.Circuit( - gate(q0), - pauli_gate(q0), - ).unitary() - mat_swap = cirq.Circuit( - pauli_gate(q0), - gate(q0), - ).unitary() + mat = cirq.Circuit(gate(q0), pauli_gate(q0)).unitary() + mat_swap = cirq.Circuit(pauli_gate(q0), gate(q0)).unitary() commutes = cirq.commutes(gate, pauli_gate) commutes_check = np.allclose(mat, mat_swap) assert commutes == commutes_check, f"gate: {gate}, pauli {pauli}" @@ -783,14 +760,7 @@ def test_pad_tableau(): ) 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.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 @@ -800,14 +770,7 @@ def test_pad_tableau(): ) 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.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)) @@ -817,14 +780,10 @@ def test_clifford_gate_act_on_small_case(): qubits = cirq.LineQubit.range(5) args = cirq.ActOnCliffordTableauArgs( - tableau=cirq.CliffordTableau(num_qubits=5), - qubits=qubits, - prng=np.random.RandomState(), + tableau=cirq.CliffordTableau(num_qubits=5), qubits=qubits, prng=np.random.RandomState() ) expected_args = cirq.ActOnCliffordTableauArgs( - tableau=cirq.CliffordTableau(num_qubits=5), - qubits=qubits, - prng=np.random.RandomState(), + tableau=cirq.CliffordTableau(num_qubits=5), qubits=qubits, prng=np.random.RandomState() ) 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)