Skip to content

Commit

Permalink
Call cirq.testing.assert_equivalent_repr for `cirq.SingleQubitCliff…
Browse files Browse the repository at this point in the history
…ordGate` 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: #5641
  • Loading branch information
vtomole authored Jul 7, 2022
1 parent 1ca3e95 commit 11726fc
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 35 deletions.
11 changes: 1 addition & 10 deletions cirq-core/cirq/ops/clifford_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
20 changes: 13 additions & 7 deletions cirq-core/cirq/ops/clifford_gate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
100 changes: 84 additions & 16 deletions cirq-core/cirq/qis/clifford_tableau.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -137,30 +138,92 @@ 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.
initial_state: The computational basis representation of the
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:
Expand Down Expand Up @@ -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 = ''
Expand Down
18 changes: 16 additions & 2 deletions cirq-core/cirq/qis/clifford_tableau_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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():
Expand Down

0 comments on commit 11726fc

Please sign in to comment.