Skip to content

Commit

Permalink
Synthesis of a stabilizer state for LNN connectivity (#9734)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ShellyGarion committed Apr 4, 2023
1 parent 2ebf81e commit 93ab1f9
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 2 deletions.
10 changes: 10 additions & 0 deletions qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
========================
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion qiskit/synthesis/clifford/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
15 changes: 15 additions & 0 deletions qiskit/synthesis/stabilizer/__init__.py
Original file line number Diff line number Diff line change
@@ -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
188 changes: 188 additions & 0 deletions qiskit/synthesis/stabilizer/stabilizer_decompose.py
Original file line number Diff line number Diff line change
@@ -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] <https://arxiv.org/abs/2003.09412>`_
"""

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] <https://arxiv.org/abs/2003.09412>`_
2. Dmitri Maslov, Martin Roetteler,
*Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*,
`arXiv:1705.09176 <https://arxiv.org/abs/1705.09176>`_.
"""

circ = synth_stabilizer_layers(
stab,
cz_synth_func=synth_cz_depth_line_mr,
cz_func_reverse_qubits=True,
)
return circ
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 1 addition & 1 deletion test/python/synthesis/test_clifford_decompose_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
101 changes: 101 additions & 0 deletions test/python/synthesis/test_stabilizer_synthesis.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 93ab1f9

Please sign in to comment.