#!/usr/bin/python
# -*- coding: utf-8 -*-
##
# stab.py: Classes and methods encapsulating and manipulating stabilizer codes.
##
# © 2012 Christopher E. Granade (cgranade@gmail.com) and
# Ben Criger (bcriger@gmail.com).
# This file is a part of the QuaEC project.
# Licensed under the AGPL version 3.
##
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
## IMPORTS ##
from sys import version_info
if version_info[0] == 3:
PY3 = True
from importlib import reload
elif version_info[0] == 2:
PY3 = False
else:
raise EnvironmentError("sys.version_info refers to a version of "
"Python neither 2 nor 3. This is not permitted. "
"sys.version_info = {}".format(version_info))
import operator as op
import itertools as it
import math
from collections import defaultdict
import warnings
from functools import reduce
if PY3:
from . import PauliClass as p # Sorry for the confusing notation here.
from . import CliffordClass as c
from . import paulicollections as pc
from . import constraint_solvers as cs
from . import circuit as circuit
from . import utils as u
from .singletons import EmptyClifford, Unspecified
else:
import PauliClass as p # Sorry for the confusing notation here.
import CliffordClass as c
import paulicollections as pc
import constraint_solvers as cs
import circuit as circuit
import utils as u
from singletons import EmptyClifford, Unspecified
## ALL ##
__all__ = [
'StabilizerCode'
]
## CLASSES ##
[docs]class StabilizerCode(object):
r"""
Class representing a stabilizer code specified by the generators of its
stabilizer group and by representatives for the logical operators acting
on the code.
:param group_generators: Generators :math:`N_i` such that the stabilizer
group :math:`S` of the represented code is given by
:math:`S = \left\langle N_i \right\rangle`.
:param logical_xs: Representatives for the logical :math:`X` operators
acting on encoded states.
:param logical_zs: Representatives for the logical :math:`Z` operators
acting on encoded states.
:param str label: User-facing name for the stabilizer code.
"""
## CONSTRUCTOR ##
def __init__(self, group_generators, logical_xs, logical_zs, label=None):
self.group_generators = pc.PauliList(*group_generators)
if (Unspecified in logical_xs) or (Unspecified in logical_zs):
raise ValueError("Logical operators must be specified.")
self.logical_xs = pc.PauliList(*logical_xs)
self.logical_zs = pc.PauliList(*logical_zs)
self.label = label
## PRETTY PRINTING ##
def __repr__(self):
return "<[[{n}, {k}, {d}]] StabilizerCode{label_or_space}at {id:0x}>".format(
n=self.nq, k=self.nq_logical,
d=self.distance if self.nq < 6 else "?",
id=id(self),
label_or_space=' "{}" '.format(self.label) if self.label is not None else ""
)
def __str__(self):
retstr = "S = <{group_generators}>\nXbars = {0.logical_xs}\nZbars = {0.logical_zs}".format(
self,
group_generators=", ".join(map(str, self.group_generators)))
return ((self.label + "\n") if self.label is not None else "") + retstr
## READ-ONLY PROPERTIES ##
@property
def nq(self):
"""
The number of physical qubits into which this code encodes.
"""
try:
return len(next(iter(
gen
for gen in self.group_generators + self.logical_xs + self.logical_zs
if gen is not Unspecified
)))
except StopIteration:
return 0
@property
def n_constraints(self):
"""
The number of stabilizer constraints on valid codewords.
"""
return len(self.group_generators)
@property
def nq_logical(self):
"""
The number of logical qubits admitted by this code.
"""
return self.nq - self.n_constraints
@property
def logical_ys(self):
"""Derives logical :math:`Y` operators, given logical :math:`X`
and :math:`Z` operators."""
return pc.PauliList((ex * zed).mul_phase(1) for (ex,zed) in zip(self.logical_xs,self.logical_zs))
@property
def logical_ops(self):
"""Returns a list of all logical operators for a code in the
form [Xs, Ys, Zs]."""
return self.logical_xs+self.logical_ys+self.logical_zs
@property
def distance(self):
r"""
The distance of this code, defined by :math:`\min\text{wt} \{
P | P \in \text{N}(S) \backslash S \}`, where :math:`S` is the stabilizer group
for this code.
Warning: this property is currently very slow to compute.
"""
return min(P.wt for P in self.normalizer_group(mod_s=True))
@property
def n_correctable(self):
r"""
The number of errors :math:`t` correctable by this code, defined by
:math:`\left\lfloor \frac{d - 1}{2} \right\rfloor`, where :math:`d` is
the distance of the code, given by the ``distance`` property.
"""
return math.floor((self.distance - 1) / 2)
## GROUP ENUMERATION METHODS ##
[docs] def stabilizer_group(self, coset_rep=None):
r"""
Iterator onto all elements of the stabilizer group :math:`S` describing
this code, or onto a coset :math:`PS` of the stabilizer group.
:param qecc.Pauli coset_rep: A Pauli operator :math:`P`, so that the
iterated coset is :math:`PS`. If not specified, defaults to the
identity.
:yields: All elements of the coset :math:`PS` of the stabilizer
group :math:`S`.
"""
return self.group_generators.generated_group(coset_rep=coset_rep)
[docs] def logical_pauli_group(self, incl_identity=True):
r"""
Iterator onto the group :math:`\text{N}(S) / S`, where :math:`S` is
the stabilizer group describing this code. Each member of the group
is specified by a coset representative drawn from the respective
elements of :math:`\text{N}(S) / S`. These representatives are
chosen to be the logical :math:`X` and :math:`Z` operators specified
as properties of this instance.
:param bool incl_identity: If ``False``, the identity coset :math:`S`
is excluded from this iterator.
:yields: A representative for each element of :math:`\text{N}(S) / S`.
"""
return p.from_generators(self.logical_xs + self.logical_zs, incl_identity=incl_identity)
[docs] def normalizer_group(self, mod_s=False):
r"""
Returns all elements of the normalizer of the stabilizer group. If
``mod_s`` is ``True``, returns the set :math:`N(S)\backslash S`.
"""
for Pbar in self.logical_pauli_group(incl_identity=not mod_s):
for normalizer_element in self.stabilizer_group(coset_rep=Pbar):
yield normalizer_element
## EN/DE/TRANSCODING METHODS ##
[docs] def encoding_cliffords(self):
r"""
Returns an iterator onto all Clifford operators that encode into this
stabilizer code, starting from an input register such that the state to
be encoded is a state of the first :math:`k` qubits, and such that the
rest of the qubits in the input register are initialized to
:math:`\left|0\right\rangle`.
:yields: instances ``C`` of :class:`qecc.Clifford` such that
``C(q.StabilizerCode.unencoded_state(k, n - k))`` equals this code.
"""
C = c.Clifford(
self.logical_xs + ([Unspecified] * self.n_constraints),
self.logical_zs + self.group_generators)
return C.constraint_completions()
[docs] def syndrome_to_recovery_operator(self,synd):
r"""
Returns a Pauli operator which corrects an error on the stabilizer code
``self``, given the syndrome ``synd``, a bitstring indicating which
generators the implied error commutes with and anti-commutes with.
:param synd: a string, list, tuple or other sequence type with entries
consisting only of 0 or 1. This parameter will be certified before
use.
"""
# If the syndrome is an integer, change it to a bitstring by
# using string formatting.
if isinstance(synd,int):
fmt = "{{0:0>{n}b}}".format(n=self.n_constraints)
synd = fmt.format(synd)
# Ensures synd is a list of integers by mapping int onto the list.
synd=list(map(int, synd))
# Check that the syndrome is all zeros and ones.
acceptable_syndrome = all([bit == 0 or bit == 1 for bit in synd])
if not acceptable_syndrome:
raise ValueError("Please input a syndrome which is an iterable onto 0 and 1.")
if len(synd) != self.nq - self.nq_logical:
raise ValueError("Syndrome must account for n-k bits of syndrome data.")
# We produce commutation and anti_commutation constraints from synd.
anti_coms = list(it.compress(self.group_generators,synd))
coms = list(it.compress(self.group_generators,[1-bit for bit in synd]))
for op_weight in range(self.nq+1):
#We loop over all possible weights. As soon as we find an operator
#that satisfies the commutation and anti-commutation constraints,
#we return it:
low_weight_ops=list(map(p.remove_phase,
cs.solve_commutation_constraints(coms,anti_coms,
search_in_set=p.paulis_by_weight(self.nq,
op_weight))))
if low_weight_ops:
break
return low_weight_ops[0]
[docs] def syndromes_and_recovery_operators(self):
r"""
Outputs an iterator onto tuples of syndromes and appropriate recovery
operators.
"""
for bitstring in it.product([0,1],repeat=self.nq-self.nq_logical):
yield (bitstring, self.syndrome_to_recovery_operator(bitstring))
[docs] def recovery_circuit_as_qcircuit(self, C=None, R=None):
"""
Returns the recovery operator (as specified by
:meth:`syndromes_and_recovery_operators`), expressed as a `Qcircuit`_
array.
:param float C: Width (in ems) of each column.
:param float R: Height (in ems) of each column.
.. _Qcircuit: http://www.cquic.org/Qcircuit/
"""
nq_data = self.nq
nq_anc = nq_data - self.nq_logical
nq = nq_data + nq_anc
# Put a blank line of array cells coming into the circuit.
trans_cells = [[""] * nq]
for bitstring, recovery_gate in self.syndromes_and_recovery_operators():
trans_cells.append(
# Data register
[r"\gate{{{}}} {}".format(P if P != "I" else r"\id", "\qwx" if idx != 0 else "") for idx, P in enumerate(recovery_gate.op)] +
# Ancilla register
[(r"\controlo" if bit == 0 else r"\control") + r" \cw \cwx" for bit in bitstring]
)
trans_cells.append([r"\qw"] * nq_data + [r"\cw"] * nq_anc)
# FIXME: consolidate this with Circuit.as_qcircuit().
return r"""
\Qcircuit {C} {R} {{
{0}
}}
""".format(
u.latex_array_contents(u.transpose(trans_cells)),
C="@C{}em".format(C) if C is not None else "",
R="@R{}em".format(R) if R is not None else ""
)
[docs] def star_decoder(self, for_enc=None, as_dict=False):
r"""
Returns a tuple of a decoding Clifford and a :class:`qecc.PauliList`
specifying the recovery operation to perform as a function of the result
of a :math:`Z^{\otimes{n - k}}` measurement on the ancilla register.
For syndromes corresponding to errors of weight greater than the distance,
the relevant element of the recovery list will be set to
:obj:`qecc.Unspecified`.
:param for_enc: If not ``None``, specifies to use a given Clifford
operator as the encoder, instead of the first element yielded by
:meth:`encoding_cliffords`.
:param bool as_dict: If ``True``, returns a dictionary from recovery
operators to syndromes that indicate that recovery.
"""
def error_to_pauli(error):
if error == p.I.as_clifford():
return "I"
if error == p.X.as_clifford():
return "X"
if error == p.Y.as_clifford():
return "Y"
if error == p.Z.as_clifford():
return "Z"
if for_enc is None:
encoder = next(self.encoding_cliffords())
else:
encoder = for_enc
decoder = encoder.inv()
errors = pc.PauliList(p.eye_p(self.nq)) + pc.PauliList(p.paulis_by_weight(self.nq, self.n_correctable))
syndrome_dict = defaultdict(lambda: Unspecified)
syndrome_meas = [p.elem_gen(self.nq, idx, 'Z') for idx in range(self.nq_logical, self.nq)]
for error in errors:
effective_gate = decoder * error.as_clifford() * encoder
# FIXME: the following line emulates measurement until we have a real
# measurement simulation method.
syndrome = tuple([effective_gate(meas).ph / 2 for meas in syndrome_meas])
recovery = "".join([
# FIXME: the following is a broken hack to get the phases on the logical qubit register.
error_to_pauli(c.Clifford([effective_gate.xout[idx][idx]], [effective_gate.zout[idx][idx]]))
for idx in range(self.nq_logical)
])
# For degenerate codes, the syndromes can collide, so long as we
# correct the same way for each.
if syndrome in syndrome_dict and syndrome_dict[syndrome] != recovery:
raise RuntimeError('Syndrome {} has collided.'.format(syndrome))
syndrome_dict[syndrome] = recovery
if as_dict:
outdict = dict()
keyfn = lambda syndrome_recovery: syndrome_recovery[1]
data = sorted(list(syndrome_dict.items()), key=keyfn)
for recovery, syndrome_group in it.groupby(data, keyfn):
outdict[recovery] = [syn[0] for syn in syndrome_group]
return decoder, outdict
else:
recovery_list = pc.PauliList(syndrome_dict[syndrome] for syndrome in it.product(list(range(2)), repeat=self.n_constraints))
return decoder, recovery_list
[docs] def minimize_distance_from(self, other, quiet=True):
"""
Reorders the stabilizer group generators of this code to minimize
the Hamming distance with the group generators of another code,
using a greedy heuristic algorithm.
"""
self_gens = self.group_generators
other_gens = other.group_generators
for idx_generator in range(len(self_gens)):
min_hdist = self.nq + 1 # Effectively infinite.
min_wt = self.nq + 1
best_gen = None
best_gen_decomp = ()
for stab_elems in p.powerset(self_gens[idx_generator:]):
if len(stab_elems) > 0:
stab_elem = reduce(op.mul, stab_elems)
hd = stab_elem.hamming_dist(other_gens[idx_generator])
if hd <= min_hdist and stab_elem.wt <= min_wt and (hd < min_hdist or stab_elem.wt < min_wt):
min_hdist = hd
min_wt = stab_elem.wt
best_gen = stab_elem
best_gen_decomp = stab_elems
assert best_gen is not None, "Powerset iteration failed."
if best_gen in self_gens:
# Swap so that it lies at the front.
idx = self_gens.index(best_gen)
if not quiet and idx != idx_generator:
print('Swap move: {} <-> {}'.format(idx_generator, idx))
self_gens[idx_generator], self_gens[idx] = self_gens[idx], self_gens[idx_generator]
else:
# Set the head element to best_gen, correcting the rest
# as needed.
if self_gens[idx_generator] in best_gen_decomp:
if not quiet:
print('Set move: {} = {}'.format(idx_generator, best_gen))
self_gens[idx_generator] = best_gen
else:
if not quiet:
print('Mul move: {} *= {}'.format(idx_generator, best_gen))
self_gens[idx_generator] *= best_gen
return self
[docs] def stabilizer_subspace(self):
r"""
Returns a :math:`2^{k} \times 2^{n}` array whose rows form a basis for
the codespace of this code. Please note that by necessity, this code
is exponentially slow as a function of the numbers of physical and
logical qubits.
"""
return self.group_generators.stabilizer_subspace()
## BLOCK CODE METHODS ##
[docs] def block_logical_pauli(self, P):
r"""
Given a Pauli operator :math:`P` acting on :math:`k`, finds a Pauli
operator :math:`\overline{P}` on :math:`n_k` qubits that corresponds
to the logical operator acting across :math:`k` blocks of this code.
Note that this method is only supported for single logical qubit codes.
"""
if self.nq_logical > 1:
raise NotImplementedError("Mapping of logical Pauli operators is currently only supported for single-qubit codes.")
# TODO: test that phases are handled correctly.
# FIXME: cache this dictionary.
replace_dict = {
'I': p.eye_p(self.nq),
'X': self.logical_xs[0],
'Y': (self.logical_xs[0] * self.logical_zs[0]).mul_phase(1),
'Z': self.logical_zs[0]
}
# FIXME: using eye_p(0) is a hack.
return reduce(op.and_,
(replace_dict[sq_op] for sq_op in P.op),
p.eye_p(0))
#TODO: Find a nice place to put this method.
[docs] def measure_gen_onto_ancilla(self, gen_idx):
"""
Produces a circuit that measures the stabilizer code generator
``self.group_generators[gen_idx]`` onto the qubit labelled by
``stab.nq`` (that is, the next qubit not in the physical register
used by the code).
:param int gen_idx: Index of a generator of the stabilizer group, as
specified by the ``group_generators`` property of this instance.
:returns qecc.Circuit: A circuit that maps a measurement of
``group_generators[gen_idx]`` onto a measurement of :math:`Z` on the
ancilla qubit alone.
"""
circ = circuit.Circuit()
for qubit_idx, operator in enumerate(self.group_generators[gen_idx].op):
# operator = (self.group_generators[gen_idx].op)[qubit_idx]
if operator == 'I':
pass
elif operator == 'Z':
circ += circuit.Circuit(('CNOT',qubit_idx,self.nq))
elif operator == 'Y':
circ += circuit.Circuit(circuit.Location('P',qubit_idx),circuit.Location('Z',qubit_idx),circuit.Location('H',qubit_idx),circuit.Location('CNOT',qubit_idx,self.nq),circuit.Location('H',qubit_idx),circuit.Location('P',qubit_idx))
elif operator == 'X':
circ += circuit.Circuit(circuit.Location('H',qubit_idx),circuit.Location('CNOT',qubit_idx,self.nq),circuit.Location('H',qubit_idx))
else:
raise ValueError("Pauli operator not I, X, Y, or Z")
return circ
[docs] def syndrome_meas_circuit(self):
"""Returns a circuit which measures all stabilizer generators
onto ancillae, using ``measure_gen_onto_ancilla``."""
return sum((
self.measure_gen_onto_ancilla(idx_gen).relabel_qubits({self.nq: self.nq + idx_gen})
for idx_gen in range(len(self.group_generators))
),
circuit.Circuit()
)
## OPERATORS ##
def __and__(self, other):
"""Returns the Kronecker product of two stabilizer codes,
given each of the constituent codes. """
if not isinstance(other, StabilizerCode):
return NotImplemented
return StabilizerCode(
(self.group_generators & p.eye_p(other.nq)) +
(p.eye_p(self.nq) & other.group_generators),
(self.logical_xs & p.eye_p(other.nq)) +
(p.eye_p(self.nq) & other.logical_xs),
(self.logical_zs & p.eye_p(other.nq)) +
(p.eye_p(self.nq) & other.logical_zs),
)
def __eq__(self, other):
if not isinstance(other, StabilizerCode):
return NotImplemented
# NOTE: We do not check the label as that is unimportant for
# equality of two codes.
return (
self.group_generators == other.group_generators and
self.logical_xs == other.logical_xs and
self.logical_zs == other.logical_zs
)
## PERMUTATION ##
[docs] def permute_gen_ops(self, perm):
r"""
Returns a stabilizer code with generators related to the
generators of `self`, with every instance of {X,Y,Z} replaced with
{perm[0],perm[1],perm[2]}.
:param list perm: A list containing 'X','Y', and 'Z' in any order,
indicating which permutation is to be applied.
>>> new_stab = StabilizerCode.bit_flip_code(1).permute_gen_ops('ZYX')
>>> assert new_stab.group_generators == StabilizerCode.phase_flip_code(1).group_generators
"""
new_group_generators=pc.PauliList()
for pauli in self.group_generators:
new_group_generators.append(pauli.permute_op(perm))
new_log_xs=pc.PauliList()
for pauli in self.logical_xs:
new_log_xs.append(pauli.permute_op(perm))
new_log_zs=pc.PauliList()
for pauli in self.logical_zs:
new_log_zs.append(pauli.permute_op(perm))
return StabilizerCode(new_group_generators,new_log_xs,new_log_zs)
## CONCATENATION ##
[docs] def concatenate(self,other):
r"""
Returns the stabilizer for a concatenated code, given the
stabilizers for two codes. At this point, it only works for two
:math:`k=1` codes.
"""
if self.nq_logical > 1 or other.nq_logical > 1:
raise NotImplementedError("Concatenation is currently only supported for single-qubit codes.")
nq_self = self.nq
nq_other = other.nq
nq_new = nq_self * nq_other
# To obtain the new generators, we must apply the stabilizer generators
# to each block of the inner code (self), as well as the stabilizer
# generators of the outer code (other), using the inner logical Paulis
# for the outer stabilizer generators.
# Making the stabilizer generators from the inner (L0) code is straight-
# forward: we repeat the code other.nq times, once on each block of the
# outer code. We use that PauliList supports tensor products.
new_generators = sum(
(
p.eye_p(nq_self * k) & self.group_generators & p.eye_p(nq_self * (nq_other - k - 1))
for k in range(nq_other)
),
pc.PauliList())
# Each of the stabilizer generators due to the outer (L1) code can be
# found by computing the block-logical operator across multiple L0
# blocks, as implemented by StabilizerCode.block_logical_pauli.
new_generators += list(map(self.block_logical_pauli, other.group_generators))
# In the same way, the logical operators are also found by mapping L1
# operators onto L0 qubits.
# This completes the definition of the concatenated code, and so we are
# done.
return StabilizerCode(new_generators,
logical_xs=list(map(self.block_logical_pauli, other.logical_xs)),
logical_zs=list(map(self.block_logical_pauli, other.logical_zs))
)
## TRANSCODING ##
[docs] def transcoding_cliffords(self,other):
r"""
Returns an iterator onto all :class:`qecc.Clifford` objects which
take states specified by ``self``, and
return states specified by ``other``.
:arg other: :class:`qecc.StabilizerCode`
"""
#Preliminaries:
stab_in = self.group_generators
stab_out = other.group_generators
xs_in = self.logical_xs
xs_out = other.logical_xs
zs_in = self.logical_zs
zs_out = other.logical_zs
nq_in=len(stab_in[0])
nq_out=len(stab_out[0])
nq_anc=abs(nq_in-nq_out)
#Decide left side:
if nq_in<nq_out:
stab_left=stab_out
xs_left=xs_out
zs_left=zs_out
stab_right=stab_in
xs_right=xs_in
zs_right=zs_in
else:
stab_right=stab_out
xs_right=xs_out
zs_right=zs_out
stab_left=stab_in
xs_left=xs_in
zs_left=zs_in
cliff_xouts_left=stab_left+xs_left
cliff_zouts_left=[Unspecified]*len(stab_left)+zs_left
cliff_left=next(c.Clifford(cliff_xouts_left,cliff_zouts_left).constraint_completions())
list_left=cliff_left.xout+cliff_left.zout
for mcset in p.mutually_commuting_sets(n_elems=len(stab_left)-len(stab_right),n_bits=nq_anc):
temp_xouts_right = p.pad(stab_right,lower_right=mcset) + [elem & p.eye_p(nq_anc) for elem in xs_right]
temp_zouts_right = [Unspecified]*len(stab_left) + [elem & p.eye_p(nq_anc) for elem in zs_right]
for completion in c.Clifford(temp_xouts_right,temp_zouts_right).constraint_completions():
if nq_in < nq_out:
yield c.gen_cliff(completion.xout+completion.zout,list_left)
else:
yield c.gen_cliff(list_left,completion.xout+completion.zout)
[docs] def min_len_transcoding_clifford(self,other):
"""
Searches the iterator provided by `transcoding_cliffords` for the shortest
circuit decomposition.
"""
circuit_iter=[p.as_bsm().circuit_decomposition() for p in self.transcoding_cliffords(other)]
return min(*circuit_iter)
## COMMON CODES ##
[docs] @staticmethod
def ancilla_register(nq=1):
r"""
Creates an instance of :class:`qecc.StabilizerCode` representing an
ancilla register of ``nq`` qubits, initialized in the state
:math:`\left|0\right\rangle^{\otimes \text{nq}}`.
:rtype: qecc.StabilizerCode
"""
return StabilizerCode(
p.elem_gens(nq)[1],
[], []
)
[docs] @staticmethod
def unencoded_state(nq_logical=1, nq_ancilla=0):
"""
Creates an instance of :class:`qecc.StabilizerCode` representing an
unencoded register of ``nq_logical`` qubits tensored with an ancilla
register of ``nq_ancilla`` qubits.
:param int nq_logical: Number of qubits to
:rtype: qecc.StabilizerCode
"""
return (
StabilizerCode([], *p.elem_gens(nq_logical)) &
StabilizerCode.ancilla_register(nq_ancilla)
)
[docs] @staticmethod
def flip_code(n_correctable, stab_kind='Z'):
"""
Creates an instance of :class:`qecc.StabilizerCode` representing a
code that protects against weight-``n_correctable`` flip errors of a
single kind.
This method generalizes the bit-flip and phase-flip codes, corresponding
to ``stab_kind=qecc.Z`` and ``stab_kind=qecc.X``, respectively.
:param int n_correctable: Maximum weight of the errors that can be
corrected by this code.
:param qecc.Pauli stab_kind: Single-qubit Pauli operator specifying
which kind of operators to use for the new stabilizer code.
:rtype: qecc.StabilizerCode
"""
nq = 2 * n_correctable + 1
stab_kind = p.ensure_pauli(stab_kind)
if len(stab_kind) != 1:
raise ValueError("stab_kind must be single-qubit.")
return StabilizerCode(
[p.eye_p(j) & stab_kind & stab_kind & p.eye_p(nq-j-2) for j in range(nq-1)],
['X'*nq], ['Z'*nq],
label='{}-flip code (t = {})'.format(stab_kind.op, n_correctable)
)
[docs] @staticmethod
def bit_flip_code(n_correctable):
"""
Creates an instance of :class:`qecc.StabilizerCode` representing a
code that protects against weight-``n_correctable`` bit-flip errors.
:param int n_correctable: Maximum weight of the bit-flip errors that can
be corrected by this code.
:rtype: qecc.StabilizerCode
"""
return StabilizerCode.flip_code(n_correctable, stab_kind=p.Z)
[docs] @staticmethod
def phase_flip_code(n_correctable):
"""
Creates an instance of :class:`qecc.StabilizerCode` representing a
code that protects against weight-``n_correctable`` phase-flip errors.
:param int n_correctable: Maximum weight of the phase-flip errors that
can be corrected by this code.
:rtype: qecc.StabilizerCode
"""
return StabilizerCode.flip_code(n_correctable, stab_kind=p.X)
[docs] @staticmethod
def perfect_5q_code():
"""
Creates an instance of :class:`qecc.StabilizerCode` representing the
5-qubit perfect code.
:rtype: qecc.StabilizerCode
"""
return StabilizerCode(
[
'XZZXI',
'IXZZX',
'XIXZZ',
'ZXIXZ'
],
['XXXXX'], ['ZZZZZ'],
label='5-qubit perfect code'
)
[docs] @staticmethod
def steane_code():
"""
Creates an instance of :class:`qecc.StabilizerCode` representing the
7-qubit Steane code.
:rtype: qecc.StabilizerCode
"""
return StabilizerCode(
[
'XXXXIII',
'XXIIXXI',
'XIXIXIX',
'ZZZZIII',
'ZZIIZZI',
'ZIZIZIZ'
],
['XXXXXXX'], ['ZZZZZZZ'],
label='7-qubit Steane code'
)
[docs] @staticmethod
def shor_code():
"""
Creates an instance of :class:`qecc.StabilizerCode` representing the
9-qubit Shor code.
:rtype: qecc.StabilizerCode
"""
stab = StabilizerCode.bit_flip_code(1).concatenate(StabilizerCode.phase_flip_code(1))
stab.label = '9-qubit Shor code'
return stab
[docs] @staticmethod
def css_code(C1, C2):
"""
Not yet implemented.
"""
raise NotImplementedError("Not yet implemented.")
[docs] @staticmethod
def reed_muller_code(r,t):
"""
Not yet implemented.
"""
raise NotImplementedError("Coming Soon: Reed-Muller Codes")
[docs] @staticmethod
def reed_solomon_code(r,t):
"""
Not yet implemented.
"""
raise NotImplementedError("Coming Soon: Reed-Solomon Codes")