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]
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]
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
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