Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add circuit generation from set of stabilizers #11483

Merged
merged 13 commits into from
Jan 18, 2024
44 changes: 44 additions & 0 deletions qiskit/quantum_info/states/stabilizerstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"""

from __future__ import annotations

from collections.abc import Collection

import numpy as np

from qiskit.exceptions import QiskitError
Expand Down Expand Up @@ -57,6 +60,17 @@ class StabilizerState(QuantumState):
{'00': 0.5, '11': 0.5}
1

Given a list of stabilizers, :meth:`qiskit.quantum_info.StabilizerState.from_stabilizer_list`
returns a state stabilized by the list

.. code-block:: python

from qiskit.quantum_info import StabilizerState

stabilizer_list = ["ZXX", "-XYX", "+ZYY"]
stab = StabilizerState.from_stabilizer_list(stabilizer_list)


References:
1. S. Aaronson, D. Gottesman, *Improved Simulation of Stabilizer Circuits*,
Phys. Rev. A 70, 052328 (2004).
Expand Down Expand Up @@ -91,6 +105,36 @@ def __init__(
# Initialize
super().__init__(op_shape=OpShape.auto(num_qubits_r=self._data.num_qubits, num_qubits_l=0))

@classmethod
def from_stabilizer_list(
cls,
stabilizers: Collection[str],
allow_redundant: bool = False,
allow_underconstrained: bool = False,
) -> StabilizerState:
"""Create a stabilizer state from the collection of stabilizers.

Args:
stabilizers (Collection[str]): list of stabilizer strings
allow_redundant (bool): allow redundant stabilizers (i.e., some stabilizers
can be products of the others)
allow_underconstrained (bool): allow underconstrained set of stabilizers (i.e.,
the stabilizers do not specify a unique state)

Return:
StabilizerState: a state stabilized by stabilizers.
Comment on lines +124 to +125
Copy link
Contributor

@alexanderivrii alexanderivrii Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually use Returns: instead of Return: (though I do see Return: in multiple places in the code as well). Let me address this to a higher autority, @jakelishman.

"""

# pylint: disable=cyclic-import
from qiskit.synthesis.stabilizer import synth_circuit_from_stabilizers

circuit = synth_circuit_from_stabilizers(
stabilizers,
allow_redundant=allow_redundant,
allow_underconstrained=allow_underconstrained,
)
return cls(circuit)

def __eq__(self, other):
return (self._data.stab == other._data.stab).all()

Expand Down
7 changes: 6 additions & 1 deletion qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@

.. autofunction:: synth_stabilizer_layers
.. autofunction:: synth_stabilizer_depth_lnn
.. autofunction:: synth_circuit_from_stabilizers

Discrete Basis Synthesis
========================
Expand Down Expand Up @@ -123,6 +124,10 @@
synth_cnotdihedral_two_qubits,
synth_cnotdihedral_general,
)
from .stabilizer import synth_stabilizer_layers, synth_stabilizer_depth_lnn
from .stabilizer import (
synth_stabilizer_layers,
synth_stabilizer_depth_lnn,
synth_circuit_from_stabilizers,
)
from .discrete_basis import SolovayKitaevDecomposition, generate_basic_approximations
from .qft import synth_qft_line
1 change: 1 addition & 0 deletions qiskit/synthesis/stabilizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
"""Module containing stabilizer state preparation circuit synthesis."""

from .stabilizer_decompose import synth_stabilizer_layers, synth_stabilizer_depth_lnn
from .stabilizer_circuit import synth_circuit_from_stabilizers
149 changes: 149 additions & 0 deletions qiskit/synthesis/stabilizer/stabilizer_circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# 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.
"""
Stabilizer to circuit function
"""
from __future__ import annotations

from collections.abc import Collection

import numpy as np

from qiskit.quantum_info import PauliList
from qiskit.exceptions import QiskitError
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info.operators.symplectic.clifford import Clifford


def synth_circuit_from_stabilizers(
stabilizers: Collection[str],
allow_redundant: bool = False,
allow_underconstrained: bool = False,
invert: bool = False,
) -> QuantumCircuit:
# pylint: disable=line-too-long
"""Synthesis of a circuit that generates a state stabilized by the stabilziers
using Gaussian elimination with Clifford gates.
If the stabilizers are underconstrained, and `allow_underconstrained` is `True`,
the circuit will output one of the states stabilized by the stabilizers.
Based on stim implementation.

Args:
stabilizers (Collection[str]): list of stabilizer strings
allow_redundant (bool): allow redundant stabilizers (i.e., some stabilizers
can be products of the others)
allow_underconstrained (bool): allow underconstrained set of stabilizers (i.e.,
the stabilizers do not specify a unique state)
invert (bool): return inverse circuit

Return:
QuantumCircuit: a circuit that generates a state stabilized by `stabilizers`.

Raises:
QiskitError: if the stabilizers are invalid, do not commute, or contradict each other,
if the list is underconstrained and `allow_underconstrained` is `False`,
or if the list is redundant and `allow_redundant` is `False`.

Reference:
1. https://github.com/quantumlib/Stim/blob/c0dd0b1c8125b2096cd54b6f72884a459e47fe3e/src/stim/stabilizers/conversions.inl#L469
2. https://quantumcomputing.stackexchange.com/questions/12721/how-to-calculate-destabilizer-group-of-toric-and-other-codes

"""
stabilizer_list = PauliList(stabilizers)
if np.any(stabilizer_list.phase % 2):
raise QiskitError("Some stabilizers have an invalid phase")
if len(stabilizer_list.commutes_with_all(stabilizer_list)) < len(stabilizer_list):
raise QiskitError("Some stabilizers do not commute.")

num_qubits = stabilizer_list.num_qubits
circuit = QuantumCircuit(num_qubits)

used = 0
for i in range(len(stabilizer_list)):
curr_stab = stabilizer_list[i].evolve(Clifford(circuit), frame="s")

# Find pivot.
pivot = used
while pivot < num_qubits:
if curr_stab[pivot].x or curr_stab[pivot].z:
break
pivot += 1

if pivot == num_qubits:
if curr_stab.x.any():
raise QiskitError(
f"Stabilizer {i} ({stabilizer_list[i]}) anti-commutes with some of "
"the previous stabilizers."
)
if curr_stab.phase == 2:
raise QiskitError(
f"Stabilizer {i} ({stabilizer_list[i]}) contradicts "
"some of the previous stabilizers."
)
if curr_stab.z.any() and not allow_redundant:
raise QiskitError(
f"Stabilizer {i} ({stabilizer_list[i]}) is a product of the others "
"and allow_redundant is False. Add allow_redundant=True "
"to the function call if you want to allow redundant stabilizers."
)
continue

# Change pivot basis to the Z axis.
if curr_stab[pivot].x:
if curr_stab[pivot].z:
circuit.h(pivot)
circuit.s(pivot)
circuit.h(pivot)
circuit.s(pivot)
circuit.s(pivot)
else:
circuit.h(pivot)

# Cancel other terms in Pauli string.
for j in range(num_qubits):
if j == pivot or not (curr_stab[j].x or curr_stab[j].z):
continue
p = curr_stab[j].x + curr_stab[j].z * 2
if p == 1: # X
circuit.h(pivot)
circuit.cx(pivot, j)
circuit.h(pivot)
elif p == 2: # Z
circuit.cx(j, pivot)
elif p == 3: # Y
circuit.h(pivot)
circuit.s(j)
circuit.s(j)
circuit.s(j)
circuit.cx(pivot, j)
circuit.h(pivot)
circuit.s(j)

# Move pivot to diagonal.
if pivot != used:
circuit.swap(pivot, used)

# fix sign
curr_stab = stabilizer_list[i].evolve(Clifford(circuit), frame="s")
if curr_stab.phase == 2:
circuit.x(used)
used += 1

if used < num_qubits and not allow_underconstrained:
raise QiskitError(
"Stabilizers are underconstrained and allow_underconstrained is False."
" Add allow_underconstrained=True to the function call "
"if you want to allow underconstrained stabilizers."
)
if invert:
return circuit
return circuit.inverse()
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
features:
- |
Add :func:`qiskit.synthesis.synth_circuit_from_stabilizers` function that, given stabilizers,
returns a circuit that outputs the state stabilized by the stabilizers.

- |
Add :meth:`qiskit.quantum_info.StabilizerState.from_stabilizer_list` method
that generates a stabilizer state from a list of stabilizers::

from qiskit.quantum_info import StabilizerState

stabilizer_list = ["ZXX", "-XYX", "+ZYY"]
stab = StabilizerState.from_stabilizer_list(stabilizer_list)
Loading