Example #1
0
    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)
Example #2
0
    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=c.Clifford(cliff_xouts_left,cliff_zouts_left).constraint_completions().next()
        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) + map(lambda elem: elem & p.eye_p(nq_anc), xs_right)
            temp_zouts_right = [Unspecified]*len(stab_left) + map(lambda elem: elem & p.eye_p(nq_anc), 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)
Example #3
0
    def from_clifford(cliff_in):
        """
        Tests an input Clifford ``cliff_in`` to determine if it is, in
        fact, a Pauli. If so, it outputs the Pauli. If not, it
        raises an error. 
        
        :arg cliff_in: Representation of Clifford operator to be converted, 
            if possible.
        :rtype: :class:`qecc.Pauli`
        
        Example:
        
        >>> import qecc as q
        >>> cliff = q.Clifford([q.Pauli('XI',2),q.Pauli('IX')], map(q.Pauli,['ZI','IZ']))
        >>> q.Pauli.from_clifford(cliff)
        i^0 ZI
        
        Converting a Pauli into a Clifford and back again will erase
        the phase:
        
        >>> import qecc as q
        >>> paul = q.Pauli('YZ',3)
        >>> cliff = paul.as_clifford()
        >>> q.Pauli.from_clifford(cliff)
        i^0 YZ
        
        """

        nq = len(cliff_in.xout)  #Determine number of qubits.
        test_ex, test_zed = elem_gens(nq)  #Get paulis to compare.
        """If the Paulis input to the Clifford are only altered in phase, then the Clifford is also a Pauli."""
        for ex_clif, zed_clif, ex_test, zed_test in zip(
                cliff_in.xout, cliff_in.zout, test_ex, test_zed):
            if ex_clif.op != ex_test.op or zed_clif.op != zed_test.op:
                raise ValueError("Clifford is not Pauli.")
            #If the Clifford is Pauli, determine which by examining operators with altered phases.
            exact = eye_p(nq)
            zedact = eye_p(nq)  #Initialize accumulators
            """If a negative sign appears on a given generator, assign a Pauli to that qubit that conjugates the generator to a minus sign, e.g. ZXZ = -X """
            for idx_x in range(nq):
                if cliff_in.xout[idx_x].ph == 2:
                    exact.op = cc.replace_one_character(exact.op, idx_x, 'Z')
            for idx_z in range(nq):
                if cliff_in.zout[idx_z].ph == 2:
                    zedact.op = cc.replace_one_character(zedact.op, idx_z, 'X')
            return Pauli((exact * zedact).op)
Example #4
0
 def from_clifford(cliff_in):
     """
     Tests an input Clifford ``cliff_in`` to determine if it is, in
     fact, a Pauli. If so, it outputs the Pauli. If not, it
     raises an error. 
     
     :arg cliff_in: Representation of Clifford operator to be converted, 
         if possible.
     :rtype: :class:`qecc.Pauli`
     
     Example:
     
     >>> import qecc as q
     >>> cliff = q.Clifford([q.Pauli('XI',2),q.Pauli('IX')], map(q.Pauli,['ZI','IZ']))
     >>> q.Pauli.from_clifford(cliff)
     i^0 ZI
     
     Converting a Pauli into a Clifford and back again will erase
     the phase:
     
     >>> import qecc as q
     >>> paul = q.Pauli('YZ',3)
     >>> cliff = paul.as_clifford()
     >>> q.Pauli.from_clifford(cliff)
     i^0 YZ
     
     """
     
     nq=len(cliff_in.xout) #Determine number of qubits.
     test_ex,test_zed=elem_gens(nq) #Get paulis to compare.
     """If the Paulis input to the Clifford are only altered in phase, then the Clifford is also a Pauli."""
     for ex_clif,zed_clif,ex_test,zed_test in zip(cliff_in.xout, cliff_in.zout,test_ex,test_zed):
         if ex_clif.op != ex_test.op or zed_clif.op != zed_test.op:
             raise ValueError("Clifford is not Pauli.")
         #If the Clifford is Pauli, determine which by examining operators with altered phases.
         exact=eye_p(nq)
         zedact=eye_p(nq) #Initialize accumulators
         """If a negative sign appears on a given generator, assign a Pauli to that qubit that conjugates the generator to a minus sign, e.g. ZXZ = -X """
         for idx_x in range(nq):
             if cliff_in.xout[idx_x].ph==2:
                 exact.op = cc.replace_one_character(exact.op, idx_x, 'Z')
         for idx_z in range(nq):
             if cliff_in.zout[idx_z].ph==2:
                 zedact.op = cc.replace_one_character(zedact.op, idx_z, 'X')
         return Pauli((exact*zedact).op)
Example #5
0
def embed(pauli,qubits_tpl,nq):
    """
    Takes a Pauli, defined on as many qubits as are in qubits_tpl, and acts it
    on the register specified by qubits_tpl, within a register nq long.  
    """
    new_pauli_op='I'*nq
    new_pauli_ph=pauli.ph
    for idx in range(len(qubits_tpl)):
        new_pauli_op = cc.replace_one_character(new_pauli_op, qubits_tpl[idx], pauli.op[idx])
    return Pauli(new_pauli_op,new_pauli_ph)
Example #6
0
def embed(pauli,qubits_tpl,nq):
    """
    Takes a Pauli, defined on as many qubits as are in qubits_tpl, and acts it
    on the register specified by qubits_tpl, within a register nq long.  
    """
    new_pauli_op='I'*nq
    new_pauli_ph=pauli.ph
    for idx in range(len(qubits_tpl)):
        new_pauli_op = cc.replace_one_character(new_pauli_op, qubits_tpl[idx], pauli.op[idx])
    return Pauli(new_pauli_op,new_pauli_ph)
Example #7
0
 def as_clifford(self):
     """
     If this circuit is composed entirely of Clifford operators, converts it
     to a :class:`qecc.Clifford` instance representing the action of the
     entire circuit. If the circuit is not entirely Clifford gates, this method
     raises a :obj:`RuntimeError`.
     """
     if not all(loc.is_clifford for loc in self):
         raise RuntimeError('All locations must be Clifford gates in order to represent a circuit as a Clifford operator.')
         
     nq = self.nq
     return reduce(mul, (loc.as_clifford(nq) for loc in reversed(self)), cc.eye_c(nq))
Example #8
0
 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()
Example #9
0
    def as_clifford(self):
        """
        Converts a Pauli into a Clifford which changes the signs of input
        Paulis. 
        :returns: A Clifford representing conjugation by this Pauli
        operator.
        :rtype: :class:`qecc.Clifford`
        """
        Xs, Zs = elem_gens(len(self))
        for P in chain(Xs, Zs):
            if com(self, P) == 1:
                P.mul_phase(2)

        return cc.Clifford(Xs, Zs)
Example #10
0
    def as_clifford(self):
        """
        If this circuit is composed entirely of Clifford operators, converts it
        to a :class:`qecc.Clifford` instance representing the action of the
        entire circuit. If the circuit is not entirely Clifford gates, this method
        raises a :obj:`RuntimeError`.
        """
        if not all(loc.is_clifford for loc in self):
            raise RuntimeError(
                'All locations must be Clifford gates in order to represent a circuit as a Clifford operator.'
            )

        nq = self.nq
        return reduce(mul, (loc.as_clifford(nq) for loc in reversed(self)),
                      cc.eye_c(nq))
Example #11
0
 def as_clifford(self, nq):
     return cc.phase(nq, *self.qubits)
Example #12
0
    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
Example #13
0
class Location(object):
    """
    Represents a gate, wait, measurement or preparation location in a
    circuit.
    
    Note that currently, only gate locations are implemented.
    
    :param kind: The kind of location to be created. Each kind is an
        abbreviation drawn from ``Location.KIND_NAMES``, or is the index in
        ``Location.KIND_NAMES`` corresponding to the desired location kind.
    :type kind: int or str
    :param qubits: Indicies of the qubits on which this location acts.
    :type qubits: tuple of ints.
    """

    ## PRIVATE CLASS CONSTANTS ##
    _CLIFFORD_GATE_KINDS = [
        'I', 'X', 'Y', 'Z', 'H', 'R_pi4', 'CNOT', 'CZ', 'SWAP'
    ]

    _CLIFFORD_GATE_FUNCS = {
        'I': lambda nq, idx: cc.eye_c(nq),
        'X': lambda nq, idx: pc.elem_gen(nq, idx, 'X').as_clifford(),
        'Y': lambda nq, idx: pc.elem_gen(nq, idx, 'Y').as_clifford(),
        'Z': lambda nq, idx: pc.elem_gen(nq, idx, 'Z').as_clifford(),
        'H': cc.hadamard,
        'R_pi4': cc.phase,
        'CNOT': cc.cnot,
        'CZ': cc.cz,
        'SWAP': cc.swap
    }

    _QCVIEWER_NAMES = {
        'I': 'I',  # This one is implemented by a gate definition
        # included by Circuit.as_qcviewer().
        'X': 'X',
        'Y': 'Y',
        'Z': 'Z',
        'H': 'H',
        'R_pi4': 'P',
        'CNOT': 'tof',
        'CZ': 'Z',
        'SWAP': 'swap'
    }

    ## PUBLIC CLASS CONSTANTS ##

    #: Names of the kinds of locations used by QuaEC.
    KIND_NAMES = sum([_CLIFFORD_GATE_KINDS], [])

    ## INITIALIZER ##

    def __init__(self, kind, *qubits):
        if isinstance(kind, int):
            self._kind = kind
        elif isinstance(kind, str):
            self._kind = self.KIND_NAMES.index(kind)
        else:
            raise TypeError("Location kind must be an int or str.")

        #if not all(isinstance(q, int) for q in qubits):
        #    raise TypeError('Qubit indices must be integers. Got {} instead, which is of type {}.'.format(
        #        *(iter((q, type(q)) for q in qubits if not isinstance(q, int)).next())
        #    ))

        try:
            self._qubits = tuple(map(int, qubits))
        except TypeError as e:
            raise TypeError('Qubit integers must be int-like.')
        self._is_clifford = bool(self.kind in self._CLIFFORD_GATE_KINDS)

    ## REPRESENTATION METHODS ##

    def __str__(self):
        return "    {:<4}    {}".format(self.kind,
                                        ' '.join(map(str, self.qubits)))

    def __repr__(self):
        return "<{} Location on qubits {}>".format(self.kind, self.qubits)

    def __hash__(self):
        return hash((self._kind, ) + self.qubits)

    ## IMPORT METHODS ##

    @staticmethod
    def from_quasm(source):
        """
        Returns a :class:`qecc.Location` initialized from a QuASM-formatted line.
        
        :type str source: A line of QuASM code specifying a location.
        :rtype: :class:`qecc.Location`
        :returns: The location represented by the given QuASM source.
        """
        parts = source.split()
        return Location(parts[0], *map(int, parts[1:]))

    ## PROPERTIES ##

    @property
    def kind(self):
        """
        Returns a string defining which kind of location this instance
        represents. Guaranteed to be a string that is an element of
        ``Location.KIND_NAMES``.
        """
        return self.KIND_NAMES[self._kind]

    @property
    def qubits(self):
        """
        Returns a tuple of ints describing which qubits this location acts upon.
        """
        return self._qubits

    @property
    def nq(self):
        """
        Returns the number of qubits in the smallest circuit that can contain
        this location without relabeling qubits. For a :class:`qecc.Location`
        ``loc``, this property is defined as ``1 + max(loc.nq)``.
        """
        return 1 + max(self.qubits)

    @property
    def is_clifford(self):
        """
        Returns ``True`` if and only if this location represents a gate drawn
        from the Clifford group.
        """
        return self._is_clifford

    @property
    def wt(self):
        """
        Returns the number of qubits on which this location acts.
        """
        return len(self.qubits)

    ## SIMULATION METHODS ##

    def as_clifford(self, nq=None):
        """
        If this location represents a Clifford gate, returns the action of that
        gate. Otherwise, a :obj:`RuntimeError` is raised.
        
        :param int nq: Specifies how many qubits to represent this location as
            acting upon. If not specified, defaults to the value of the ``nq``
            property.
        :rtype: :class:`qecc.Clifford`
        """
        if not self.is_clifford:
            raise RuntimeError("Location must be a Clifford gate.")
        else:
            if nq is None:
                nq = self.nq
            elif nq < self.nq:
                raise ValueError(
                    'nq must be greater than or equal to the nq property.')

            return self._CLIFFORD_GATE_FUNCS[self.kind](nq, *self.qubits)

    ## EXPORT METHODS ##

    def as_qcviewer(self, qubit_names=None):
        """
        Returns a representation of this location in a format suitable for
        inclusion in a QCViewer file. 
            
        :param qubit_names: If specified, the given aliases will be used for the
            qubits involved in this location when exporting to QCViewer.
            Defaults to "q1", "q2", etc.
        :rtype: str
        
        Note that the identity (or "wait") location requires the following to be
        added to QCViewer's ``gateLib``::
        
            NAME wait
            DRAWNAME "1"
            SYMBOL I
            1 , 0
            0 , 1
        """
        # FIXME: link to QCViewer in the docstring here.
        return '    {gatename}    {gatespec}\n'.format(
            gatename=self._QCVIEWER_NAMES[self.kind],
            gatespec=qubits_str(self.qubits, qubit_names),
        )

    ## OTHER METHODS ##

    def relabel_qubits(self, relabel_dict):
        """
        Returns a new location related to this one by a relabeling of the
        qubits. The relabelings are to be indicated by a dictionary that
        specifies what each qubit index is to be mapped to.
        
        >>> import qecc as q
        >>> loc = q.Location('CNOT', 0, 1)
        >>> print loc
            CNOT    0 1
        >>> print loc.relabel_qubits({1: 2})
            CNOT    0 2
            
        :param dict relabel_dict: If `i` is a key of `relabel_dict`, then qubit
            `i` will be replaced by `relabel_dict[i]` in the returned location.
        :rtype: :class:`qecc.Location`
        :returns: A new location with the qubits relabeled as 
            specified by `relabel_dict`.
        """
        return Location(
            self.kind,
            *tuple(relabel_dict[i] if i in relabel_dict else i
                   for i in self.qubits))
Example #14
0
 def as_clifford(self, nq):
     return cc.cnot(nq, *self.qubits)
Example #15
0
 def as_clifford(self, nq):
     return cc.eye_c(nq)
Example #16
0
 def as_clifford(self, nq):
     return cc.hadamard(nq, *self.qubits)
Example #17
0
 def as_clifford(self, nq):
     return cc.hadamard(nq, *self.qubits)
Example #18
0
 def as_clifford(self, nq):
     return cc.cnot(nq, *self.qubits)
Example #19
0
 def as_clifford(self, nq):
     return cc.eye_c(nq)
Example #20
0
 def as_clifford(self, nq):
     return cc.phase(nq, *self.qubits)