예제 #1
0
    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]
예제 #2
0
    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=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=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]
예제 #3
0
 def constraint_completions(self):
     """
     Yields an iterator onto possible Clifford operators whose outputs agree
     with this operator for all outputs that are specified. Note that all
     yielded operators assign the phase 0 to all outputs, by convention.
     
     If this operator is fully specified, the iterator will yield exactly
     one element, which will be equal to this operator.
     
     For example:
     
     >>> import qecc as q
     >>> C = q.Clifford([q.Pauli('XI'), q.Pauli('IX')], [q.Unspecified, q.Unspecified])
     >>> it = C.constraint_completions()
     >>> print it.next()
     XI |->  +XI
     IX |->  +IX
     ZI |->  +ZI
     IZ |->  +IZ
     >>> print it.next()
     XI |->  +XI
     IX |->  +IX
     ZI |->  +ZI
     IZ |->  +IY
     >>> print len(list(C.constraint_completions()))
     8
     
     If this operator is not a valid Clifford operator, then this method will
     raise an :class:`qecc.InvalidCliffordError` upon iteraton.
     """
     # Check for validity first.
     if not self.is_valid():
         raise InvalidCliffordError("The specified constraints are invalid or are contradictory.")
     
     # Useful constants.
     XKIND, ZKIND = range(2)
     
     # Start by finding the first unspecified output.
     nq = len(self)
     X_bars, Z_bars = self.xout, self.zout
     P_bars = map(copy, [X_bars, Z_bars]) # <- Useful for indexing by kinds.
     XZ_pairs = zip(X_bars, Z_bars)
     try:
         unspecified_idx, unspecified_kind = iter(
             (idx, kind)
             for idx, kind
             in product(xrange(nq), range(2))
             if XZ_pairs[idx][kind] is Unspecified
         ).next()
     except StopIteration:
         # If there are no unspecified constraints, then self is the only
         # satisfying completion.
         yield self
         return
     
     # We must always commute with disjoint qubits.
     commutation_constraints = reduce(op.add,
         (XZ_pairs[idx] for idx in xrange(nq) if idx != unspecified_idx),
         tuple()
         )
         
     # On the same qubit, we must anticommute with the opposite operator.
     anticommutation_constraints = [XZ_pairs[unspecified_idx][XKIND if unspecified_kind == ZKIND else ZKIND]]
     
     # Filter out Unspecified constraints.
     specified_pred = lambda P: P is not Unspecified
     commutation_constraints = filter(specified_pred, commutation_constraints)
     anticommutation_constraints = filter(specified_pred, anticommutation_constraints)
     
     # Now we iterate over satisfactions of the constraints, yielding
     # all satisfactions of the remaining constraints recursively.
     Xgs, Zgs = elem_gens(nq)
     for P in solve_commutation_constraints(commutation_constraints, anticommutation_constraints, search_in_gens=Xgs+Zgs):
         P_bars[unspecified_kind][unspecified_idx] = Pauli(P.op, phase=0)
         # I wish I had "yield from" here. Ah, well. We have to recurse
         # manually instead.
         C = Clifford(*P_bars)
         for completion in C.constraint_completions():
             yield completion
             
     return
예제 #4
0
    def constraint_completions(self):
        """
        Yields an iterator onto possible Clifford operators whose outputs agree
        with this operator for all outputs that are specified. Note that all
        yielded operators assign the phase 0 to all outputs, by convention.
        
        If this operator is fully specified, the iterator will yield exactly
        one element, which will be equal to this operator.
        
        For example:
        
        >>> import qecc as q
        >>> C = q.Clifford([q.Pauli('XI'), q.Pauli('IX')], [q.Unspecified, q.Unspecified])
        >>> it = C.constraint_completions()
        >>> print it.next()
        XI |->  +XI
        IX |->  +IX
        ZI |->  +ZI
        IZ |->  +IZ
        >>> print it.next()
        XI |->  +XI
        IX |->  +IX
        ZI |->  +ZI
        IZ |->  +IY
        >>> print len(list(C.constraint_completions()))
        8
        
        If this operator is not a valid Clifford operator, then this method will
        raise an :class:`qecc.InvalidCliffordError` upon iteraton.
        """
        # Check for validity first.
        if not self.is_valid():
            raise InvalidCliffordError(
                "The specified constraints are invalid or are contradictory.")

        # Useful constants.
        XKIND, ZKIND = range(2)

        # Start by finding the first unspecified output.
        nq = len(self)
        X_bars, Z_bars = self.xout, self.zout
        P_bars = map(copy,
                     [X_bars, Z_bars])  # <- Useful for indexing by kinds.
        XZ_pairs = zip(X_bars, Z_bars)
        try:
            unspecified_idx, unspecified_kind = iter(
                (idx, kind) for idx, kind in product(xrange(nq), range(2))
                if XZ_pairs[idx][kind] is Unspecified).next()
        except StopIteration:
            # If there are no unspecified constraints, then self is the only
            # satisfying completion.
            yield self
            return

        # We must always commute with disjoint qubits.
        commutation_constraints = reduce(
            op.add,
            (XZ_pairs[idx] for idx in xrange(nq) if idx != unspecified_idx),
            tuple())

        # On the same qubit, we must anticommute with the opposite operator.
        anticommutation_constraints = [
            XZ_pairs[unspecified_idx][XKIND if unspecified_kind ==
                                      ZKIND else ZKIND]
        ]

        # Filter out Unspecified constraints.
        specified_pred = lambda P: P is not Unspecified
        commutation_constraints = filter(specified_pred,
                                         commutation_constraints)
        anticommutation_constraints = filter(specified_pred,
                                             anticommutation_constraints)

        # Now we iterate over satisfactions of the constraints, yielding
        # all satisfactions of the remaining constraints recursively.
        Xgs, Zgs = elem_gens(nq)
        for P in solve_commutation_constraints(commutation_constraints,
                                               anticommutation_constraints,
                                               search_in_gens=Xgs + Zgs):
            P_bars[unspecified_kind][unspecified_idx] = Pauli(P.op, phase=0)
            # I wish I had "yield from" here. Ah, well. We have to recurse
            # manually instead.
            C = Clifford(*P_bars)
            for completion in C.constraint_completions():
                yield completion

        return