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
42 changes: 42 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 state stabilized by the list
Randl marked this conversation as resolved.
Show resolved Hide resolved

.. 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,34 @@ 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 stabilizer state from the collection of stabilizers.
Randl marked this conversation as resolved.
Show resolved Hide resolved

Args:
stabilizers (Collection[str]): list of stabilizer strings
allow_redundant (bool): allow redundant stabilizers
allow_underconstrained (bool): allow underconstrained set of stabilizers
Randl marked this conversation as resolved.
Show resolved Hide resolved

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

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
227 changes: 227 additions & 0 deletions qiskit/synthesis/stabilizer/stabilizer_circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017--2023
#
Randl marked this conversation as resolved.
Show resolved Hide resolved
# 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

from qiskit.exceptions import QiskitError

from qiskit.circuit import QuantumCircuit

from qiskit.quantum_info.operators.symplectic.clifford import Clifford
from qiskit.quantum_info.operators.symplectic.pauli import Pauli


def _add_sign(stabilizer: str) -> str:
"""
Add a sign to stabilizer if it is missing.

Args:
stabilizer (str): stabilizer string

Return:
str: stabilizer string with sign
"""
if stabilizer[0] not in ["+", "-"]:
return "+" + stabilizer
return stabilizer


def _drop_sign(stabilizer: str) -> str:
"""
Drop sign from stabilizer if it is present.

Args:
stabilizer (str): stabilizer string

Return:
str: stabilizer string without sign
"""
if stabilizer[0] not in ["+", "-"]:
return stabilizer
if stabilizer[1] == "i":
return stabilizer[2:]
return stabilizer[1:]


def _check_stabilizers_commutator(s_1: str, s_2: str) -> bool:
"""
Check if two stabilizers commute.

Args:
s_1 (str): stabilizer string
s_1 (str): stabilizer string

Return:
bool: True if stabilizers commute, False otherwise.
"""
prod = 1
for o1, o2 in zip(_drop_sign(s_1), _drop_sign(s_2)):
if o1 == "I" or o2 == "I":
continue
if o1 != o2:
prod *= -1
return prod == 1
Randl marked this conversation as resolved.
Show resolved Hide resolved


def _apply_circuit_on_stabilizer(stabilizer: str, circuit: QuantumCircuit) -> str:
"""
Given a stabilizer string and a circuit, conjugate the circuit on the stabilizer.

Args:
stabilizer (str): stabilizer string
circuit (QuantumCircuit): Clifford circuit to apply

Return:
str: a pauli string after conjugation.
"""
cliff = Clifford(circuit)
stab_operator = Pauli(stabilizer)
pauli_conjugated = stab_operator.evolve(cliff, frame="s")
return pauli_conjugated.to_label()


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 state stabilized by the stabilziers
Randl marked this conversation as resolved.
Show resolved Hide resolved
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
allow_underconstrained (bool): allow underconstrained set of stabilizers
invert (bool): return inverse circuit
Randl marked this conversation as resolved.
Show resolved Hide resolved

Return:
QuantumCircuit: a circuit that generates state stabilized by `stabilizers`.
Randl marked this conversation as resolved.
Show resolved Hide resolved

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 = list(stabilizers)

# verification
for i, stabilizer in enumerate(stabilizer_list):
if set(stabilizer) - set("IXYZ+-i"):
raise QiskitError(f"Stabilizer {i} ({stabilizer}) contains invalid characters")
if stabilizer[1] == "i":
raise QiskitError(f"Stabilizer {i} ({stabilizer}) has an invalid phase")
Randl marked this conversation as resolved.
Show resolved Hide resolved
for i in range(len(stabilizer_list)):
for j in range(i + 1, len(stabilizer_list)):
if not _check_stabilizers_commutator(stabilizer_list[i], stabilizer_list[j]):
raise QiskitError(
f"Stabilizers {i} ({stabilizer_list[i]}) and {j} ({stabilizer_list[j]}) "
"do not commute"
)

num_qubits = len(_drop_sign(stabilizer_list[0]))
circuit = QuantumCircuit(num_qubits)

used = 0
for i in range(len(stabilizer_list)):
curr_stab = _add_sign(_apply_circuit_on_stabilizer(stabilizer_list[i], circuit))

# Find pivot.
pivot = used + 1
while pivot <= num_qubits:
if curr_stab[pivot] != "I":
break
pivot += 1
pivot_index = num_qubits - pivot

Randl marked this conversation as resolved.
Show resolved Hide resolved
if pivot == num_qubits + 1:
if "X" in curr_stab or "Y" in curr_stab:
raise QiskitError(
f"Stabilizer {i} ({stabilizer_list[i]}) anti-commutes with some of "
"the previous stabilizers"
)
if curr_stab[0] == "-":
raise QiskitError(
f"Stabilizer {i} ({stabilizer_list[i]}) contradicts "
"some of the previous stabilizers"
)
if "Z" in curr_stab 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":
circuit.h(pivot_index)
elif curr_stab[pivot] == "Y":
circuit.h(pivot_index)
circuit.s(pivot_index)
circuit.h(pivot_index)
circuit.s(pivot_index)
circuit.s(pivot_index)

# Cancel other terms in Pauli string.
for j in range(1, num_qubits + 1):
j_index = num_qubits - j
if j_index == pivot_index or curr_stab[j] == "I":
continue
if curr_stab[j] == "X":
circuit.h(pivot_index)
circuit.cx(pivot_index, j_index)
circuit.h(pivot_index)
elif curr_stab[j] == "Y":
circuit.h(pivot_index)
circuit.s(j_index)
circuit.s(j_index)
circuit.s(j_index)
circuit.cx(pivot_index, j_index)
circuit.h(pivot_index)
circuit.s(j_index)
elif curr_stab[j] == "Z":
circuit.cx(j_index, pivot_index)

# Move pivot to diagonal.
used_index = num_qubits - used - 1
if pivot_index != used_index:
circuit.swap(pivot_index, used_index)

# fix sign
curr_stab = _add_sign(_apply_circuit_on_stabilizer(stabilizer_list[i], circuit))
if curr_stab[0] == "-":
circuit.x(used_index)
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,9 @@
---
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.
Randl marked this conversation as resolved.
Show resolved Hide resolved