Ejemplo n.º 1
0
 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)
     )
Ejemplo n.º 2
0
    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))
Ejemplo n.º 3
0
 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))
Ejemplo n.º 4
0
    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))
Ejemplo n.º 5
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)
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
 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 += 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=map(self.block_logical_pauli, other.logical_xs),
         logical_zs=map(self.block_logical_pauli, other.logical_zs)
     )
Ejemplo n.º 8
0
    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)))
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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    
Ejemplo n.º 11
0
def clifford_as_unitary(C):
    nq = len(C)
    dim = 2**nq
    U = np.zeros((dim, dim), dtype='complex')
    psi_0 = mutual_eigenspace(map(pauli_as_unitary, C.zout)).T
    for b in xrange(dim):
        bits = '{{0:0>{nq}b}}'.format(nq=nq).format(b)
        Xb = reduce(op.mul,
                    (C.xout[idx] for idx in xrange(nq) if bits[idx] == '1'),
                    pc.eye_p(nq)).as_unitary()
        for a in xrange(dim):
            bra_a = np.zeros((1, dim))
            bra_a[0, a] = 1
            U[a, b] = reduce(np.dot, [bra_a, Xb, psi_0])[0, 0]

    return U
Ejemplo n.º 12
0
    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),
        )
Ejemplo n.º 13
0
 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),
     )
Ejemplo n.º 14
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 = self.encoding_cliffords().next()
     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): recovery
         data = sorted(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(range(2), repeat=self.n_constraints))
         
         return decoder, recovery_list
Ejemplo n.º 15
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
Ejemplo n.º 16
0
def clifford_as_unitary(C):
    nq = len(C)
    dim = 2**nq
    U = np.zeros((dim,dim), dtype='complex')
    psi_0 = mutual_eigenspace(map(pauli_as_unitary, C.zout)).T
    for b in xrange(dim):
        bits = '{{0:0>{nq}b}}'.format(nq=nq).format(b)
        Xb   = reduce(op.mul, (C.xout[idx] for idx in xrange(nq) if bits[idx] == '1'), pc.eye_p(nq)).as_unitary()
        for a in xrange(dim):
            bra_a = np.zeros((1, dim))
            bra_a[0, a] = 1
            U[a, b] = reduce(np.dot, [bra_a, Xb, psi_0])[0,0]
            
    return U