Skip to content

Commit

Permalink
Add circuit generation from set of stabilizers (Qiskit#11483)
Browse files Browse the repository at this point in the history
* Add stabilizer_to_circuit function

* Fix verification test

* Mention `stabilizer_to_circuit` in `StabilizerState` documentation

* * Add `from_stabilizer_list` function to Stabilizer state
* Move to synthesis library and rename accordingly
* Add pseudo-random tests
* Fix docstrings

* doc fixes

* Allow any Collection and not just list, fix naming, tests, and docs accordingly.

* typo

* missed rename

* Documentation fixes

* fix indent

* fix indent 2

* fix indent 3

* Rewrite using `Pauli` instead of string.
  • Loading branch information
Randl authored and ihincks committed Jan 19, 2024
1 parent 7718d7e commit 74bddd9
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 1 deletion.
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.
"""

# 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)

0 comments on commit 74bddd9

Please sign in to comment.