Source code for qecc.paulicollections

#!/usr/bin/python
# -*- coding: utf-8 -*-
##
# paulicollections.py: 
##
# © 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))
if PY3:
    from . import PauliClass as pc
    from .singletons import Unspecified
    from . import unitary_reps
else:
    import PauliClass as pc
    from singletons import Unspecified
    import unitary_reps

from collections import Sequence

## ALL ##

__all__ = [
    'PauliList'
    ]
        
## CLASSES ##

[docs]class PauliList(list): r""" Subclass of :obj:`list` offering useful methods for lists of :class:`qecc.Pauli` instances. :param paulis: Instances either of :obj:`str` or :class:`qecc.Pauli`, or the special object :obj:`qecc.Unspecified`. Strings are passed to the constructor of :class:`qecc.Pauli` for convinenence. """ def __init__(self, *paulis): if len(paulis) == 1: single_arg = paulis[0] if isinstance(single_arg, str): paulis = [pc.Pauli(single_arg)] elif isinstance(single_arg, Sequence) or hasattr(single_arg, '__iter__'): paulis = list(map(pc.ensure_pauli, single_arg)) else: paulis = list(map(pc.ensure_pauli, paulis)) else: paulis = list(map(pc.ensure_pauli, paulis)) # FIXME: figure out why super(list, self).__init__ doesn't work. list.__init__(self, paulis) def __getitem__(self, *args): item = super(PauliList, self).__getitem__(*args) if not isinstance(item, list): return item else: return PauliList(*item) def __getslice__(self, *args): # Note that this must be overrided due to an implementation detail of # CPython. See the note at # http://docs.python.org/reference/datamodel.html#additional-methods-for-emulation-of-sequence-types return PauliList(*super(PauliList, self).__getslice__(*args)) def __add__(self, other): return PauliList(*(super(PauliList, self).__add__(other))) ## REPRESENTATION AND STRING COVNERSIONS ## def __repr__(self): # For now, he repr and str for a list can be the same. We can improve # this in the future. return str(self) def __str__(self): return "PauliList({})".format(", ".join(map(repr, self))) ## OPERATORS ACTING ON PAULI LISTS ## def __and__(self, other): if not isinstance(other, pc.Pauli): return NotImplemented else: return PauliList(*[P & other for P in self]) def __rand__(self, other): if not isinstance(other, pc.Pauli): return NotImplemented else: return PauliList(*[other & P for P in self]) ## OTHER METHODS ##
[docs] def pad(self, extra_bits=0, lower_right=None): r""" Takes a PauliList, and returns a new PauliList, appending ``extra_bits`` qubits, with stabilizer operators specified by ``lower_right``. :arg pauli_list_in: list of Pauli operators to be padded. :param int extra_bits: Number of extra bits to be appended to the system. :param lower_right: list of `qecc.Pauli` operators, acting on `extra_bits` qubits. :rtype: list of :class:`qecc.Pauli` objects. Example: >>> import qecc as q >>> pauli_list = q.PauliList('XXX', 'YIY', 'ZZI') >>> pauli_list.pad(extra_bits=2, lower_right=q.PauliList('IX','ZI')) PauliList(i^0 XXXII, i^0 YIYII, i^0 ZZIII, i^0 IIIIX, i^0 IIIZI) """ len_P = len(self) nq_P = len(self[0]) if len_P > 0 else 0 if extra_bits == 0 and lower_right is None or len(lower_right) == 0: return PauliList(self) elif len(lower_right) != 0: extra_bits=len(lower_right[0]) setout = PauliList([pc.Pauli(pauli.op + 'I'*extra_bits) for pauli in self]) if lower_right is None: setout += [pc.eye_p(nq_P + extra_bits)] * extra_bits else: setout += [pc.eye_p(nq_P) & P for P in lower_right] return setout
[docs] def generated_group(self, coset_rep=None): """ Yields an iterator onto the group generated by this list of Pauli operators. See also :obj:`qecc.from_generators`. """ return pc.from_generators(self, coset_rep)
[docs] def stabilizer_subspace(self): r""" Returns a :class:`numpy.ndarray` of shape ``(n - k, 2 ** n)`` containing an orthonormal basis for the mutual +1 eigenspace of each fully specified Pauli in this list. Here, ``n`` is taken to be the number of qubits and ``k`` is taken to be the number of independent Pauli operators in this list. Raises a :obj:`RuntimeError` if NumPy cannot be imported. For example, to find the Bell basis vector :math:`\left|\beta_{00}\right\rangle` using the stabilizer formalism: >>> import qecc as q >>> q.PauliList('XX', q.Unspecified, q.Unspecified, 'ZZ').stabilizer_subspace() array([[ 0.70710678+0.j, 0.00000000+0.j, 0.00000000+0.j, 0.70710678+0.j]]) Similarly, one can find the codewords of the phase-flip code :math:`S = \langle XXI, IXX \rangle`: >>> q.PauliList('XXI', 'IXX').stabilizer_subspace() array([[ 0.50000000+0.j, 0.00000000-0.j, 0.00000000-0.j, 0.50000000+0.j, 0.00000000-0.j, 0.50000000+0.j, 0.50000000+0.j, 0.00000000-0.j], [ 0.02229922+0.j, 0.49950250+0.j, 0.49950250+0.j, 0.02229922+0.j, 0.49950250+0.j, 0.02229922+0.j, 0.02229922+0.j, 0.49950250+0.j]]) Note that in this second case, some numerical errors have occured; this method does not guarantee that the returned basis vectors are exact. """ return unitary_reps.mutual_eigenspace([P.as_unitary() for P in self if P is not Unspecified])
[docs] def centralizer_gens(self, group_gens=None): r""" Returns the generators of the centralizer group :math:`\mathrm{C}(P_1, \dots, P_k)`, where :math:`P_i` is the :math:`i^{\text{th}}` element of this list. See :meth:`qecc.Pauli.centralizer_gens` for more information. """ if group_gens is None: # NOTE: Assumes all Paulis contained by self have the same nq. Xs, Zs = pc.elem_gens(len(self[0])) group_gens = Xs + Zs if len(self) == 0: # C({}) = G return PauliList(group_gens) centralizer_0 = self[0].centralizer_gens(group_gens=group_gens) if len(self) == 1: return centralizer_0 else: return self[1:].centralizer_gens(group_gens=centralizer_0)