From b2d9269699d1e771f2b5b62a7e7273a59f0f6bc7 Mon Sep 17 00:00:00 2001 From: ybc Date: Fri, 28 May 2021 23:29:29 -0700 Subject: [PATCH 01/17] 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/17] 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 03dc632cec8b9048918e9bfc82c515b75dd21478 Mon Sep 17 00:00:00 2001 From: ybc Date: Mon, 7 Jun 2021 11:56:33 -0700 Subject: [PATCH 03/17] Initial Clifford Decomposition Method --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/optimizers/__init__.py | 2 + .../cirq/optimizers/clifford_decomposition.py | 144 +++++++++++++++ .../optimizers/clifford_decomposition_test.py | 172 ++++++++++++++++++ 4 files changed, 319 insertions(+) create mode 100644 cirq-core/cirq/optimizers/clifford_decomposition.py create mode 100644 cirq-core/cirq/optimizers/clifford_decomposition_test.py diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 0c9bfeb9098..b2ceef88d06 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -303,6 +303,7 @@ AlignRight, compute_cphase_exponents_for_fsim_decomposition, ConvertToCzAndSingleGates, + decompose_clifford_tableau_to_operations, decompose_cphase_into_two_fsim, decompose_multi_controlled_x, decompose_multi_controlled_rotation, diff --git a/cirq-core/cirq/optimizers/__init__.py b/cirq-core/cirq/optimizers/__init__.py index 28ccc11cede..cc46f27fc73 100644 --- a/cirq-core/cirq/optimizers/__init__.py +++ b/cirq-core/cirq/optimizers/__init__.py @@ -22,6 +22,8 @@ AlignRight, ) +from cirq.optimizers.clifford_decomposition import decompose_clifford_tableau_to_operations + from cirq.optimizers.cphase_to_fsim import ( compute_cphase_exponents_for_fsim_decomposition, decompose_cphase_into_two_fsim, diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py new file mode 100644 index 00000000000..a4d76cd8c56 --- /dev/null +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -0,0 +1,144 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utility methods related to decompose clifford gate into circuits.""" + +from typing import Iterable, List, Tuple, Optional, cast, TYPE_CHECKING + +import numpy as np +import functools +from cirq import ops, linalg, protocols, circuits, qis + +if TYPE_CHECKING: + import cirq + + +def _X(table, q, operations, qubits): + table.rs[:] ^= table.zs[:, q] + operations.append(ops.X(qubits[q])) + + +def _Z(table, q, operations, qubits): + table.rs[:] ^= table.xs[:, q] + operations.append(ops.Z(qubits[q])) + + +def _S(table, q, operations, qubits): + table.rs[:] ^= table.xs[:, q] & table.zs[:, q] + table.zs[:, q] ^= table.xs[:, q] + operations.append(ops.S(qubits[q])**-1) + + +def _H(table, q, operations, qubits): + (table.xs[:, q], table.zs[:, q]) = (table.zs[:, q].copy(), table.xs[:, q].copy()) + table.rs[:] ^= table.xs[:, q] & table.zs[:, q] + operations.append(ops.H(qubits[q])) + + +def _CNOT(table, q1, q2, operations, qubits): + table.rs[:] ^= table.xs[:, q1] & table.zs[:, q2] & (~(table.xs[:, q2] ^ table.zs[:, q1])) + table.xs[:, q2] ^= table.xs[:, q1] + table.zs[:, q1] ^= table.zs[:, q2] + operations.append(ops.CNOT(qubits[q1], qubits[q2])) + + +def _SWAP(table, q1, q2, operations, qubits): + table.xs[:, [q1, q2]] = table.xs[:, [q2, q1]] + table.zs[:, [q1, q2]] = table.xs[:, [q2, q1]] + operations.append(ops.SWAP(qubits[q1], qubits[q2])) + + +def decompose_clifford_tableau_to_operations( + qubits: List['cirq.Qid'], clifford_tableau: qis.CliffordTableau +) -> List[ops.Operation]: + """Decompose an n-qubit Clifford Tableau into one/two qubit operations. + + Args: + qubits: The list of qubits being operated on. + clifford_tableau: The Clifford Tableau used to decompose to the operations. + + Returns: + A list of operations implementing the Clifford tableau + """ + if len(qubits) != clifford_tableau.n: + raise ValueError( + f"The number of qubits must be the same as the number of Clifford Tableau." + ) + assert ( + clifford_tableau._validate() + ), "The provided clifford_tableau must satisfy the symplectic property." + + t: qis.CliffordTableau = clifford_tableau.copy() + operations: List[ops.Operation] = [] + _X_with_ops = functools.partial(_X, operations=operations, qubits=qubits) + _Z_with_ops = functools.partial(_Z, operations=operations, qubits=qubits) + _H_with_ops = functools.partial(_H, operations=operations, qubits=qubits) + _S_with_ops = functools.partial(_S, operations=operations, qubits=qubits) + _CNOT_with_ops = functools.partial(_CNOT, operations=operations, qubits=qubits) + _SWAP_with_ops = functools.partial(_SWAP, operations=operations, qubits=qubits) + + # The procedure is based on theorem 8 in + # [1] S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*, + # Phys. Rev. A 70, 052328 (2004). https://arxiv.org/abs/quant-ph/0406196 + # with some modification by doing it row-by-row instead. + + # Suppose we have a Clifford Tableau + # Xs Zs + # Destabilizers: [ A | B ] + # Stabilizers: [ C | D ] + for i in range(t.n): + # Step 1: Make sure the Diagonal Elements are 1 by swapping. + if not t.xs[i, i]: + for j in range(i + 1, t.n): + if t.xs[i, j]: + _SWAP_with_ops(t, i, j) + break + # We may still not be able to find non-zero element in whole Xs row. In this case, + # apply swap + Hadamard from zs. It is guaranteed to find one by lemma 5 in [1]. + if not t.xs[i, i]: + for j in range(i, t.n): + if t.zs[i, j]: + _H_with_ops(t, j) + if j != i: + _SWAP_with_ops(t, i, j) + break + + # Step 2: Gaussian Elimination of A By CNOT and phase gate (row style). + # first i rows of destabilizers: [ I 0 | 0 0 ] + _ = [_CNOT_with_ops(t, i, j) for j in range(i + 1, t.n) if t.xs[i, j]] + if np.any(t.zs[i, i:]): + if not t.zs[i, i]: + _S_with_ops(t, i) + _ = [_CNOT_with_ops(t, j, i) for j in range(i + 1, t.n) if t.zs[i, j]] + _S_with_ops(t, i) + + # Step 3: Gaussian Elimination of D By CNOT and phase gate (row style). + # first i rows of stabilizers: [ 0 0 | I 0 ] + _ = [_CNOT_with_ops(t, j, i) for j in range(i + 1, t.n) if t.zs[i + t.n, j]] + if np.any(t.xs[i + t.n, i:]): + # Swap xs and zs + _H_with_ops(t, i) + _ = [_CNOT_with_ops(t, i, j) for j in range(i + 1, t.n) if t.xs[i + t.n, j]] + if t.zs[i + t.n, i]: + _S_with_ops(t, i) + _H_with_ops(t, i) + + # Step 4: Correct the phase of tableau + _ = [_Z_with_ops(t, i) for i, p in enumerate(t.rs[: t.n]) if p] + _ = [_X_with_ops(t, i) for i, p in enumerate(t.rs[t.n :]) if p] + + # Step 5: invert the operations by reserver the orde: (AB)^{+} = B^{+} A^{+}. + # Note only S gate is not self-adjoint. + print(t.matrix(), t.rs) + return operations[::-1] diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py new file mode 100644 index 00000000000..f3ca9c28e8b --- /dev/null +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -0,0 +1,172 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +import numpy as np + +import cirq +from cirq.testing import assert_allclose_up_to_global_phase + + +def _X(table, q, qubits, circ): + table.rs[:] ^= table.zs[:, q] + circ.append(cirq.X(qubits[q])) + + +def _Z(table, q, qubits, circ): + table.rs[:] ^= table.xs[:, q] + circ.append(cirq.Z(qubits[q])) + + +def _S(table, q, qubits, circ): + table.rs[:] ^= table.xs[:, q] & table.zs[:, q] + table.zs[:, q] ^= table.xs[:, q] + circ.append(cirq.S(qubits[q])) + + +def _H(table, q, qubits, circ): + (table.xs[:, q], table.zs[:, q]) = (table.zs[:, q].copy(), table.xs[:, q].copy()) + table.rs[:] ^= table.xs[:, q] & table.zs[:, q] + circ.append(cirq.H(qubits[q])) + + +def _CNOT(table, q1, q2, qubits, circ): + table.rs[:] ^= table.xs[:, q1] & table.zs[:, q2] & (~(table.xs[:, q2] ^ table.zs[:, q1])) + table.xs[:, q2] ^= table.xs[:, q1] + table.zs[:, q1] ^= table.zs[:, q2] + circ.append(cirq.CNOT(qubits[q1], qubits[q2])) + + +def test_clifford_decompose_one_qubit(): + """Two random instance for one qubit decomposition.""" + qubits = cirq.LineQubit.range(1) + args = cirq.ActOnCliffordTableauArgs( + tableau=cirq.CliffordTableau(num_qubits=1), + axes=[0], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(cirq.X, args, allow_decompose=False) + cirq.act_on(cirq.H, args, allow_decompose=False) + cirq.act_on(cirq.S, args, allow_decompose=False) + expect_circ = cirq.Circuit(cirq.X(qubits[0]), cirq.H(qubits[0]), cirq.S(qubits[0])) + ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) + circ = cirq.Circuit(ops) + assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) + + qubits = cirq.LineQubit.range(1) + args = cirq.ActOnCliffordTableauArgs( + tableau=cirq.CliffordTableau(num_qubits=1), + axes=[0], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(cirq.Z, args, allow_decompose=False) + cirq.act_on(cirq.H, args, allow_decompose=False) + cirq.act_on(cirq.S, args, allow_decompose=False) + cirq.act_on(cirq.H, args, allow_decompose=False) + cirq.act_on(cirq.X, args, allow_decompose=False) + expect_circ = cirq.Circuit( + cirq.Z(qubits[0]), + cirq.H(qubits[0]), + cirq.S(qubits[0]), + cirq.H(qubits[0]), + cirq.X(qubits[0]), + ) + ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) + circ = cirq.Circuit(ops) + assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) + + +def test_clifford_decompose_two_qubits(): + """Two random instance for one qubit decomposition.""" + qubits = cirq.LineQubit.range(2) + args = cirq.ActOnCliffordTableauArgs( + tableau=cirq.CliffordTableau(num_qubits=2), + axes=[0], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(cirq.H, args, allow_decompose=False) + args.axes = [0, 1] + cirq.act_on(cirq.CNOT, args, allow_decompose=False) + expect_circ = cirq.Circuit(cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1])) + ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) + circ = cirq.Circuit(ops) + assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) + + qubits = cirq.LineQubit.range(2) + args = cirq.ActOnCliffordTableauArgs( + tableau=cirq.CliffordTableau(num_qubits=2), + axes=[0], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(cirq.H, args, allow_decompose=False) + args.axes = [0, 1] + cirq.act_on(cirq.CNOT, args, allow_decompose=False) + args.axes = [0] + cirq.act_on(cirq.H, args, allow_decompose=False) + cirq.act_on(cirq.S, args, allow_decompose=False) + args.axes = [1] + cirq.act_on(cirq.X, args, allow_decompose=False) + expect_circ = cirq.Circuit( + cirq.H(qubits[0]), + cirq.CNOT(qubits[0], qubits[1]), + cirq.H(qubits[0]), + cirq.S(qubits[0]), + cirq.X(qubits[1]), + ) + + ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) + circ = cirq.Circuit(ops) + assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) + + +def test_clifford_decompose_small_number_qubits_unitary(): + """Use unitary matrix to validate the decomposition of random Clifford Tableau. + + Due to the exponential increasing in dimension, it cannot validate very large number of qubits. + """ + n, num_ops = 2, 5 + gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] + for seed in range(2, 3): + prng = np.random.RandomState(seed) + t = cirq.CliffordTableau(num_qubits=n) + qubits = cirq.LineQubit.range(n) + expect_circ = cirq.Circuit() + args = cirq.ActOnCliffordTableauArgs( + tableau=t, axes=[], prng=prng, log_of_measurement_results={} + ) + for _ in range(num_ops): + g = prng.randint(len(gate_candidate)) + indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) + args.axes = indices + cirq.act_on(gate_candidate[g], args, allow_decompose=False) + expect_circ.append(gate_candidate[g].on(*[qubits[i] for i in indices])) + ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) + print() + print(args.tableau.matrix().astype(int), '\n', args.tableau.rs.astype(int)) + print(ops) + circ = cirq.Circuit(ops) + print(expect_circ) + assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) + + +def test_clifford_decompose_large_number_qubits_tableau(): + """Use tabeau inverse and then method to validate the decomposition of random Clifford Tableau. + + This approach can validate very large number of qubits. + """ + pass From b5cffaea0f0018ad24fc50729a268d438750ff08 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 9 Jun 2021 17:07:04 -0700 Subject: [PATCH 04/17] Update the test of Clifford decomposition --- .../cirq/optimizers/clifford_decomposition.py | 42 +++++++++--------- .../optimizers/clifford_decomposition_test.py | 43 ++++--------------- 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index a4d76cd8c56..94cfa2f3d04 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -24,38 +24,41 @@ import cirq -def _X(table, q, operations, qubits): - table.rs[:] ^= table.zs[:, q] +def _X(tableau, q, operations, qubits): + tableau.rs[:] ^= tableau.zs[:, q] operations.append(ops.X(qubits[q])) -def _Z(table, q, operations, qubits): - table.rs[:] ^= table.xs[:, q] +def _Z(tableau, q, operations, qubits): + tableau.rs[:] ^= tableau.xs[:, q] operations.append(ops.Z(qubits[q])) -def _S(table, q, operations, qubits): - table.rs[:] ^= table.xs[:, q] & table.zs[:, q] - table.zs[:, q] ^= table.xs[:, q] - operations.append(ops.S(qubits[q])**-1) +def _Sdg(tableau, q, operations, qubits): + # Apply the tableau with S^+, so the inverse of operation is S + tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) + tableau.zs[:, q] ^= tableau.xs[:, q] + operations.append(ops.S(qubits[q])) -def _H(table, q, operations, qubits): - (table.xs[:, q], table.zs[:, q]) = (table.zs[:, q].copy(), table.xs[:, q].copy()) - table.rs[:] ^= table.xs[:, q] & table.zs[:, q] +def _H(tableau, q, operations, qubits): + (tableau.xs[:, q], tableau.zs[:, q]) = (tableau.zs[:, q].copy(), tableau.xs[:, q].copy()) + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] operations.append(ops.H(qubits[q])) -def _CNOT(table, q1, q2, operations, qubits): - table.rs[:] ^= table.xs[:, q1] & table.zs[:, q2] & (~(table.xs[:, q2] ^ table.zs[:, q1])) - table.xs[:, q2] ^= table.xs[:, q1] - table.zs[:, q1] ^= table.zs[:, q2] +def _CNOT(tableau, q1, q2, operations, qubits): + tableau.rs[:] ^= ( + tableau.xs[:, q1] & tableau.zs[:, q2] & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) + ) + tableau.xs[:, q2] ^= tableau.xs[:, q1] + tableau.zs[:, q1] ^= tableau.zs[:, q2] operations.append(ops.CNOT(qubits[q1], qubits[q2])) -def _SWAP(table, q1, q2, operations, qubits): - table.xs[:, [q1, q2]] = table.xs[:, [q2, q1]] - table.zs[:, [q1, q2]] = table.xs[:, [q2, q1]] +def _SWAP(tableau, q1, q2, operations, qubits): + tableau.xs[:, [q1, q2]] = tableau.xs[:, [q2, q1]] + tableau.zs[:, [q1, q2]] = tableau.zs[:, [q2, q1]] operations.append(ops.SWAP(qubits[q1], qubits[q2])) @@ -84,7 +87,7 @@ def decompose_clifford_tableau_to_operations( _X_with_ops = functools.partial(_X, operations=operations, qubits=qubits) _Z_with_ops = functools.partial(_Z, operations=operations, qubits=qubits) _H_with_ops = functools.partial(_H, operations=operations, qubits=qubits) - _S_with_ops = functools.partial(_S, operations=operations, qubits=qubits) + _S_with_ops = functools.partial(_Sdg, operations=operations, qubits=qubits) _CNOT_with_ops = functools.partial(_CNOT, operations=operations, qubits=qubits) _SWAP_with_ops = functools.partial(_SWAP, operations=operations, qubits=qubits) @@ -140,5 +143,4 @@ def decompose_clifford_tableau_to_operations( # Step 5: invert the operations by reserver the orde: (AB)^{+} = B^{+} A^{+}. # Note only S gate is not self-adjoint. - print(t.matrix(), t.rs) return operations[::-1] diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py index f3ca9c28e8b..71327facf4e 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition_test.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -19,35 +19,6 @@ from cirq.testing import assert_allclose_up_to_global_phase -def _X(table, q, qubits, circ): - table.rs[:] ^= table.zs[:, q] - circ.append(cirq.X(qubits[q])) - - -def _Z(table, q, qubits, circ): - table.rs[:] ^= table.xs[:, q] - circ.append(cirq.Z(qubits[q])) - - -def _S(table, q, qubits, circ): - table.rs[:] ^= table.xs[:, q] & table.zs[:, q] - table.zs[:, q] ^= table.xs[:, q] - circ.append(cirq.S(qubits[q])) - - -def _H(table, q, qubits, circ): - (table.xs[:, q], table.zs[:, q]) = (table.zs[:, q].copy(), table.xs[:, q].copy()) - table.rs[:] ^= table.xs[:, q] & table.zs[:, q] - circ.append(cirq.H(qubits[q])) - - -def _CNOT(table, q1, q2, qubits, circ): - table.rs[:] ^= table.xs[:, q1] & table.zs[:, q2] & (~(table.xs[:, q2] ^ table.zs[:, q1])) - table.xs[:, q2] ^= table.xs[:, q1] - table.zs[:, q1] ^= table.zs[:, q2] - circ.append(cirq.CNOT(qubits[q1], qubits[q2])) - - def test_clifford_decompose_one_qubit(): """Two random instance for one qubit decomposition.""" qubits = cirq.LineQubit.range(1) @@ -139,9 +110,9 @@ def test_clifford_decompose_small_number_qubits_unitary(): Due to the exponential increasing in dimension, it cannot validate very large number of qubits. """ - n, num_ops = 2, 5 + n, num_ops = 5, 20 gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] - for seed in range(2, 3): + for seed in range(150, 300): prng = np.random.RandomState(seed) t = cirq.CliffordTableau(num_qubits=n) qubits = cirq.LineQubit.range(n) @@ -156,11 +127,9 @@ def test_clifford_decompose_small_number_qubits_unitary(): cirq.act_on(gate_candidate[g], args, allow_decompose=False) expect_circ.append(gate_candidate[g].on(*[qubits[i] for i in indices])) ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) - print() - print(args.tableau.matrix().astype(int), '\n', args.tableau.rs.astype(int)) - print(ops) circ = cirq.Circuit(ops) - print(expect_circ) + circ.append(cirq.I.on_each(qubits)) + expect_circ.append(cirq.I.on_each(qubits)) assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) @@ -170,3 +139,7 @@ def test_clifford_decompose_large_number_qubits_tableau(): This approach can validate very large number of qubits. """ pass + + +if __name__ == "__main__": + test_clifford_decompose_small_number_qubits_unitary() From b7b717060c6307bfca34746f70caa962eba872f5 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 9 Jun 2021 21:18:21 -0700 Subject: [PATCH 05/17] Change to ActOnCliffordTableauArgs style --- cirq-core/cirq/ops/common_gates.py | 5 +- .../cirq/optimizers/clifford_decomposition.py | 107 +++++++++--------- .../optimizers/clifford_decomposition_test.py | 35 ++++-- 3 files changed, 88 insertions(+), 59 deletions(-) diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index 40873868df2..b1c846fdf8c 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -1073,8 +1073,11 @@ def _act_on_(self, args: Any): tableau.zs[:, q2].copy(), tableau.xs[:, q2].copy(), ) + tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] tableau.rs[:] ^= ( - tableau.xs[:, q1] & tableau.zs[:, q2] & (tableau.xs[:, q2] ^ tableau.zs[:, q1]) + tableau.xs[:, q1] + & tableau.zs[:, q2] + & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) ) tableau.xs[:, q2] ^= tableau.xs[:, q1] tableau.zs[:, q1] ^= tableau.zs[:, q2] diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 94cfa2f3d04..975f37a474b 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -14,51 +14,50 @@ """Utility methods related to decompose clifford gate into circuits.""" -from typing import Iterable, List, Tuple, Optional, cast, TYPE_CHECKING +from typing import List, TYPE_CHECKING +import functools import numpy as np -import functools -from cirq import ops, linalg, protocols, circuits, qis +from cirq import ops, protocols, qis, sim if TYPE_CHECKING: import cirq -def _X(tableau, q, operations, qubits): - tableau.rs[:] ^= tableau.zs[:, q] +def _X(q, args, operations, qubits): + args.axes = [q] + protocols.act_on(ops.X, args, allow_decompose=False) operations.append(ops.X(qubits[q])) -def _Z(tableau, q, operations, qubits): - tableau.rs[:] ^= tableau.xs[:, q] +def _Z(q, args, operations, qubits): + args.axes = [q] + protocols.act_on(ops.Z, args, allow_decompose=False) operations.append(ops.Z(qubits[q])) -def _Sdg(tableau, q, operations, qubits): +def _Sdg(q, args, operations, qubits): # Apply the tableau with S^+, so the inverse of operation is S - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - tableau.zs[:, q] ^= tableau.xs[:, q] + args.axes = [q] + protocols.act_on(ops.ZPowGate() ** 1.5, args, allow_decompose=False) operations.append(ops.S(qubits[q])) -def _H(tableau, q, operations, qubits): - (tableau.xs[:, q], tableau.zs[:, q]) = (tableau.zs[:, q].copy(), tableau.xs[:, q].copy()) - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] +def _H(q, args, operations, qubits): + args.axes = [q] + protocols.act_on(ops.H, args, allow_decompose=False) operations.append(ops.H(qubits[q])) -def _CNOT(tableau, q1, q2, operations, qubits): - tableau.rs[:] ^= ( - tableau.xs[:, q1] & tableau.zs[:, q2] & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) - ) - tableau.xs[:, q2] ^= tableau.xs[:, q1] - tableau.zs[:, q1] ^= tableau.zs[:, q2] +def _CNOT(q1, q2, args, operations, qubits): + args.axes = [q1, q2] + protocols.act_on(ops.CNOT, args, allow_decompose=False) operations.append(ops.CNOT(qubits[q1], qubits[q2])) -def _SWAP(tableau, q1, q2, operations, qubits): - tableau.xs[:, [q1, q2]] = tableau.xs[:, [q2, q1]] - tableau.zs[:, [q1, q2]] = tableau.zs[:, [q2, q1]] +def _SWAP(q1, q2, args, operations, qubits): + args.axes = [q1, q2] + protocols.act_on(ops.SWAP, args, allow_decompose=False) operations.append(ops.SWAP(qubits[q1], qubits[q2])) @@ -84,63 +83,69 @@ def decompose_clifford_tableau_to_operations( t: qis.CliffordTableau = clifford_tableau.copy() operations: List[ops.Operation] = [] - _X_with_ops = functools.partial(_X, operations=operations, qubits=qubits) - _Z_with_ops = functools.partial(_Z, operations=operations, qubits=qubits) - _H_with_ops = functools.partial(_H, operations=operations, qubits=qubits) - _S_with_ops = functools.partial(_Sdg, operations=operations, qubits=qubits) - _CNOT_with_ops = functools.partial(_CNOT, operations=operations, qubits=qubits) - _SWAP_with_ops = functools.partial(_SWAP, operations=operations, qubits=qubits) + args = sim.ActOnCliffordTableauArgs( + tableau=t, axes=[], prng=np.random.RandomState(), log_of_measurement_results={} + ) + + _X_with_ops = functools.partial(_X, args=args, operations=operations, qubits=qubits) + _Z_with_ops = functools.partial(_Z, args=args, operations=operations, qubits=qubits) + _H_with_ops = functools.partial(_H, args=args, operations=operations, qubits=qubits) + _S_with_ops = functools.partial(_Sdg, args=args, operations=operations, qubits=qubits) + _CNOT_with_ops = functools.partial(_CNOT, args=args, operations=operations, qubits=qubits) + _SWAP_with_ops = functools.partial(_SWAP, args=args, operations=operations, qubits=qubits) # The procedure is based on theorem 8 in # [1] S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*, # Phys. Rev. A 70, 052328 (2004). https://arxiv.org/abs/quant-ph/0406196 - # with some modification by doing it row-by-row instead. + # with modification by doing it row-by-row instead. # Suppose we have a Clifford Tableau - # Xs Zs + # Xs Zs # Destabilizers: [ A | B ] # Stabilizers: [ C | D ] for i in range(t.n): - # Step 1: Make sure the Diagonal Elements are 1 by swapping. + # Step 1a: Make the diagonal element of A as 1 by Hadamard gate if necessary. + if not t.xs[i, i] and t.zs[i, i]: + _H_with_ops(i) + # Step 1b: Make the diagonal element of A as 1 by swapping gate if necessary. if not t.xs[i, i]: for j in range(i + 1, t.n): if t.xs[i, j]: - _SWAP_with_ops(t, i, j) + _SWAP_with_ops(i, j) break - # We may still not be able to find non-zero element in whole Xs row. In this case, + # Step 1c: We may still not be able to find non-zero element in whole Xs row. Then, # apply swap + Hadamard from zs. It is guaranteed to find one by lemma 5 in [1]. if not t.xs[i, i]: - for j in range(i, t.n): + for j in range(i + 1, t.n): if t.zs[i, j]: - _H_with_ops(t, j) - if j != i: - _SWAP_with_ops(t, i, j) + _H_with_ops(j) + _SWAP_with_ops(i, j) break - # Step 2: Gaussian Elimination of A By CNOT and phase gate (row style). + # Step 2: Eliminate the elements in A By CNOT and phase gate (i-th row) # first i rows of destabilizers: [ I 0 | 0 0 ] - _ = [_CNOT_with_ops(t, i, j) for j in range(i + 1, t.n) if t.xs[i, j]] + _ = [_CNOT_with_ops(i, j) for j in range(i + 1, t.n) if t.xs[i, j]] if np.any(t.zs[i, i:]): if not t.zs[i, i]: - _S_with_ops(t, i) - _ = [_CNOT_with_ops(t, j, i) for j in range(i + 1, t.n) if t.zs[i, j]] - _S_with_ops(t, i) + _S_with_ops(i) + _ = [_CNOT_with_ops(j, i) for j in range(i + 1, t.n) if t.zs[i, j]] + _S_with_ops(i) - # Step 3: Gaussian Elimination of D By CNOT and phase gate (row style). + # Step 3: Eliminate the elements in D By CNOT and phase gate (i-th row) # first i rows of stabilizers: [ 0 0 | I 0 ] - _ = [_CNOT_with_ops(t, j, i) for j in range(i + 1, t.n) if t.zs[i + t.n, j]] + _ = [_CNOT_with_ops(j, i) for j in range(i + 1, t.n) if t.zs[i + t.n, j]] if np.any(t.xs[i + t.n, i:]): # Swap xs and zs - _H_with_ops(t, i) - _ = [_CNOT_with_ops(t, i, j) for j in range(i + 1, t.n) if t.xs[i + t.n, j]] + _H_with_ops(i) + _ = [_CNOT_with_ops(i, j) for j in range(i + 1, t.n) if t.xs[i + t.n, j]] if t.zs[i + t.n, i]: - _S_with_ops(t, i) - _H_with_ops(t, i) + _S_with_ops(i) + _H_with_ops(i) # Step 4: Correct the phase of tableau - _ = [_Z_with_ops(t, i) for i, p in enumerate(t.rs[: t.n]) if p] - _ = [_X_with_ops(t, i) for i, p in enumerate(t.rs[t.n :]) if p] + _ = [_Z_with_ops(i) for i, p in enumerate(t.rs[: t.n]) if p] + _ = [_X_with_ops(i) for i, p in enumerate(t.rs[t.n :]) if p] - # Step 5: invert the operations by reserver the orde: (AB)^{+} = B^{+} A^{+}. + # Step 5: invert the operations by reversing the orde: (AB)^{+} = B^{+} A^{+}. # Note only S gate is not self-adjoint. return operations[::-1] diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py index 71327facf4e..33799b031ce 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition_test.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest import numpy as np import cirq @@ -112,7 +111,7 @@ def test_clifford_decompose_small_number_qubits_unitary(): """ n, num_ops = 5, 20 gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] - for seed in range(150, 300): + for seed in range(100): prng = np.random.RandomState(seed) t = cirq.CliffordTableau(num_qubits=n) qubits = cirq.LineQubit.range(n) @@ -134,12 +133,34 @@ def test_clifford_decompose_small_number_qubits_unitary(): def test_clifford_decompose_large_number_qubits_tableau(): - """Use tabeau inverse and then method to validate the decomposition of random Clifford Tableau. + """Use tabeau comparison to validate the decomposition of random Clifford Tableau. - This approach can validate very large number of qubits. + This approach can validate large number of qubits compared with unitary one. """ - pass + n, num_ops = 100, 500 + gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] + for seed in range(10): + prng = np.random.RandomState(seed) + t = cirq.CliffordTableau(num_qubits=n) + qubits = cirq.LineQubit.range(n) + expect_circ = cirq.Circuit() + args = cirq.ActOnCliffordTableauArgs( + tableau=t, axes=[], prng=prng, log_of_measurement_results={} + ) + for _ in range(num_ops): + g = prng.randint(len(gate_candidate)) + indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) + args.axes = indices + cirq.act_on(gate_candidate[g], args, allow_decompose=False) + expect_circ.append(gate_candidate[g].on(*[qubits[i] for i in indices])) + ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) + reconstruct_t = cirq.CliffordTableau(num_qubits=n) + reconstruct_args = cirq.ActOnCliffordTableauArgs( + tableau=reconstruct_t, axes=[], prng=prng, log_of_measurement_results={} + ) + for op in ops: + reconstruct_args.axes = [q.x for q in op.qubits] + cirq.act_on(op.gate, reconstruct_args, allow_decompose=False) -if __name__ == "__main__": - test_clifford_decompose_small_number_qubits_unitary() + assert t == reconstruct_t From 20760c27b3add5cbe5cdb8dfa53335383351d8ef Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 9 Jun 2021 21:24:06 -0700 Subject: [PATCH 06/17] Fix some typos --- .../cirq/optimizers/clifford_decomposition.py | 10 +++++----- .../cirq/optimizers/clifford_decomposition_test.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 975f37a474b..826cdaae890 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -64,14 +64,14 @@ def _SWAP(q1, q2, args, operations, qubits): def decompose_clifford_tableau_to_operations( qubits: List['cirq.Qid'], clifford_tableau: qis.CliffordTableau ) -> List[ops.Operation]: - """Decompose an n-qubit Clifford Tableau into one/two qubit operations. + """Decompose an n-qubit Clifford Tableau into a list of one/two qubit operations. Args: qubits: The list of qubits being operated on. - clifford_tableau: The Clifford Tableau used to decompose to the operations. + clifford_tableau: The Clifford Tableau for decomposition. Returns: - A list of operations implementing the Clifford tableau + A list of operations reconstructs the same Clifford tableau. """ if len(qubits) != clifford_tableau.n: raise ValueError( @@ -94,12 +94,12 @@ def decompose_clifford_tableau_to_operations( _CNOT_with_ops = functools.partial(_CNOT, args=args, operations=operations, qubits=qubits) _SWAP_with_ops = functools.partial(_SWAP, args=args, operations=operations, qubits=qubits) - # The procedure is based on theorem 8 in + # The procedure is based on Theorem 8 in # [1] S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*, # Phys. Rev. A 70, 052328 (2004). https://arxiv.org/abs/quant-ph/0406196 # with modification by doing it row-by-row instead. - # Suppose we have a Clifford Tableau + # Suppose we have a Clifford Tableau: # Xs Zs # Destabilizers: [ A | B ] # Stabilizers: [ C | D ] diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py index 33799b031ce..e936146030e 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition_test.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -60,7 +60,7 @@ def test_clifford_decompose_one_qubit(): def test_clifford_decompose_two_qubits(): - """Two random instance for one qubit decomposition.""" + """Two random instance for two qubits decomposition.""" qubits = cirq.LineQubit.range(2) args = cirq.ActOnCliffordTableauArgs( tableau=cirq.CliffordTableau(num_qubits=2), @@ -104,10 +104,10 @@ def test_clifford_decompose_two_qubits(): assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) -def test_clifford_decompose_small_number_qubits_unitary(): - """Use unitary matrix to validate the decomposition of random Clifford Tableau. +def test_clifford_decompose_by_unitary(): + """Validate the decomposition of random Clifford Tableau by unitary matrix. - Due to the exponential increasing in dimension, it cannot validate very large number of qubits. + Due to the exponential growth in dimension, it cannot validate very large number of qubits. """ n, num_ops = 5, 20 gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] @@ -132,10 +132,10 @@ def test_clifford_decompose_small_number_qubits_unitary(): assert_allclose_up_to_global_phase(cirq.unitary(expect_circ), cirq.unitary(circ), atol=1e-7) -def test_clifford_decompose_large_number_qubits_tableau(): - """Use tabeau comparison to validate the decomposition of random Clifford Tableau. +def test_clifford_decompose_by_reconstruction(): + """Validate the decomposition of random Clifford Tableau by reconstruction. - This approach can validate large number of qubits compared with unitary one. + This approach can validate large number of qubits compared with the unitary one. """ n, num_ops = 100, 500 gate_candidate = [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ] From 4ede0af15b3e336ba17b3a093b2012d2385a5062 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 9 Jun 2021 22:20:28 -0700 Subject: [PATCH 07/17] Add misaligned_qubits test --- cirq-core/cirq/optimizers/clifford_decomposition_test.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py index e936146030e..ac50f933fda 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition_test.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -12,12 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest import numpy as np import cirq from cirq.testing import assert_allclose_up_to_global_phase +def test_misaligned_qubits(): + qubits = cirq.LineQubit.range(1) + tableau = cirq.CliffordTableau(num_qubits=2) + with pytest.raises(ValueError): + ops = cirq.decompose_clifford_tableau_to_operations(qubits, tableau) + + def test_clifford_decompose_one_qubit(): """Two random instance for one qubit decomposition.""" qubits = cirq.LineQubit.range(1) From a076caa7df39eb916de413481a342c48123af0f6 Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 9 Jun 2021 22:25:04 -0700 Subject: [PATCH 08/17] fix the lint --- cirq-core/cirq/optimizers/clifford_decomposition_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py index ac50f933fda..c9e88556516 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition_test.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -23,7 +23,7 @@ def test_misaligned_qubits(): qubits = cirq.LineQubit.range(1) tableau = cirq.CliffordTableau(num_qubits=2) with pytest.raises(ValueError): - ops = cirq.decompose_clifford_tableau_to_operations(qubits, tableau) + cirq.decompose_clifford_tableau_to_operations(qubits, tableau) def test_clifford_decompose_one_qubit(): From 1a2ef9b967977433aa10ddc47f2e300f3bbdb6a9 Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Tue, 15 Jun 2021 21:47:32 -0700 Subject: [PATCH 09/17] Update cirq-core/cirq/optimizers/clifford_decomposition.py Co-authored-by: Balint Pato --- cirq-core/cirq/optimizers/clifford_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 826cdaae890..1fcab6d7376 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -146,6 +146,6 @@ def decompose_clifford_tableau_to_operations( _ = [_Z_with_ops(i) for i, p in enumerate(t.rs[: t.n]) if p] _ = [_X_with_ops(i) for i, p in enumerate(t.rs[t.n :]) if p] - # Step 5: invert the operations by reversing the orde: (AB)^{+} = B^{+} A^{+}. + # Step 5: invert the operations by reversing the order: (AB)^{+} = B^{+} A^{+}. # Note only S gate is not self-adjoint. return operations[::-1] From 9f87a053c8c81e801a8d6e9b89469e3b2d99481d Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Tue, 15 Jun 2021 21:47:40 -0700 Subject: [PATCH 10/17] Update cirq-core/cirq/optimizers/clifford_decomposition.py Co-authored-by: Balint Pato --- cirq-core/cirq/optimizers/clifford_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 1fcab6d7376..3ed179f7d5e 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -37,7 +37,7 @@ def _Z(q, args, operations, qubits): def _Sdg(q, args, operations, qubits): - # Apply the tableau with S^+, so the inverse of operation is S + # Apply the tableau with S^\{dagger} args.axes = [q] protocols.act_on(ops.ZPowGate() ** 1.5, args, allow_decompose=False) operations.append(ops.S(qubits[q])) From 56a536efc9fccb6fbddd62ca2b6979b3f49e3a6e Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Tue, 15 Jun 2021 21:47:45 -0700 Subject: [PATCH 11/17] Update cirq-core/cirq/optimizers/clifford_decomposition.py Co-authored-by: Balint Pato --- cirq-core/cirq/optimizers/clifford_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 3ed179f7d5e..94dba8cefbd 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Utility methods related to decompose clifford gate into circuits.""" +"""Utility methods to decompose Clifford gates into circuits.""" from typing import List, TYPE_CHECKING import functools From 31a30bfa1654f137703604da0a0130556044c48e Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Tue, 15 Jun 2021 21:48:02 -0700 Subject: [PATCH 12/17] Update cirq-core/cirq/optimizers/clifford_decomposition.py Co-authored-by: Balint Pato --- cirq-core/cirq/optimizers/clifford_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 94dba8cefbd..bff8c926ae7 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -104,7 +104,7 @@ def decompose_clifford_tableau_to_operations( # Destabilizers: [ A | B ] # Stabilizers: [ C | D ] for i in range(t.n): - # Step 1a: Make the diagonal element of A as 1 by Hadamard gate if necessary. + # Step 1a: Make the diagonal element of A equal to 1 by Hadamard gate if necessary. if not t.xs[i, i] and t.zs[i, i]: _H_with_ops(i) # Step 1b: Make the diagonal element of A as 1 by swapping gate if necessary. From a5bf0037ff4c83a10dff2ce31631f94cbd81e24a Mon Sep 17 00:00:00 2001 From: Bicheng Ying Date: Tue, 15 Jun 2021 21:48:08 -0700 Subject: [PATCH 13/17] Update cirq-core/cirq/optimizers/clifford_decomposition.py Co-authored-by: Balint Pato --- cirq-core/cirq/optimizers/clifford_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index bff8c926ae7..b73e394657b 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -107,7 +107,7 @@ def decompose_clifford_tableau_to_operations( # Step 1a: Make the diagonal element of A equal to 1 by Hadamard gate if necessary. if not t.xs[i, i] and t.zs[i, i]: _H_with_ops(i) - # Step 1b: Make the diagonal element of A as 1 by swapping gate if necessary. + # Step 1b: Make the diagonal element of A equal to 1 by SWAP gate if necessary. if not t.xs[i, i]: for j in range(i + 1, t.n): if t.xs[i, j]: From 6c2a9a3790f31712597cb67382e5461d16764210 Mon Sep 17 00:00:00 2001 From: ybc Date: Tue, 15 Jun 2021 22:06:36 -0700 Subject: [PATCH 14/17] Add type annotation --- .../cirq/optimizers/clifford_decomposition.py | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index b73e394657b..5435ac3e244 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -24,39 +24,71 @@ import cirq -def _X(q, args, operations, qubits): - args.axes = [q] +def _X( + q: int, + args: sim.ActOnCliffordTableauArgs, + operations: List[ops.Operation], + qubits: List['cirq.Qid'], +): + args.axes = (q,) protocols.act_on(ops.X, args, allow_decompose=False) operations.append(ops.X(qubits[q])) -def _Z(q, args, operations, qubits): - args.axes = [q] +def _Z( + q: int, + args: sim.ActOnCliffordTableauArgs, + operations: List[ops.Operation], + qubits: List['cirq.Qid'], +): + args.axes = (q,) protocols.act_on(ops.Z, args, allow_decompose=False) operations.append(ops.Z(qubits[q])) -def _Sdg(q, args, operations, qubits): +def _Sdg( + q: int, + args: sim.ActOnCliffordTableauArgs, + operations: List[ops.Operation], + qubits: List['cirq.Qid'], +): # Apply the tableau with S^\{dagger} - args.axes = [q] - protocols.act_on(ops.ZPowGate() ** 1.5, args, allow_decompose=False) + args.axes = (q,) + protocols.act_on(ops.S ** -1, args, allow_decompose=False) operations.append(ops.S(qubits[q])) -def _H(q, args, operations, qubits): - args.axes = [q] +def _H( + q: int, + args: sim.ActOnCliffordTableauArgs, + operations: List[ops.Operation], + qubits: List['cirq.Qid'], +): + args.axes = (q,) protocols.act_on(ops.H, args, allow_decompose=False) operations.append(ops.H(qubits[q])) -def _CNOT(q1, q2, args, operations, qubits): - args.axes = [q1, q2] +def _CNOT( + q1: int, + q2: int, + args: sim.ActOnCliffordTableauArgs, + operations: List[ops.Operation], + qubits: List['cirq.Qid'], +): + args.axes = (q1, q2) protocols.act_on(ops.CNOT, args, allow_decompose=False) operations.append(ops.CNOT(qubits[q1], qubits[q2])) -def _SWAP(q1, q2, args, operations, qubits): - args.axes = [q1, q2] +def _SWAP( + q1: int, + q2: int, + args: sim.ActOnCliffordTableauArgs, + operations: List[ops.Operation], + qubits: List['cirq.Qid'], +): + args.axes = (q1, q2) protocols.act_on(ops.SWAP, args, allow_decompose=False) operations.append(ops.SWAP(qubits[q1], qubits[q2])) From ad20805751c5adeea8465ae7ec360f024d6860ee Mon Sep 17 00:00:00 2001 From: ybc Date: Wed, 23 Jun 2021 20:56:08 -0700 Subject: [PATCH 15/17] Update the act_on for new style --- .../cirq/optimizers/clifford_decomposition.py | 20 +++--- .../optimizers/clifford_decomposition_test.py | 61 +++++++++---------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 5435ac3e244..279de77f749 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -30,8 +30,7 @@ def _X( operations: List[ops.Operation], qubits: List['cirq.Qid'], ): - args.axes = (q,) - protocols.act_on(ops.X, args, allow_decompose=False) + protocols.act_on(ops.X, args, qubits=[qubits[q]], allow_decompose=False) operations.append(ops.X(qubits[q])) @@ -41,8 +40,7 @@ def _Z( operations: List[ops.Operation], qubits: List['cirq.Qid'], ): - args.axes = (q,) - protocols.act_on(ops.Z, args, allow_decompose=False) + protocols.act_on(ops.Z, args, qubits=[qubits[q]], allow_decompose=False) operations.append(ops.Z(qubits[q])) @@ -53,8 +51,7 @@ def _Sdg( qubits: List['cirq.Qid'], ): # Apply the tableau with S^\{dagger} - args.axes = (q,) - protocols.act_on(ops.S ** -1, args, allow_decompose=False) + protocols.act_on(ops.S ** -1, args, qubits=[qubits[q]], allow_decompose=False) operations.append(ops.S(qubits[q])) @@ -64,8 +61,7 @@ def _H( operations: List[ops.Operation], qubits: List['cirq.Qid'], ): - args.axes = (q,) - protocols.act_on(ops.H, args, allow_decompose=False) + protocols.act_on(ops.H, args, qubits=[qubits[q]], allow_decompose=False) operations.append(ops.H(qubits[q])) @@ -76,8 +72,7 @@ def _CNOT( operations: List[ops.Operation], qubits: List['cirq.Qid'], ): - args.axes = (q1, q2) - protocols.act_on(ops.CNOT, args, allow_decompose=False) + protocols.act_on(ops.CNOT, args, qubits=[qubits[q1], qubits[q2]], allow_decompose=False) operations.append(ops.CNOT(qubits[q1], qubits[q2])) @@ -88,8 +83,7 @@ def _SWAP( operations: List[ops.Operation], qubits: List['cirq.Qid'], ): - args.axes = (q1, q2) - protocols.act_on(ops.SWAP, args, allow_decompose=False) + protocols.act_on(ops.SWAP, args, qubits=[qubits[q1], qubits[q2]], allow_decompose=False) operations.append(ops.SWAP(qubits[q1], qubits[q2])) @@ -116,7 +110,7 @@ def decompose_clifford_tableau_to_operations( t: qis.CliffordTableau = clifford_tableau.copy() operations: List[ops.Operation] = [] args = sim.ActOnCliffordTableauArgs( - tableau=t, axes=[], prng=np.random.RandomState(), log_of_measurement_results={} + tableau=t, qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={} ) _X_with_ops = functools.partial(_X, args=args, operations=operations, qubits=qubits) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition_test.py b/cirq-core/cirq/optimizers/clifford_decomposition_test.py index c9e88556516..64ba424ce51 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition_test.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition_test.py @@ -31,13 +31,13 @@ def test_clifford_decompose_one_qubit(): qubits = cirq.LineQubit.range(1) args = cirq.ActOnCliffordTableauArgs( tableau=cirq.CliffordTableau(num_qubits=1), - axes=[0], + qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}, ) - cirq.act_on(cirq.X, args, allow_decompose=False) - cirq.act_on(cirq.H, args, allow_decompose=False) - cirq.act_on(cirq.S, args, allow_decompose=False) + cirq.act_on(cirq.X, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.S, args, qubits=[qubits[0]], allow_decompose=False) expect_circ = cirq.Circuit(cirq.X(qubits[0]), cirq.H(qubits[0]), cirq.S(qubits[0])) ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) circ = cirq.Circuit(ops) @@ -46,15 +46,15 @@ def test_clifford_decompose_one_qubit(): qubits = cirq.LineQubit.range(1) args = cirq.ActOnCliffordTableauArgs( tableau=cirq.CliffordTableau(num_qubits=1), - axes=[0], + qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}, ) - cirq.act_on(cirq.Z, args, allow_decompose=False) - cirq.act_on(cirq.H, args, allow_decompose=False) - cirq.act_on(cirq.S, args, allow_decompose=False) - cirq.act_on(cirq.H, args, allow_decompose=False) - cirq.act_on(cirq.X, args, allow_decompose=False) + cirq.act_on(cirq.Z, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.S, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.X, args, qubits=[qubits[0]], allow_decompose=False) expect_circ = cirq.Circuit( cirq.Z(qubits[0]), cirq.H(qubits[0]), @@ -72,13 +72,12 @@ def test_clifford_decompose_two_qubits(): qubits = cirq.LineQubit.range(2) args = cirq.ActOnCliffordTableauArgs( tableau=cirq.CliffordTableau(num_qubits=2), - axes=[0], + qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}, ) - cirq.act_on(cirq.H, args, allow_decompose=False) - args.axes = [0, 1] - cirq.act_on(cirq.CNOT, args, allow_decompose=False) + cirq.act_on(cirq.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) expect_circ = cirq.Circuit(cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1])) ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) circ = cirq.Circuit(ops) @@ -87,18 +86,15 @@ def test_clifford_decompose_two_qubits(): qubits = cirq.LineQubit.range(2) args = cirq.ActOnCliffordTableauArgs( tableau=cirq.CliffordTableau(num_qubits=2), - axes=[0], + qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}, ) - cirq.act_on(cirq.H, args, allow_decompose=False) - args.axes = [0, 1] - cirq.act_on(cirq.CNOT, args, allow_decompose=False) - args.axes = [0] - cirq.act_on(cirq.H, args, allow_decompose=False) - cirq.act_on(cirq.S, args, allow_decompose=False) - args.axes = [1] - cirq.act_on(cirq.X, args, allow_decompose=False) + cirq.act_on(cirq.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.CNOT, args, qubits=[qubits[0], qubits[1]], allow_decompose=False) + cirq.act_on(cirq.H, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.S, args, qubits=[qubits[0]], allow_decompose=False) + cirq.act_on(cirq.X, args, qubits=[qubits[1]], allow_decompose=False) expect_circ = cirq.Circuit( cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1]), @@ -125,13 +121,14 @@ def test_clifford_decompose_by_unitary(): qubits = cirq.LineQubit.range(n) expect_circ = cirq.Circuit() args = cirq.ActOnCliffordTableauArgs( - tableau=t, axes=[], prng=prng, log_of_measurement_results={} + tableau=t, qubits=qubits, prng=prng, log_of_measurement_results={} ) for _ in range(num_ops): g = prng.randint(len(gate_candidate)) indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) - args.axes = indices - cirq.act_on(gate_candidate[g], args, allow_decompose=False) + cirq.act_on( + gate_candidate[g], args, qubits=[qubits[i] for i in indices], allow_decompose=False + ) expect_circ.append(gate_candidate[g].on(*[qubits[i] for i in indices])) ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) circ = cirq.Circuit(ops) @@ -153,22 +150,22 @@ def test_clifford_decompose_by_reconstruction(): qubits = cirq.LineQubit.range(n) expect_circ = cirq.Circuit() args = cirq.ActOnCliffordTableauArgs( - tableau=t, axes=[], prng=prng, log_of_measurement_results={} + tableau=t, qubits=qubits, prng=prng, log_of_measurement_results={} ) for _ in range(num_ops): g = prng.randint(len(gate_candidate)) indices = (prng.randint(n),) if g < 5 else prng.choice(n, 2, replace=False) - args.axes = indices - cirq.act_on(gate_candidate[g], args, allow_decompose=False) + cirq.act_on( + gate_candidate[g], args, qubits=[qubits[i] for i in indices], allow_decompose=False + ) expect_circ.append(gate_candidate[g].on(*[qubits[i] for i in indices])) ops = cirq.decompose_clifford_tableau_to_operations(qubits, args.tableau) reconstruct_t = cirq.CliffordTableau(num_qubits=n) reconstruct_args = cirq.ActOnCliffordTableauArgs( - tableau=reconstruct_t, axes=[], prng=prng, log_of_measurement_results={} + tableau=reconstruct_t, qubits=qubits, prng=prng, log_of_measurement_results={} ) for op in ops: - reconstruct_args.axes = [q.x for q in op.qubits] - cirq.act_on(op.gate, reconstruct_args, allow_decompose=False) + cirq.act_on(op.gate, reconstruct_args, qubits=op.qubits, allow_decompose=False) assert t == reconstruct_t From db940eddbc9b9d08c348e0d7b5fec73dc6ce4809 Mon Sep 17 00:00:00 2001 From: ybc Date: Tue, 28 Sep 2021 20:13:11 -0700 Subject: [PATCH 16/17] Fix lint error about documenting the raise. --- cirq-core/cirq/optimizers/clifford_decomposition.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 279de77f749..323e95f8011 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -98,6 +98,9 @@ def decompose_clifford_tableau_to_operations( Returns: A list of operations reconstructs the same Clifford tableau. + + Raises: + ValueError: The length of input qubit mismatch with the size of tabluea. """ if len(qubits) != clifford_tableau.n: raise ValueError( From 67147e3f132a304bbd145d96bc2dcb2ff019a71a Mon Sep 17 00:00:00 2001 From: ybc Date: Thu, 30 Sep 2021 20:04:41 -0700 Subject: [PATCH 17/17] Add paper link to docstring and fix a typo --- cirq-core/cirq/optimizers/clifford_decomposition.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/optimizers/clifford_decomposition.py b/cirq-core/cirq/optimizers/clifford_decomposition.py index 323e95f8011..557676b2a3a 100644 --- a/cirq-core/cirq/optimizers/clifford_decomposition.py +++ b/cirq-core/cirq/optimizers/clifford_decomposition.py @@ -92,6 +92,10 @@ def decompose_clifford_tableau_to_operations( ) -> List[ops.Operation]: """Decompose an n-qubit Clifford Tableau into a list of one/two qubit operations. + The implementation is based on Theorem 8 in [1]. + [1] S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*, + Phys. Rev. A 70, 052328 (2004). https://arxiv.org/abs/quant-ph/0406196 + Args: qubits: The list of qubits being operated on. clifford_tableau: The Clifford Tableau for decomposition. @@ -100,7 +104,7 @@ def decompose_clifford_tableau_to_operations( A list of operations reconstructs the same Clifford tableau. Raises: - ValueError: The length of input qubit mismatch with the size of tabluea. + ValueError: The length of input qubit mismatch with the size of tableau. """ if len(qubits) != clifford_tableau.n: raise ValueError(