From 93ab1f919cd9f0f472ed22474bb0d39e3637b484 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Tue, 4 Apr 2023 10:00:05 +0300 Subject: [PATCH] Synthesis of a stabilizer state for LNN connectivity (#9734) * minor * add synthesis of a stabilizer state into layers * add a test of StabilizerState decomposition * add stabilizer state synthesis for LNN connectivity and tests * updates following review comments * add documentation * minor update * add release notes * minor update * add a test for a reduced inverse Clifford * improved stabilizer synthesis. Co-authored-by: alexi@il.ibm.com * minor updates following review * combining tests --- qiskit/synthesis/__init__.py | 10 + qiskit/synthesis/clifford/__init__.py | 2 +- qiskit/synthesis/stabilizer/__init__.py | 15 ++ .../stabilizer/stabilizer_decompose.py | 188 ++++++++++++++++++ ...izer_state_synthesis-c48c0389941715a6.yaml | 11 + .../test_clifford_decompose_layers.py | 2 +- .../synthesis/test_stabilizer_synthesis.py | 101 ++++++++++ 7 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 qiskit/synthesis/stabilizer/__init__.py create mode 100644 qiskit/synthesis/stabilizer/stabilizer_decompose.py create mode 100644 releasenotes/notes/stabilizer_state_synthesis-c48c0389941715a6.yaml create mode 100644 test/python/synthesis/test_stabilizer_synthesis.py diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index eb3adae2955a..6e86c7ba4318 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -78,6 +78,15 @@ synth_cnotdihedral_two_qubits synth_cnotdihedral_general +Stabilizer State Synthesis +========================== + +.. autosummary:: + :toctree: ../stubs/ + + synth_stabilizer_layers + synth_stabilizer_depth_lnn + Discrete Basis Synthesis ======================== @@ -121,4 +130,5 @@ synth_cnotdihedral_two_qubits, synth_cnotdihedral_general, ) +from .stabilizer import synth_stabilizer_layers, synth_stabilizer_depth_lnn from .discrete_basis import SolovayKitaevDecomposition, generate_basic_approximations diff --git a/qiskit/synthesis/clifford/__init__.py b/qiskit/synthesis/clifford/__init__.py index 5b170b2c2492..950f0af1153e 100644 --- a/qiskit/synthesis/clifford/__init__.py +++ b/qiskit/synthesis/clifford/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2017, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory diff --git a/qiskit/synthesis/stabilizer/__init__.py b/qiskit/synthesis/stabilizer/__init__.py new file mode 100644 index 000000000000..1e17fa81ee0f --- /dev/null +++ b/qiskit/synthesis/stabilizer/__init__.py @@ -0,0 +1,15 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Module containing stabilizer state preparation circuit synthesis.""" + +from .stabilizer_decompose import synth_stabilizer_layers, synth_stabilizer_depth_lnn diff --git a/qiskit/synthesis/stabilizer/stabilizer_decompose.py b/qiskit/synthesis/stabilizer/stabilizer_decompose.py new file mode 100644 index 000000000000..598a26f6a7ee --- /dev/null +++ b/qiskit/synthesis/stabilizer/stabilizer_decompose.py @@ -0,0 +1,188 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Circuit synthesis for a stabilizer state preparation circuit. +""" +# pylint: disable=invalid-name + +import numpy as np +from qiskit.circuit import QuantumCircuit +from qiskit.exceptions import QiskitError +from qiskit.quantum_info.states import StabilizerState +from qiskit.synthesis.linear.linear_matrix_utils import ( + calc_inverse_matrix, +) +from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr +from qiskit.synthesis.clifford.clifford_decompose_layers import ( + _default_cz_synth_func, + _reverse_clifford, + _create_graph_state, + _decompose_graph_state, +) + + +def synth_stabilizer_layers( + stab, + cz_synth_func=_default_cz_synth_func, + cz_func_reverse_qubits=False, + validate=False, +): + """Synthesis of a stabilizer state into layers. + + It provides a similar decomposition to the synthesis described in Lemma 8 of Bravyi and Maslov, + without the initial Hadamard-free sub-circuit which do not affect the stabilizer state. + + For example, a 5-qubit stabilizer state is decomposed into the following layers: + + .. parsed-literal:: + ┌─────┐┌─────┐┌─────┐┌─────┐┌────────┐ + q_0: ┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├ + │ ││ ││ ││ ││ │ + q_1: ┤1 ├┤1 ├┤1 ├┤1 ├┤1 ├ + │ ││ ││ ││ ││ │ + q_2: ┤2 H2 ├┤2 S1 ├┤2 CZ ├┤2 H1 ├┤2 Pauli ├ + │ ││ ││ ││ ││ │ + q_3: ┤3 ├┤3 ├┤3 ├┤3 ├┤3 ├ + │ ││ ││ ││ ││ │ + q_4: ┤4 ├┤4 ├┤4 ├┤4 ├┤4 ├ + └─────┘└─────┘└─────┘└─────┘└────────┘ + + Args: + stab (StabilizerState): a stabilizer state. + cz_synth_func (Callable): a function to decompose the CZ sub-circuit. + It gets as input a boolean symmetric matrix, and outputs a QuantumCircuit. + validate (Boolean): if True, validates the synthesis process. + cz_func_reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr, + since this function returns a circuit that reverts the order of qubits. + + Return: + QuantumCircuit: a circuit implementation of the stabilizer state. + + Raises: + QiskitError: if the input is not a StabilizerState. + + Reference: + 1. S. Bravyi, D. Maslov, *Hadamard-free circuits expose the + structure of the Clifford group*, + `arXiv:2003.09412 [quant-ph] `_ + """ + + if not isinstance(stab, StabilizerState): + raise QiskitError("The input is not a StabilizerState.") + + cliff = stab.clifford + num_qubits = cliff.num_qubits + + if cz_func_reverse_qubits: + cliff0 = _reverse_clifford(cliff) + else: + cliff0 = cliff + + H1_circ, cliff1 = _create_graph_state(cliff0, validate=validate) + + H2_circ, CZ1_circ, S1_circ, _ = _decompose_graph_state( + cliff1, validate=validate, cz_synth_func=cz_synth_func + ) + + qubit_list = list(range(num_qubits)) + layeredCircuit = QuantumCircuit(num_qubits) + + layeredCircuit.append(H2_circ, qubit_list) + layeredCircuit.append(S1_circ, qubit_list) + layeredCircuit.append(CZ1_circ, qubit_list) + + if cz_func_reverse_qubits: + H1_circ = H1_circ.reverse_bits() + layeredCircuit.append(H1_circ, qubit_list) + + # Add Pauli layer to fix the Clifford phase signs + # pylint: disable=cyclic-import + from qiskit.quantum_info.operators.symplectic import Clifford + + clifford_target = Clifford(layeredCircuit) + pauli_circ = _calc_pauli_diff_stabilizer(cliff, clifford_target) + layeredCircuit.append(pauli_circ, qubit_list) + + return layeredCircuit + + +def _calc_pauli_diff_stabilizer(cliff, cliff_target): + """Given two Cliffords whose stabilizers differ by a Pauli, we find this Pauli.""" + + # pylint: disable=cyclic-import + from qiskit.quantum_info.operators.symplectic import Pauli + + num_qubits = cliff.num_qubits + if cliff.num_qubits != cliff_target.num_qubits: + raise QiskitError("num_qubits is not the same for the original clifford and the target.") + + # stabilizer generators of the original clifford + stab_gen = StabilizerState(cliff).clifford.to_dict()["stabilizer"] + + # stabilizer state of the target clifford + ts = StabilizerState(cliff_target) + + phase_destab = [False] * num_qubits + phase_stab = [ts.expectation_value(Pauli(stab_gen[i])) == -1 for i in range(num_qubits)] + + phase = [] + phase.extend(phase_destab) + phase.extend(phase_stab) + phase = np.array(phase, dtype=int) + + A = cliff.symplectic_matrix.astype(int) + Ainv = calc_inverse_matrix(A) + + # By carefully writing how X, Y, Z gates affect each qubit, all we need to compute + # is A^{-1} * (phase) + C = np.matmul(Ainv, phase) % 2 + + # Create the Pauli + pauli_circ = QuantumCircuit(num_qubits, name="Pauli") + for k in range(num_qubits): + destab = C[k] + stab = C[k + num_qubits] + if stab and destab: + pauli_circ.y(k) + elif stab: + pauli_circ.x(k) + elif destab: + pauli_circ.z(k) + + return pauli_circ + + +def synth_stabilizer_depth_lnn(stab): + """Synthesis of an n-qubit stabilizer state for linear-nearest neighbour connectivity, + in 2-qubit depth 2*n+2 and two distinct CX layers, using CX and phase gates (S, Sdg or Z). + + Args: + stab (StabilizerState): a stabilizer state. + + Return: + QuantumCircuit: a circuit implementation of the stabilizer state. + + Reference: + 1. S. Bravyi, D. Maslov, *Hadamard-free circuits expose the + structure of the Clifford group*, + `arXiv:2003.09412 [quant-ph] `_ + 2. Dmitri Maslov, Martin Roetteler, + *Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*, + `arXiv:1705.09176 `_. + """ + + circ = synth_stabilizer_layers( + stab, + cz_synth_func=synth_cz_depth_line_mr, + cz_func_reverse_qubits=True, + ) + return circ diff --git a/releasenotes/notes/stabilizer_state_synthesis-c48c0389941715a6.yaml b/releasenotes/notes/stabilizer_state_synthesis-c48c0389941715a6.yaml new file mode 100644 index 000000000000..4d07c620baa7 --- /dev/null +++ b/releasenotes/notes/stabilizer_state_synthesis-c48c0389941715a6.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Add a new synthesis method :class:`.synth_stabilizer_layers` of a stabilizer state into layers. + It provides a similar decomposition to the synthesis described in Lemma 8 of Bravyi and Maslov, + (arxiv:2003.09412) without the initial Hadamard-free sub-circuit which does not affect the stabilizer state. + - | + Add a new synthesis method :class:`.synth_stabilizer_lnn` of a stabilizer state + for linear nearest neighbor connectivity in 2-qubit depth of 2n+2 and two distinct CX layers, + using CX and phase gates (S, Sdg or Z). + The synthesis algorithm is based on the paper of Maslov and Roetteler (https://arxiv.org/abs/1705.09176). diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 2cd2a9ffb78c..79b791141188 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Tests for Clifford class.""" +"""Tests for Clifford synthesis methods.""" import unittest from test import combine diff --git a/test/python/synthesis/test_stabilizer_synthesis.py b/test/python/synthesis/test_stabilizer_synthesis.py new file mode 100644 index 000000000000..5e3b8ce61b14 --- /dev/null +++ b/test/python/synthesis/test_stabilizer_synthesis.py @@ -0,0 +1,101 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for stabilizer state synthesis methods.""" + + +import unittest +from test import combine +from ddt import ddt + +import numpy as np + +from qiskit.test import QiskitTestCase +from qiskit.quantum_info.states import StabilizerState +from qiskit.quantum_info import random_clifford +from qiskit.synthesis.stabilizer import synth_stabilizer_layers, synth_stabilizer_depth_lnn +from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity + + +@ddt +class TestStabDecomposeLayers(QiskitTestCase): + """Tests for stabilizer state decomposition functions.""" + + @combine(num_qubits=[4, 5, 6, 7]) + def test_decompose_stab(self, num_qubits): + """Create layer decomposition for a stabilizer state, and check that it + results in an equivalent stabilizer state.""" + rng = np.random.default_rng(1234) + samples = 10 + for _ in range(samples): + cliff = random_clifford(num_qubits, seed=rng) + stab = StabilizerState(cliff) + circ = synth_stabilizer_layers(stab, validate=True) + stab_target = StabilizerState(circ) + # Verify that the two stabilizers generate the same state + self.assertTrue(stab.equiv(stab_target)) + # Verify that the two stabilizers produce the same probabilities + self.assertEqual(stab.probabilities_dict(), stab_target.probabilities_dict()) + # Verify the layered structure + self.assertEqual(circ.data[0].operation.name, "H2") + self.assertEqual(circ.data[1].operation.name, "S1") + self.assertEqual(circ.data[2].operation.name, "CZ") + self.assertEqual(circ.data[3].operation.name, "H1") + self.assertEqual(circ.data[4].operation.name, "Pauli") + + @combine(num_qubits=[4, 5, 6, 7]) + def test_decompose_lnn_depth(self, num_qubits): + """Test stabilizer state decomposition for linear-nearest-neighbour (LNN) connectivity.""" + rng = np.random.default_rng(1234) + samples = 10 + for _ in range(samples): + cliff = random_clifford(num_qubits, seed=rng) + stab = StabilizerState(cliff) + circ = synth_stabilizer_depth_lnn(stab) + # Check that the stabilizer state circuit 2-qubit depth equals 2*n+2 + depth2q = (circ.decompose()).depth( + filter_function=lambda x: x.operation.num_qubits == 2 + ) + self.assertTrue(depth2q == 2 * num_qubits + 2) + # Check that the stabilizer state circuit has linear nearest neighbour connectivity + self.assertTrue(check_lnn_connectivity(circ.decompose())) + stab_target = StabilizerState(circ) + # Verify that the two stabilizers generate the same state + self.assertTrue(stab.equiv(stab_target)) + # Verify that the two stabilizers produce the same probabilities + self.assertEqual(stab.probabilities_dict(), stab_target.probabilities_dict()) + + @combine(num_qubits=[4, 5], method_lnn=[True, False]) + def test_reduced_inverse_clifford(self, num_qubits, method_lnn): + """Test that one can use this stabilizer state synthesis method to calculate an inverse Clifford + that preserves the ground state |0...0>, with a reduced circuit depth. + This is useful for multi-qubit Randomized Benchmarking.""" + rng = np.random.default_rng(5678) + samples = 5 + for _ in range(samples): + cliff = random_clifford(num_qubits, seed=rng) + circ_orig = cliff.to_circuit() + stab = StabilizerState(cliff) + # Calculate the reduced-depth inverse Clifford + if method_lnn: + circ_inv = synth_stabilizer_depth_lnn(stab).inverse() + else: + circ_inv = synth_stabilizer_layers(stab, validate=True).inverse() + circ = circ_orig.compose(circ_inv) + stab = StabilizerState(circ) + # Verify that we get back the ground state |0...0> with probability 1 + target_probs = {"0" * num_qubits: 1} + self.assertEqual(stab.probabilities_dict(), target_probs) + + +if __name__ == "__main__": + unittest.main()