Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add then method for composing two Clifford tableaux #4096

Merged
merged 23 commits into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d680b29
Move `import DensePauliString` under CliffordTableau into runtime
BichengYing May 6, 2021
70283c4
Add merged_with method of two clifford tableau
BichengYing May 9, 2021
701f0df
Fix lint and coverage error
BichengYing May 9, 2021
e086c41
Varying the seed in the loop
BichengYing May 10, 2021
ae82bcd
Merge branch 'master' into single_clifford
BichengYing May 10, 2021
733c738
Address the comments and rename the function to `then`
BichengYing May 12, 2021
c7877e4
Ignore the pylint warning
BichengYing May 13, 2021
3e341c5
Minor update for more comments
BichengYing May 14, 2021
4d84529
Merge branch 'master' into single_clifford
BichengYing May 14, 2021
6a19c8b
Update cirq-core/cirq/qis/clifford_tableau.py
BichengYing May 15, 2021
356b1ed
Add dimension and type check and corresponding test.
BichengYing May 15, 2021
ddc3ca7
Update cirq-core/cirq/qis/clifford_tableau.py
BichengYing May 17, 2021
ca17267
Update cirq-core/cirq/qis/clifford_tableau.py
BichengYing May 17, 2021
4acbcaf
Add more comments
BichengYing May 18, 2021
75207e5
Compute num_ys in advance
BichengYing May 18, 2021
de6c419
Fix the lint error
BichengYing May 18, 2021
9db74eb
Format file
BichengYing May 18, 2021
7c6eeac
Using the vectorization methods to compute the phases
BichengYing May 18, 2021
17f09f2
Update clifford_tableau.py
BichengYing May 18, 2021
bfa08ca
Update the comments
BichengYing May 18, 2021
464f912
Using @ and add more comments
BichengYing May 19, 2021
f044cc1
Merge branch 'master' into single_clifford
BichengYing May 20, 2021
b11caf7
Merge branch 'master' into single_clifford
CirqBot May 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 79 additions & 4 deletions cirq-core/cirq/qis/clifford_tableau.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import cirq
from cirq import protocols
from cirq.ops.dense_pauli_string import DensePauliString
from cirq.value import big_endian_int_to_digits


Expand Down Expand Up @@ -84,6 +83,10 @@ def rs(self, new_rs: np.array) -> None:
assert np.shape(new_rs) == (2 * self.n,)
self._rs[:-1] = np.array(new_rs).astype(bool)

def matrix(self) -> np.array:
BichengYing marked this conversation as resolved.
Show resolved Hide resolved
"""Returns the 2n * 2n matrix representation of the Clifford tableau."""
return np.concatenate([self.xs, self.zs], axis=1)

def _json_dict_(self) -> Dict[str, Any]:
return protocols.obj_to_dict_helper(self, ['n', 'rs', 'xs', 'zs'])

Expand Down Expand Up @@ -176,6 +179,78 @@ def _str_full_(self) -> str:

return string

def then(self, second: 'CliffordTableau') -> 'CliffordTableau':
balopat marked this conversation as resolved.
Show resolved Hide resolved
"""Returns a composed CliffordTableau of this tableau and the second tableau.

Then composed tableau is equal to (up to global phase) the composed
unitary operation of the two tableaux, i.e. equivalent to applying the unitary
operation of this CliffordTableau then applying the second one.
BichengYing marked this conversation as resolved.
Show resolved Hide resolved

Args:
second: The second CliffordTableau to compose with.

Returns:
The composed CliffordTableau.

Raises:
TypeError: If the type of second is not CliffordTableau.
ValueError: If the number of qubits in the second tableau mismatch with
this tableau.
"""
if not isinstance(second, CliffordTableau):
raise TypeError("The type for second tableau must be the CliffordTableau type")
if self.n != second.n:
raise ValueError(
f"Mismatched number of qubits of two tableaux: {self.n} vs {second.n}."
)

# Convert the underlying data type from bool to int for easier numerical computation.
m1 = self.matrix().astype(int)
m2 = second.matrix().astype(int)
BichengYing marked this conversation as resolved.
Show resolved Hide resolved

# The following computation is based on Theorem 36 in
# https://arxiv.org/pdf/2009.03218.pdf.
# Any pauli string (one stabilizer) in Clifford Tableau should be able to be expressed as
# (1i)^p (-1)^s X^(mx) Z^(mz)
# where p and s are binary scalar and mx and mz are binary vectors.
num_ys1 = np.sum(m1[:, : self.n] * m1[:, self.n :], axis=1)
num_ys2 = np.sum(m2[:, : self.n] * m2[:, self.n :], axis=1)

p1 = np.mod(num_ys1, 2)
p2 = np.mod(num_ys2, 2)

# Note the `s` is not equal to `r`, which depends on the number of Y gates.
# For example, r * Y_1Y_2Y_3 can be expanded into i^3 * r * X_1Z_1 X_2Z_2 X_3Z_3.
# The global phase is i * (-1) * r ==> s = r + 1 and p = 1.
s1 = self.rs.astype(int) + np.mod(num_ys1, 4) // 2
BichengYing marked this conversation as resolved.
Show resolved Hide resolved
s2 = second.rs.astype(int) + np.mod(num_ys2, 4) // 2

lmbda = np.zeros((2 * self.n, 2 * self.n))
lmbda[: self.n, self.n :] = np.eye(self.n)

m_12 = np.mod(m1 @ m2, 2)
p_12 = np.mod(p1 + m1 @ p2, 2)
s_12 = (
s1
+ m1 @ s2
+ p1 * (m1 @ p2)
+ np.diag(m1 @ np.tril(np.outer(p2, p2.T) + m2 @ lmbda @ m2.T, -1) @ m1.T)
)
num_ys12 = np.sum(m_12[:, : self.n] * m_12[:, self.n :], axis=1)
merged_sign = np.mod(p_12 + 2 * s_12 - num_ys12, 4) // 2

merged_tableau = CliffordTableau(num_qubits=self.n)
merged_tableau.xs = m_12[:, : self.n]
merged_tableau.zs = m_12[:, self.n :]
merged_tableau.rs = merged_sign

return merged_tableau

def __matmul__(self, second: 'CliffordTableau'):
if not isinstance(second, CliffordTableau):
return NotImplemented
return second.then(self)

def _rowsum(self, q1, q2):
"""Implements the "rowsum" routine defined by
Aaronson and Gottesman.
Expand All @@ -202,7 +277,7 @@ def g(x1, z1, x2, z2):
self._xs[q1, :] ^= self._xs[q2, :]
self._zs[q1, :] ^= self._zs[q2, :]

def _row_to_dense_pauli(self, i: int) -> DensePauliString:
def _row_to_dense_pauli(self, i: int) -> 'cirq.DensePauliString':
"""
Args:
i: index of the row in the tableau.
Expand All @@ -226,12 +301,12 @@ def _row_to_dense_pauli(self, i: int) -> DensePauliString:
pauli_mask += "I"
return cirq.DensePauliString(pauli_mask, coefficient=coefficient)

def stabilizers(self) -> List[DensePauliString]:
def stabilizers(self) -> List['cirq.DensePauliString']:
"""Returns the stabilizer generators of the state. These
are n operators {S_1,S_2,...,S_n} such that S_i |psi> = |psi>"""
return [self._row_to_dense_pauli(i) for i in range(self.n, 2 * self.n)]

def destabilizers(self) -> List[DensePauliString]:
def destabilizers(self) -> List['cirq.DensePauliString']:
"""Returns the destabilizer generators of the state. These
are n operators {S_1,S_2,...,S_n} such that along with the stabilizer
generators above generate the full Pauli group on n qubits."""
Expand Down
122 changes: 122 additions & 0 deletions cirq-core/cirq/qis/clifford_tableau_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,125 @@ def test_deprecated_clifford_location():
from cirq.sim import CliffordTableau

CliffordTableau(num_qubits=1)


def _three_identical_table(num_qubits):
t1 = cirq.CliffordTableau(num_qubits)
t2 = cirq.CliffordTableau(num_qubits)
t3 = cirq.CliffordTableau(num_qubits)
return t1, t2, t3


def test_tableau_then():

t1, t2, expected_t = _three_identical_table(1)
assert expected_t == t1.then(t2)

t1, t2, expected_t = _three_identical_table(1)
_ = [_H(t, 0) for t in (t1, expected_t)]
_ = [_H(t, 0) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)

t1, t2, expected_t = _three_identical_table(1)
_ = [_X(t, 0) for t in (t1, expected_t)]
_ = [_S(t, 0) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)
assert expected_t != t2.then(t1)

t1, t2, expected_t = _three_identical_table(1)
_ = [_X(t, 0) for t in (t1, expected_t)]
_ = [_H(t, 0) for t in (t1, expected_t)]
_ = [_Z(t, 0) for t in (t1, expected_t)]
_ = [_S(t, 0) for t in (t2, expected_t)]
_ = [_H(t, 0) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)
assert expected_t != t2.then(t1)

t1, t2, expected_t = _three_identical_table(2)
_ = [_H(t, 0) for t in (t1, expected_t)]
_ = [_H(t, 1) for t in (t1, expected_t)]
_ = [_H(t, 0) for t in (t2, expected_t)]
_ = [_H(t, 1) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)

t1, t2, expected_t = _three_identical_table(2)
_ = [_H(t, 0) for t in (t1, expected_t)]
_ = [_CNOT(t, 0, 1) for t in (t1, expected_t)]
_ = [_S(t, 0) for t in (t2, expected_t)]
_ = [_X(t, 1) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)
assert expected_t != t2.then(t1)

t1, t2, expected_t = _three_identical_table(2)
_ = [_H(t, 0) for t in (t1, expected_t)]
_ = [_CNOT(t, 0, 1) for t in (t1, expected_t)]
_ = [_S(t, 1) for t in (t2, expected_t)]
_ = [_CNOT(t, 1, 0) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)
assert expected_t != t2.then(t1)

def random_circuit(num_ops, num_qubits, seed=12345):
prng = np.random.RandomState(seed)
candidate_op = [_H, _S, _X, _Z]
if num_qubits > 1:
candidate_op = [_H, _S, _X, _Z, _CNOT]

seq_op = []
for _ in range(num_ops):
op = prng.randint(len(candidate_op))
if op != 4:
args = (prng.randint(num_qubits),)
else:
args = prng.choice(num_qubits, 2, replace=False)
seq_op.append((candidate_op[op], args))
return seq_op

# Do small random circuits test 100 times.
for seed in range(100):
t1, t2, expected_t = _three_identical_table(8)
seq_op = random_circuit(num_ops=20, num_qubits=8, seed=seed)
for i, (op, args) in enumerate(seq_op):
if i < 7:
_ = [op(t, *args) for t in (t1, expected_t)]
else:
_ = [op(t, *args) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)

# Since merging Clifford Tableau operation is O(n^3),
# running 100 qubits case is still fast.
t1, t2, expected_t = _three_identical_table(100)
seq_op = random_circuit(num_ops=1000, num_qubits=100)
for i, (op, args) in enumerate(seq_op):
if i < 350:
_ = [op(t, *args) for t in (t1, expected_t)]
else:
_ = [op(t, *args) for t in (t2, expected_t)]
assert expected_t == t1.then(t2)


def test_tableau_matmul():
t1, t2, expected_t = _three_identical_table(1)
_ = [_H(t, 0) for t in (t1, expected_t)]
_ = [_H(t, 0) for t in (t2, expected_t)]
assert expected_t == t2 @ t1

t1, t2, expected_t = _three_identical_table(1)
_ = [_X(t, 0) for t in (t1, expected_t)]
_ = [_S(t, 0) for t in (t2, expected_t)]
assert expected_t == t2 @ t1
assert expected_t != t1 @ t2

with pytest.raises(TypeError):
# pylint: disable=pointless-statement
t1 @ 21
# pylint: enable=pointless-statement


def test_tableau_then_with_bad_input():
t1 = cirq.CliffordTableau(1)
t2 = cirq.CliffordTableau(2)
with pytest.raises(ValueError, match="Mismatched number of qubits of two tableaux: 1 vs 2."):
t1.then(t2)

with pytest.raises(TypeError):
t1.then(cirq.X)