From d680b2918f1333fab8ddf13505e76da56091df39 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 5 May 2021 18:42:46 -0700 Subject: [PATCH 01/19] Move `import DensePauliString` under CliffordTableau into runtime --- cirq-core/cirq/qis/clifford_tableau.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 60e7f12b56a..e8bf32bd865 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -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 @@ -202,7 +201,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. @@ -212,6 +211,8 @@ def _row_to_dense_pauli(self, i: int) -> DensePauliString: represents the effective single Pauli operator on that qubit. The overall phase is captured in the coefficient. """ + from cirq.ops.dense_pauli_string import DensePauliString + coefficient = -1 if self.rs[i] else 1 pauli_mask = "" @@ -226,12 +227,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.""" From 70283c46d0a6c02e2f10498d973c50aede711b1a Mon Sep 17 00:00:00 2001 From: ybc Date: Sun, 9 May 2021 16:31:23 -0700 Subject: [PATCH 02/19] Add merged_with method of two clifford tableau --- cirq-core/cirq/qis/clifford_tableau.py | 40 +++++++++ cirq-core/cirq/qis/clifford_tableau_test.py | 89 +++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index e8bf32bd865..8232417797a 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -83,6 +83,9 @@ 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: + 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']) @@ -175,6 +178,43 @@ def _str_full_(self) -> str: return string + def merged_with(self, second: 'CliffordTableau') -> 'CliffordTableau': + """Returns a merged CliffordTableau of self tableau and second tableau. + + The meaning of merge two clifford tableau is the corresponding unitary operation + of merged clifford tableau is equal to (up to global phase) the composed unitary + operation of self tableau and the one of second tableau. + """ + m1 = self.matrix().astype(int) + m2 = second.matrix().astype(int) + + p1 = self.rs.astype(int) + p2 = second.rs.astype(int) + + merged_m = np.mod(m1.dot(m2), 2) + phase = np.mod(p1 + m1.dot(p2), 2) + + # we need more phase correction for expanding Y to XZ and swapping Z_iX_i order if necessary. + for k in range(2 * self.n): + swap_phase = 0 # value betwen 0 and 3 representing [1, i, -1, -i] respectively. + prev_row_sum = np.zeros([2 * self.n]) + swap_phase += np.sum(m1[k, : self.n] * m1[k, self.n :]) # Y gate => iXZ + for i, v in enumerate(m1[k]): + if v == 0: + continue + swap_phase += np.sum(m2[i, : self.n] * m2[i, self.n :]) # Y gate => iXZ + swap_phase += 2 * np.sum(m2[i, : self.n] * prev_row_sum[self.n :]) # swap Z_i X_i + prev_row_sum += m2[i] + prev_row_sum = np.mod(prev_row_sum, 2) + swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY + phase[k] = (phase[k] + (swap_phase % 4) / 2) % 2 + + merged_tableau = CliffordTableau(num_qubits=self.n) + merged_tableau.xs = merged_m[:, : self.n] + merged_tableau.zs = merged_m[:, self.n :] + merged_tableau.rs = phase + return merged_tableau + def _rowsum(self, q1, q2): """Implements the "rowsum" routine defined by Aaronson and Gottesman. diff --git a/cirq-core/cirq/qis/clifford_tableau_test.py b/cirq-core/cirq/qis/clifford_tableau_test.py index cc27bf3c4b8..df01cb6bd46 100644 --- a/cirq-core/cirq/qis/clifford_tableau_test.py +++ b/cirq-core/cirq/qis/clifford_tableau_test.py @@ -322,3 +322,92 @@ def test_deprecated_clifford_location(): from cirq.sim import CliffordTableau CliffordTableau(num_qubits=1) + + +def test_merge_tableau(): + 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 + + t1, t2, expected_t = three_identical_table(1) + assert expected_t == t1.merged_with(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.merged_with(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.merged_with(t2) + + 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.merged_with(t2) + + 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.merged_with(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.merged_with(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, 1) for t in (t2, expected_t)] + [_CNOT(t, 1, 0) for t in (t2, expected_t)] + assert expected_t == t1.merged_with(t2) + + def random_circuit(num_ops, num_qubits, seed=12345): + prng = np.random.RandomState(seed) + if num_qubits == 1: + candidate_op = [_H, _S, _X, _Z] + else: + 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 _ in range(100): + t1, t2, expected_t = three_identical_table(8) + seq_op = random_circuit(num_ops=20, num_qubits=8) + 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.merged_with(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.merged_with(t2) From 701f0df827946329d16b9bbb808afa3b439d3b16 Mon Sep 17 00:00:00 2001 From: ybc Date: Sun, 9 May 2021 16:53:58 -0700 Subject: [PATCH 03/19] Fix lint and coverage error --- cirq-core/cirq/qis/clifford_tableau.py | 4 +- cirq-core/cirq/qis/clifford_tableau_test.py | 55 ++++++++++----------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 8232417797a..cf08a717c8a 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -194,7 +194,7 @@ def merged_with(self, second: 'CliffordTableau') -> 'CliffordTableau': merged_m = np.mod(m1.dot(m2), 2) phase = np.mod(p1 + m1.dot(p2), 2) - # we need more phase correction for expanding Y to XZ and swapping Z_iX_i order if necessary. + # we need more phase correction for expanding Y to XZ and swapping Z_iX_i order. for k in range(2 * self.n): swap_phase = 0 # value betwen 0 and 3 representing [1, i, -1, -i] respectively. prev_row_sum = np.zeros([2 * self.n]) @@ -251,8 +251,6 @@ def _row_to_dense_pauli(self, i: int) -> 'cirq.DensePauliString': represents the effective single Pauli operator on that qubit. The overall phase is captured in the coefficient. """ - from cirq.ops.dense_pauli_string import DensePauliString - coefficient = -1 if self.rs[i] else 1 pauli_mask = "" diff --git a/cirq-core/cirq/qis/clifford_tableau_test.py b/cirq-core/cirq/qis/clifford_tableau_test.py index df01cb6bd46..034373b5839 100644 --- a/cirq-core/cirq/qis/clifford_tableau_test.py +++ b/cirq-core/cirq/qis/clifford_tableau_test.py @@ -335,49 +335,48 @@ def three_identical_table(num_qubits): assert expected_t == t1.merged_with(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)] + _ = [_H(t, 0) for t in (t1, expected_t)] + _ = [_H(t, 0) for t in (t2, expected_t)] assert expected_t == t1.merged_with(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)] + _ = [_X(t, 0) for t in (t1, expected_t)] + _ = [_S(t, 0) for t in (t2, expected_t)] assert expected_t == t1.merged_with(t2) 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)] + _ = [_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.merged_with(t2) 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)] + _ = [_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.merged_with(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)] + _ = [_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.merged_with(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, 1) for t in (t2, expected_t)] - [_CNOT(t, 1, 0) for t in (t2, expected_t)] + _ = [_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.merged_with(t2) def random_circuit(num_ops, num_qubits, seed=12345): prng = np.random.RandomState(seed) - if num_qubits == 1: - candidate_op = [_H, _S, _X, _Z] - else: + candidate_op = [_H, _S, _X, _Z] + if num_qubits > 1: candidate_op = [_H, _S, _X, _Z, _CNOT] seq_op = [] @@ -396,9 +395,9 @@ def random_circuit(num_ops, num_qubits, seed=12345): seq_op = random_circuit(num_ops=20, num_qubits=8) for i, (op, args) in enumerate(seq_op): if i < 7: - [op(t, *args) for t in (t1, expected_t)] + _ = [op(t, *args) for t in (t1, expected_t)] else: - [op(t, *args) for t in (t2, expected_t)] + _ = [op(t, *args) for t in (t2, expected_t)] assert expected_t == t1.merged_with(t2) # Since merging Clifford Tableau operation is O(n^3), @@ -407,7 +406,7 @@ def random_circuit(num_ops, num_qubits, seed=12345): 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)] + _ = [op(t, *args) for t in (t1, expected_t)] else: - [op(t, *args) for t in (t2, expected_t)] + _ = [op(t, *args) for t in (t2, expected_t)] assert expected_t == t1.merged_with(t2) From e086c41ffa057a328a28673cdb1dd53257be93bb Mon Sep 17 00:00:00 2001 From: ybc Date: Sun, 9 May 2021 17:03:39 -0700 Subject: [PATCH 04/19] Varying the seed in the loop --- cirq-core/cirq/qis/clifford_tableau_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau_test.py b/cirq-core/cirq/qis/clifford_tableau_test.py index 034373b5839..2a5106ca748 100644 --- a/cirq-core/cirq/qis/clifford_tableau_test.py +++ b/cirq-core/cirq/qis/clifford_tableau_test.py @@ -390,9 +390,9 @@ def random_circuit(num_ops, num_qubits, seed=12345): return seq_op # Do small random circuits test 100 times. - for _ in range(100): + for seed in range(100): t1, t2, expected_t = three_identical_table(8) - seq_op = random_circuit(num_ops=20, num_qubits=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)] From 733c73842f132d3c96646c49432b8c2f751a4b31 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 12 May 2021 16:56:50 -0700 Subject: [PATCH 05/19] Address the comments and rename the function to `then` --- cirq-core/cirq/qis/clifford_tableau.py | 16 +++-- cirq-core/cirq/qis/clifford_tableau_test.py | 70 ++++++++++++++------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index cf08a717c8a..047340783d3 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -84,6 +84,7 @@ def rs(self, new_rs: np.array) -> None: self._rs[:-1] = np.array(new_rs).astype(bool) def matrix(self) -> np.array: + """Returns a 2n * 2n matrix of Clifford table.""" return np.concatenate([self.xs, self.zs], axis=1) def _json_dict_(self) -> Dict[str, Any]: @@ -178,12 +179,12 @@ def _str_full_(self) -> str: return string - def merged_with(self, second: 'CliffordTableau') -> 'CliffordTableau': - """Returns a merged CliffordTableau of self tableau and second tableau. + def then(self, second: 'CliffordTableau') -> 'CliffordTableau': + """Returns a composed CliffordTableau of this tableau and the second tableau. - The meaning of merge two clifford tableau is the corresponding unitary operation - of merged clifford tableau is equal to (up to global phase) the composed unitary - operation of self tableau and the one of 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 + this CliffordTableau then applying the second one. """ m1 = self.matrix().astype(int) m2 = second.matrix().astype(int) @@ -215,6 +216,11 @@ def merged_with(self, second: 'CliffordTableau') -> 'CliffordTableau': merged_tableau.rs = phase 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. diff --git a/cirq-core/cirq/qis/clifford_tableau_test.py b/cirq-core/cirq/qis/clifford_tableau_test.py index 2a5106ca748..e2bb07d57ff 100644 --- a/cirq-core/cirq/qis/clifford_tableau_test.py +++ b/cirq-core/cirq/qis/clifford_tableau_test.py @@ -324,54 +324,60 @@ def test_deprecated_clifford_location(): CliffordTableau(num_qubits=1) -def test_merge_tableau(): - 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 _three_identical_table(num_qubits): + t1 = cirq.CliffordTableau(num_qubits) + t2 = cirq.CliffordTableau(num_qubits) + t3 = cirq.CliffordTableau(num_qubits) + return t1, t2, t3 - t1, t2, expected_t = three_identical_table(1) - assert expected_t == t1.merged_with(t2) - t1, t2, expected_t = three_identical_table(1) +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.merged_with(t2) + assert expected_t == t1.then(t2) - t1, t2, expected_t = three_identical_table(1) + 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.merged_with(t2) + assert expected_t == t1.then(t2) + assert expected_t != t2.then(t1) - t1, t2, expected_t = three_identical_table(1) + 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.merged_with(t2) + assert expected_t == t1.then(t2) + assert expected_t != t2.then(t1) - t1, t2, expected_t = three_identical_table(2) + 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.merged_with(t2) + assert expected_t == t1.then(t2) - t1, t2, expected_t = three_identical_table(2) + 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.merged_with(t2) + assert expected_t == t1.then(t2) + assert expected_t != t2.then(t1) - t1, t2, expected_t = three_identical_table(2) + 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.merged_with(t2) + 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) @@ -391,22 +397,38 @@ def random_circuit(num_ops, num_qubits, seed=12345): # Do small random circuits test 100 times. for seed in range(100): - t1, t2, expected_t = three_identical_table(8) + 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.merged_with(t2) + 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) + 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.merged_with(t2) + 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): + t1 @ 21 From c7877e4f6e815eecc7d2dca8cffb8c36e5368d25 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 12 May 2021 17:04:03 -0700 Subject: [PATCH 06/19] Ignore the pylint warning --- cirq-core/cirq/qis/clifford_tableau_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cirq-core/cirq/qis/clifford_tableau_test.py b/cirq-core/cirq/qis/clifford_tableau_test.py index e2bb07d57ff..44706ab1465 100644 --- a/cirq-core/cirq/qis/clifford_tableau_test.py +++ b/cirq-core/cirq/qis/clifford_tableau_test.py @@ -431,4 +431,6 @@ def test_tableau_matmul(): assert expected_t != t1 @ t2 with pytest.raises(TypeError): + # pylint: disable=pointless-statement t1 @ 21 + # pylint: enable=pointless-statement From 3e341c5a1a87d9bd977de0f9acbe398f3c0b3756 Mon Sep 17 00:00:00 2001 From: ybc Date: Thu, 13 May 2021 17:39:24 -0700 Subject: [PATCH 07/19] Minor update for more comments --- cirq-core/cirq/qis/clifford_tableau.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 047340783d3..a61c714f2d7 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -183,9 +183,10 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': """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 - this CliffordTableau then applying the second one. + unitary operation of the two tableaux, i.e. equivalent to applying the unitary + operation of this CliffordTableau then applying the second one. """ + # Convert the underlying data type from bool to int for easier numerical computation. m1 = self.matrix().astype(int) m2 = second.matrix().astype(int) From 6a19c8b4377591d09b39c95a80fb85e46494bbb7 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Fri, 14 May 2021 17:23:29 -0700 Subject: [PATCH 08/19] Update cirq-core/cirq/qis/clifford_tableau.py Co-authored-by: Balint Pato --- cirq-core/cirq/qis/clifford_tableau.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index a61c714f2d7..0a435276c80 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -84,7 +84,7 @@ def rs(self, new_rs: np.array) -> None: self._rs[:-1] = np.array(new_rs).astype(bool) def matrix(self) -> np.array: - """Returns a 2n * 2n matrix of Clifford table.""" + """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]: From 356b1ed7294515a18d7fbe1c8e96784e197aea2c Mon Sep 17 00:00:00 2001 From: ybc Date: Fri, 14 May 2021 19:04:42 -0700 Subject: [PATCH 09/19] Add dimension and type check and corresponding test. --- cirq-core/cirq/qis/clifford_tableau.py | 18 ++++++++++++++++++ cirq-core/cirq/qis/clifford_tableau_test.py | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 0a435276c80..fd48d10e12a 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -185,7 +185,25 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': 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. + + 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) diff --git a/cirq-core/cirq/qis/clifford_tableau_test.py b/cirq-core/cirq/qis/clifford_tableau_test.py index 44706ab1465..0bd87848aad 100644 --- a/cirq-core/cirq/qis/clifford_tableau_test.py +++ b/cirq-core/cirq/qis/clifford_tableau_test.py @@ -434,3 +434,13 @@ def test_tableau_matmul(): # 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) From ddc3ca773cc30bcddb4f1a015f7600ebbc9069cf Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Mon, 17 May 2021 16:06:07 -0700 Subject: [PATCH 10/19] Update cirq-core/cirq/qis/clifford_tableau.py Co-authored-by: Balint Pato --- cirq-core/cirq/qis/clifford_tableau.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index fd48d10e12a..5eecb2f6349 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -216,7 +216,7 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': # we need more phase correction for expanding Y to XZ and swapping Z_iX_i order. for k in range(2 * self.n): - swap_phase = 0 # value betwen 0 and 3 representing [1, i, -1, -i] respectively. + swap_phase = 0 # value between 0 and 3 representing [1, i, -1, -i] respectively. prev_row_sum = np.zeros([2 * self.n]) swap_phase += np.sum(m1[k, : self.n] * m1[k, self.n :]) # Y gate => iXZ for i, v in enumerate(m1[k]): From ca1726708325de5c7dd5b62d48bf37d24d8b66e4 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Mon, 17 May 2021 16:06:26 -0700 Subject: [PATCH 11/19] Update cirq-core/cirq/qis/clifford_tableau.py Co-authored-by: Balint Pato --- cirq-core/cirq/qis/clifford_tableau.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 5eecb2f6349..4ce7f98fb5a 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -223,7 +223,8 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': if v == 0: continue swap_phase += np.sum(m2[i, : self.n] * m2[i, self.n :]) # Y gate => iXZ - swap_phase += 2 * np.sum(m2[i, : self.n] * prev_row_sum[self.n :]) # swap Z_i X_i + # swapping Z_i with X_i adds a -1 phase, which is 2j + swap_phase += 2 * np.sum(m2[i, : self.n] * prev_row_sum[self.n :]) prev_row_sum += m2[i] prev_row_sum = np.mod(prev_row_sum, 2) swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY From 4acbcafcd175c2f98a2a6d8b7326c638755f6e20 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 17:59:01 -0700 Subject: [PATCH 12/19] Add more comments --- cirq-core/cirq/qis/clifford_tableau.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 4ce7f98fb5a..bfdea7c98c6 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -214,9 +214,13 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': merged_m = np.mod(m1.dot(m2), 2) phase = np.mod(p1 + m1.dot(p2), 2) - # we need more phase correction for expanding Y to XZ and swapping Z_iX_i order. + # We need more phase correction for expanding Y to XZ and swapping Z_iX_i order. + # One key property is X_i is only anti-commute with Z_j i.f.f. i = j and X_i + # commute with all X_j. Hence, we can calculate the phase on X/Z_i independently from + # X/Z_j, which is corresponding to element wise computation in differnt columns. + # This is the basis for the vectorization. for k in range(2 * self.n): - swap_phase = 0 # value between 0 and 3 representing [1, i, -1, -i] respectively. + swap_phase = 0 # The value mod 4 represents number of i factors. prev_row_sum = np.zeros([2 * self.n]) swap_phase += np.sum(m1[k, : self.n] * m1[k, self.n :]) # Y gate => iXZ for i, v in enumerate(m1[k]): @@ -228,6 +232,10 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': prev_row_sum += m2[i] prev_row_sum = np.mod(prev_row_sum, 2) swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY + + # Adding the correction phase. Note original phase is True/False for [+1, -1] phase. + # while the swap_phase mod 4 is for [1, i, -1, -i]. + # At this stage, the swap_phase must be either +1 or -1 guaranteed by the symplectic property. phase[k] = (phase[k] + (swap_phase % 4) / 2) % 2 merged_tableau = CliffordTableau(num_qubits=self.n) From 75207e59f3f3c9232aa3cc026539e5b11d7a3cc7 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 18:09:29 -0700 Subject: [PATCH 13/19] Compute num_ys in advance --- cirq-core/cirq/qis/clifford_tableau.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index bfdea7c98c6..bb4b1cd5f81 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -214,6 +214,8 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': merged_m = np.mod(m1.dot(m2), 2) phase = np.mod(p1 + m1.dot(p2), 2) + num_ys1 = np.sum(m1[:, : self.n] * m1[:, self.n :], axis=1) + num_ys2 = np.sum(m2[:, : self.n] * m2[:, self.n :], axis=1) # We need more phase correction for expanding Y to XZ and swapping Z_iX_i order. # One key property is X_i is only anti-commute with Z_j i.f.f. i = j and X_i # commute with all X_j. Hence, we can calculate the phase on X/Z_i independently from @@ -222,11 +224,11 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': for k in range(2 * self.n): swap_phase = 0 # The value mod 4 represents number of i factors. prev_row_sum = np.zeros([2 * self.n]) - swap_phase += np.sum(m1[k, : self.n] * m1[k, self.n :]) # Y gate => iXZ + swap_phase += num_ys1[k] # Y gate => iXZ for i, v in enumerate(m1[k]): if v == 0: continue - swap_phase += np.sum(m2[i, : self.n] * m2[i, self.n :]) # Y gate => iXZ + swap_phase += num_ys2[i] # Y gate => iXZ # swapping Z_i with X_i adds a -1 phase, which is 2j swap_phase += 2 * np.sum(m2[i, : self.n] * prev_row_sum[self.n :]) prev_row_sum += m2[i] From de6c419d08d9c68d8e63104cd157267cef105dc3 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 18:10:25 -0700 Subject: [PATCH 14/19] Fix the lint error --- cirq-core/cirq/qis/clifford_tableau.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index bb4b1cd5f81..275e9768be7 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -236,8 +236,8 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY # Adding the correction phase. Note original phase is True/False for [+1, -1] phase. - # while the swap_phase mod 4 is for [1, i, -1, -i]. - # At this stage, the swap_phase must be either +1 or -1 guaranteed by the symplectic property. + # while the swap_phase mod 4 is for [1, i, -1, -i]. At this stage, + # the swap_phase must be either +1 or -1 guaranteed by the symplectic property. phase[k] = (phase[k] + (swap_phase % 4) / 2) % 2 merged_tableau = CliffordTableau(num_qubits=self.n) From 9db74eb1230181d5aaa84a0f0ecfcb2afbb7e949 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 18:21:44 -0700 Subject: [PATCH 15/19] Format file --- cirq-core/cirq/qis/clifford_tableau.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 275e9768be7..0c1adc9ef59 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -236,7 +236,7 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY # Adding the correction phase. Note original phase is True/False for [+1, -1] phase. - # while the swap_phase mod 4 is for [1, i, -1, -i]. At this stage, + # while the swap_phase mod 4 is for [1, i, -1, -i]. At this stage, # the swap_phase must be either +1 or -1 guaranteed by the symplectic property. phase[k] = (phase[k] + (swap_phase % 4) / 2) % 2 From 7c6eeac10ef2c0a9bedf1535f3c62892f3fadc63 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 22:22:48 -0700 Subject: [PATCH 16/19] Using the vectorization methods to compute the phases --- cirq-core/cirq/qis/clifford_tableau.py | 62 +++++++++++++------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 0c1adc9ef59..8fa680458d3 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -208,42 +208,40 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': m1 = self.matrix().astype(int) m2 = second.matrix().astype(int) - p1 = self.rs.astype(int) - p2 = second.rs.astype(int) - - merged_m = np.mod(m1.dot(m2), 2) - phase = np.mod(p1 + m1.dot(p2), 2) - + # The following computation is based on Theorem 36 in + # https://arxiv.org/pdf/2009.03218.pdf. + # Any pauli string in Clifford Tableau should be able to 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) - # We need more phase correction for expanding Y to XZ and swapping Z_iX_i order. - # One key property is X_i is only anti-commute with Z_j i.f.f. i = j and X_i - # commute with all X_j. Hence, we can calculate the phase on X/Z_i independently from - # X/Z_j, which is corresponding to element wise computation in differnt columns. - # This is the basis for the vectorization. - for k in range(2 * self.n): - swap_phase = 0 # The value mod 4 represents number of i factors. - prev_row_sum = np.zeros([2 * self.n]) - swap_phase += num_ys1[k] # Y gate => iXZ - for i, v in enumerate(m1[k]): - if v == 0: - continue - swap_phase += num_ys2[i] # Y gate => iXZ - # swapping Z_i with X_i adds a -1 phase, which is 2j - swap_phase += 2 * np.sum(m2[i, : self.n] * prev_row_sum[self.n :]) - prev_row_sum += m2[i] - prev_row_sum = np.mod(prev_row_sum, 2) - swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY - - # Adding the correction phase. Note original phase is True/False for [+1, -1] phase. - # while the swap_phase mod 4 is for [1, i, -1, -i]. At this stage, - # the swap_phase must be either +1 or -1 guaranteed by the symplectic property. - phase[k] = (phase[k] + (swap_phase % 4) / 2) % 2 + + p1 = np.mod(num_ys1, 2) + p2 = np.mod(num_ys2, 2) + + s1 = self.rs.astype(int) + np.mod(num_ys1, 4) // 2 + 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) + m2Lm2T = m2 @ lmbda @ m2.T + + m_12 = np.mod(m1.dot(m2), 2) + p_12 = np.mod(p1 + m1.dot(p2), 2) + s_12 = ( + s1 + + m1.dot(s2) + + p1 * m1.dot(p2) + + np.diag(m1 @ np.tril(np.outer(p2, p2.T) + m2Lm2T, -1) @ m1.T) + ) + num_ys12 = np.sum(m_12[:, : self.n] * m_12[:, self.n :], axis=1) + merged_phase = np.mod(p_12 + 2 * s_12 - num_ys12, 4) merged_tableau = CliffordTableau(num_qubits=self.n) - merged_tableau.xs = merged_m[:, : self.n] - merged_tableau.zs = merged_m[:, self.n :] - merged_tableau.rs = phase + merged_tableau.xs = m_12[:, : self.n] + merged_tableau.zs = m_12[:, self.n :] + merged_tableau.rs = merged_phase + return merged_tableau def __matmul__(self, second: 'CliffordTableau'): From 17f09f2b7cacfe6ca35ec6f13e865f1af9b93ae6 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 22:24:55 -0700 Subject: [PATCH 17/19] Update clifford_tableau.py --- cirq-core/cirq/qis/clifford_tableau.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 8fa680458d3..4fe61626efc 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -235,7 +235,7 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': + np.diag(m1 @ np.tril(np.outer(p2, p2.T) + m2Lm2T, -1) @ m1.T) ) num_ys12 = np.sum(m_12[:, : self.n] * m_12[:, self.n :], axis=1) - merged_phase = np.mod(p_12 + 2 * s_12 - num_ys12, 4) + merged_phase = 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] From bfa08ca8503ec35bdb78ede42ccbec9df751b433 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 17 May 2021 22:31:26 -0700 Subject: [PATCH 18/19] Update the comments --- cirq-core/cirq/qis/clifford_tableau.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 4fe61626efc..777c6592546 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -210,9 +210,9 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': # The following computation is based on Theorem 36 in # https://arxiv.org/pdf/2009.03218.pdf. - # Any pauli string in Clifford Tableau should be able to expressed as - # (1i)^p (-1)^s X^(mx) Z^(mz) where p and s are binary scalar and - # mx and mz are binary vectors. + # 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) From 464f912a4b28c167bdc82bde36b728857ebe0493 Mon Sep 17 00:00:00 2001 From: ybc Date: Tue, 18 May 2021 17:55:57 -0700 Subject: [PATCH 19/19] Using @ and add more comments --- cirq-core/cirq/qis/clifford_tableau.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cirq-core/cirq/qis/clifford_tableau.py b/cirq-core/cirq/qis/clifford_tableau.py index 777c6592546..57347d670f3 100644 --- a/cirq-core/cirq/qis/clifford_tableau.py +++ b/cirq-core/cirq/qis/clifford_tableau.py @@ -219,28 +219,30 @@ def then(self, second: 'CliffordTableau') -> 'CliffordTableau': 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 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) - m2Lm2T = m2 @ lmbda @ m2.T - m_12 = np.mod(m1.dot(m2), 2) - p_12 = np.mod(p1 + m1.dot(p2), 2) + m_12 = np.mod(m1 @ m2, 2) + p_12 = np.mod(p1 + m1 @ p2, 2) s_12 = ( s1 - + m1.dot(s2) - + p1 * m1.dot(p2) - + np.diag(m1 @ np.tril(np.outer(p2, p2.T) + m2Lm2T, -1) @ m1.T) + + 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_phase = np.mod(p_12 + 2 * s_12 - num_ys12, 4) // 2 + 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_phase + merged_tableau.rs = merged_sign return merged_tableau