From 061c9276833c31da18cc7d5f09602d7416be4fd6 Mon Sep 17 00:00:00 2001 From: Victory Omole Date: Thu, 7 Jul 2022 18:10:37 -0500 Subject: [PATCH] Call `cirq.testing.assert_equivalent_repr` for `cirq.SingleQubitCliffordGate` and `cirq.CliffordTableau` (#5664) `cirq.CliffordTableau` is represented by `rs`, `xs` and `zs` yet these can't be passed into the `__init__` but are instead calculated and/or set in the class. This PR adds `xs`, `xs`, and `zs` as `Optional` arguments so that the `CliffordTableau` can be reconstructed from the `__repr__`. Fixes: https://github.com/quantumlib/Cirq/issues/5641 --- cirq/ops/clifford_gate.py | 11 +--- cirq/ops/clifford_gate_test.py | 20 +++--- cirq/qis/clifford_tableau.py | 100 +++++++++++++++++++++++++----- cirq/qis/clifford_tableau_test.py | 18 +++++- 4 files changed, 114 insertions(+), 35 deletions(-) diff --git a/cirq/ops/clifford_gate.py b/cirq/ops/clifford_gate.py index 5c359a0d337..7c9b47e0787 100644 --- a/cirq/ops/clifford_gate.py +++ b/cirq/ops/clifford_gate.py @@ -879,16 +879,7 @@ def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQub return self.merged_with(after).merged_with(self**-1) def __repr__(self) -> str: - x = self.pauli_tuple(pauli_gates.X) - y = self.pauli_tuple(pauli_gates.Y) - z = self.pauli_tuple(pauli_gates.Z) - x_sign = '-' if x[1] else '+' - y_sign = '-' if y[1] else '+' - z_sign = '-' if z[1] else '+' - return ( - f'cirq.SingleQubitCliffordGate(X:{x_sign}{x[0]!s}, ' - f'Y:{y_sign}{y[0]!s}, Z:{z_sign}{z[0]!s})' - ) + return f'cirq.CliffordGate.from_clifford_tableau({self.clifford_tableau!r})' def _circuit_diagram_info_( self, args: 'cirq.CircuitDiagramInfoArgs' diff --git a/cirq/ops/clifford_gate_test.py b/cirq/ops/clifford_gate_test.py index 0c65c780966..d6581419aaf 100644 --- a/cirq/ops/clifford_gate_test.py +++ b/cirq/ops/clifford_gate_test.py @@ -329,16 +329,22 @@ def test_eq_ne_and_hash(): @pytest.mark.parametrize( - 'gate,rep', + 'gate', ( - (cirq.SingleQubitCliffordGate.I, 'cirq.SingleQubitCliffordGate(X:+X, Y:+Y, Z:+Z)'), - (cirq.SingleQubitCliffordGate.H, 'cirq.SingleQubitCliffordGate(X:+Z, Y:-Y, Z:+X)'), - (cirq.SingleQubitCliffordGate.X, 'cirq.SingleQubitCliffordGate(X:+X, Y:-Y, Z:-Z)'), - (cirq.SingleQubitCliffordGate.X_sqrt, 'cirq.SingleQubitCliffordGate(X:+X, Y:+Z, Z:-Y)'), + cirq.SingleQubitCliffordGate.I, + cirq.SingleQubitCliffordGate.H, + cirq.SingleQubitCliffordGate.X, + cirq.SingleQubitCliffordGate.X_sqrt, ), ) -def test_repr(gate, rep): - assert repr(gate) == rep +def test_repr_gate(gate): + cirq.testing.assert_equivalent_repr(gate) + + +def test_repr_operation(): + cirq.testing.assert_equivalent_repr( + cirq.SingleQubitCliffordGate.from_pauli(cirq.Z).on(cirq.LineQubit(2)) + ) @pytest.mark.parametrize( diff --git a/cirq/qis/clifford_tableau.py b/cirq/qis/clifford_tableau.py index afe66ce92c5..a0f19f459ef 100644 --- a/cirq/qis/clifford_tableau.py +++ b/cirq/qis/clifford_tableau.py @@ -17,6 +17,7 @@ import numpy as np from cirq import protocols +from cirq._compat import proper_repr from cirq.qis import quantum_state_representation from cirq.value import big_endian_int_to_digits, linear_dict @@ -137,7 +138,14 @@ class CliffordTableau(StabilizerState): an eigenoperator of the state vector with eigenvalue one: P|psi> = |psi>. """ - def __init__(self, num_qubits, initial_state: int = 0): + def __init__( + self, + num_qubits, + initial_state: int = 0, + rs: Optional[np.ndarray] = None, + xs: Optional[np.ndarray] = None, + zs: Optional[np.ndarray] = None, + ): """Initializes CliffordTableau Args: num_qubits: The number of qubits in the system. @@ -145,22 +153,77 @@ def __init__(self, num_qubits, initial_state: int = 0): state as a big endian int. """ self.n = num_qubits - - # The last row (`2n+1`-th row) is the scratch row used in _measurement + self.initial_state = initial_state + # _reconstruct_* adds the last row (`2n+1`-th row) to the input arrays, + # which is the scratch row used in _measurement # computation process only. It should not be exposed to external usage. - self._rs = np.zeros(2 * self.n + 1, dtype=bool) - - for (i, val) in enumerate( - big_endian_int_to_digits(initial_state, digit_count=num_qubits, base=2) - ): - self._rs[self.n + i] = bool(val) + self._rs = self._reconstruct_rs(rs) + self._xs = self._reconstruct_xs(xs) + self._zs = self._reconstruct_zs(zs) + + def _reconstruct_rs(self, rs: Optional[np.ndarray]) -> np.ndarray: + if rs is None: + new_rs = np.zeros(2 * self.n + 1, dtype=bool) + for (i, val) in enumerate( + big_endian_int_to_digits(self.initial_state, digit_count=self.n, base=2) + ): + new_rs[self.n + i] = bool(val) + else: + shape = rs.shape + if len(shape) == 1 and shape[0] == 2 * self.n and rs.dtype == np.dtype(bool): + new_rs = np.append(rs, np.zeros(1, dtype=bool)) + else: + raise ValueError( + f"The value you passed for rs is not the correct shape and/or type. " + f"Please confirm that it's a single row with 2*num_qubits columns " + f"and of type bool." + ) + return new_rs + + def _reconstruct_xs(self, xs: Optional[np.ndarray]) -> np.ndarray: + if xs is None: + new_xs = np.zeros((2 * self.n + 1, self.n), dtype=bool) + for i in range(self.n): + new_xs[i, i] = True + else: + shape = xs.shape + if ( + len(shape) == 2 + and shape[0] == 2 * self.n + and shape[1] == self.n + and xs.dtype == np.dtype(bool) + ): + new_xs = np.append(xs, np.zeros((1, self.n), dtype=bool), axis=0) + else: + raise ValueError( + f"The value you passed for xs is not the correct shape and/or type. " + f"Please confirm that it's 2*num_qubits rows, num_qubits columns, " + f"and of type bool." + ) + return new_xs - self._xs = np.zeros((2 * self.n + 1, self.n), dtype=bool) - self._zs = np.zeros((2 * self.n + 1, self.n), dtype=bool) + def _reconstruct_zs(self, zs: Optional[np.ndarray]) -> np.ndarray: - for i in range(self.n): - self._xs[i, i] = True - self._zs[self.n + i, i] = True + if zs is None: + new_zs = np.zeros((2 * self.n + 1, self.n), dtype=bool) + for i in range(self.n): + new_zs[self.n + i, i] = True + else: + shape = zs.shape + if ( + len(shape) == 2 + and shape[0] == 2 * self.n + and shape[1] == self.n + and zs.dtype == np.dtype(bool) + ): + new_zs = np.append(zs, np.zeros((1, self.n), dtype=bool), axis=0) + else: + raise ValueError( + f"The value you passed for zs is not the correct shape and/or type. " + f"Please confirm that it's 2*num_qubits rows, num_qubits columns, " + f"and of type bool." + ) + return new_zs @property def xs(self) -> np.ndarray: @@ -233,8 +296,13 @@ def copy(self, deep_copy_buffers: bool = True) -> 'CliffordTableau': return state def __repr__(self) -> str: - stabilizers = ", ".join([repr(stab) for stab in self.stabilizers()]) - return f'stabilizers: [{stabilizers}]' + return ( + f"cirq.CliffordTableau({self.n}," + f"rs={proper_repr(np.delete(self._rs, len(self._rs)-1))}, " + f"xs={proper_repr(np.delete(self._xs, len(self._xs)-1, axis=0))}," + f"zs={proper_repr(np.delete(self._zs, len(self._zs)-1, axis=0))}, " + f"initial_state={self.initial_state})" + ) def __str__(self) -> str: string = '' diff --git a/cirq/qis/clifford_tableau_test.py b/cirq/qis/clifford_tableau_test.py index b8d5add1d47..63d98471b43 100644 --- a/cirq/qis/clifford_tableau_test.py +++ b/cirq/qis/clifford_tableau_test.py @@ -55,6 +55,21 @@ def test_tableau_initial_state_string(num_qubits): assert splitted_represent_string[n] == expected_string +def test_tableau_invalid_initial_state(): + with pytest.raises(ValueError, match="2*num_qubits columns and of type bool."): + cirq.CliffordTableau(1, rs=np.zeros(1, dtype=bool)) + + with pytest.raises( + ValueError, match="2*num_qubits rows, num_qubits columns, and of type bool." + ): + cirq.CliffordTableau(1, xs=np.zeros(1, dtype=bool)) + + with pytest.raises( + ValueError, match="2*num_qubits rows, num_qubits columns, and of type bool." + ): + cirq.CliffordTableau(1, zs=np.zeros(1, dtype=bool)) + + def test_stabilizers(): # Note: the stabilizers are not unique for one state. We just use the one # produced by the tableau algorithm. @@ -271,8 +286,7 @@ def test_str(): def test_repr(): - t = cirq.CliffordTableau(num_qubits=1) - assert repr(t) == "stabilizers: [cirq.DensePauliString('Z', coefficient=(1+0j))]" + cirq.testing.assert_equivalent_repr(cirq.CliffordTableau(num_qubits=1)) def test_str_full():