def centralizer_gens(self, group_gens=None): r""" Returns the generators of the centralizer group :math:`\mathrm{C}(P)`, where :math:`P` is the Pauli operator represented by this instance. If ``group_gens`` is specified, :math:`\mathrm{C}(P)` is taken to be a subgroup of the group :math:`G = \langle G_1, \dots, G_k\rangle`, where :math:`G_i` is the :math:`i^{\text{th}}` element of ``group_gens``. :param group_gens: Either ``None`` or a list of generators :math:`G_i`. If not ``None``, the returned centralizer :math:`\mathrm{C}(P)` is a subgroup of the group :math:`\langle G_i \rangle_{i=1}^k`. :type group_gens: list of :class:`qecc.Pauli` instances :returns: A list of elements :math:`P_i` of the Pauli group such that :math:`\mathrm{C}(P) = \langle P_i \rangle_{i=1}^{n}`, where :math:`n` is the number of unique generators of the centralizer. """ if group_gens is None: Xs, Zs = elem_gens(len(self)) group_gens = Xs + Zs if not group_gens: # If group_gens is empty, it's false-y. return PauliList() if com(self, group_gens[0]) == 0: # That generator commutes, and so we pass it along # unmodified. return PauliList(group_gens[0], *self.centralizer_gens(group_gens[1:])) else: # That generator anticommutes, and so we must modify it by # multiplication with another anticommuting generator, if one # exists. found = False for idx in range(1, len(group_gens)): if com(self, group_gens[idx]) == 1: found = True g_prime = group_gens[idx] * group_gens[0] assert com(self, g_prime) == 0 return PauliList(g_prime, *self.centralizer_gens(group_gens[1:])) if not found: # Generator 0 anticommuted, and so we know two things # from our search: # - All other generators commute. # - The anticommuting generator (0) has no match, and must # be excluded. return PauliList(*group_gens[1:])
def clifford_bottoms(c_top): r""" This function yields the next set of nq paulis that mutually commute, and anti-commute with selected elements from c_top. The intent behind this function is to begin with a list of mutually commuting Paulis, and ifnd an associated set that share the same commutation relations as :math:`\left[ X_1,\ldots,X_n \range]` and :math:`\left[ Z_1,\ldots,Z_n \range]`. This allows the input and output of this function can be thought of as the :math:`X` and :math:`Z` outputs of a Clifford operator. :param c_top: list of :class:`qecc.Pauli` objects, which mutually commute. """ nq = len(c_top) possible_zs = [] for jj in range(nq): applicable_centralizer_gens = PauliList( *(c_top[:jj] + c_top[jj + 1:])).centralizer_gens() possible_zs.append( filter(lambda a: com(a, c_top[jj]) == 1, from_generators(applicable_centralizer_gens))) for possible_set in product(*possible_zs): if all( imap( lambda twolist: pred.commutes_with(twolist[0])(twolist[1]), combinations(possible_set, 2))): yield possible_set
def __init__(self, xbars, zbars): # Require that all specified operators be Paulis. # Moreover, we should warn the caller if the output phase is not either # 0 or 2, since such operators are not automorphisms of the Pauli group. self.xout = PauliList(*xbars) self.zout = PauliList(*zbars) for output_xz in chain(self.xout, self.zout): if output_xz is not Unspecified and output_xz.ph not in [0, 2]: warnings.warn( 'The output phase of a Clifford operator has been specified as {}, such that the operator is not a valid automorphism.\n' .format(output_xz.ph) + 'To avoid this warning, please choose all output phases to be from the set {0, 2}.' ) # Prevent fully unspecified operators. if all([P is Unspecified for P in chain(xbars, zbars)]): raise ValueError("At least one output must be specified.")
def elem_gens(nq): """ Produces all weight-one :math:`X` and :math:`Z` operators on `nq` qubits. For example, >>> import qecc as q >>> Xgens, Zgens = q.elem_gens(2) >>> print Xgens[1] i^0 IX :param int nq: Number of qubits for each returned operator. :returns: a tuple of two lists, containing :math:`X` and :math:`Z` generators, respectively. """ return tuple(PauliList(*[elem_gen(nq,idx,P) for idx in range(nq)]) for P in ['X','Z'])
def __call__(self, other): if isinstance(other, Pauli): return self.conjugate_pauli(other) elif isinstance(other, str): return self.conjugate_pauli(Pauli(other)) elif isinstance(other, PauliList): return PauliList(*map(self, other)) elif isinstance(other, stab.StabilizerCode): return stab.StabilizerCode(self(other.group_generators), self(other.logical_xs), self(other.logical_zs)) else: return NotImplemented
def solve_commutation_constraints( commutation_constraints=[], anticommutation_constraints=[], search_in_gens=None, search_in_set=None ): r""" Given commutation constraints on a Pauli operator, yields an iterator onto all solutions of those constraints. :param commutation_constraints: A list of operators :math:`\{A_i\}` such that each solution :math:`P` yielded by this function must satisfy :math:`[A_i, P] = 0` for all :math:`i`. :param anticommutation_constraints: A list of operators :math:`\{B_i\}` such that each solution :math:`P` yielded by this function must satisfy :math:`\{B_i, P\} = 0` for all :math:`i`. :param search_in_gens: A list of operators :math:`\{N_i\}` that generate the group in which to search for solutions. If ``None``, defaults to the elementary generators of the pc.Pauli group on :math:`n` qubits, where :math:`n` is given by the length of the commutation and anticommutation constraints. :param search_in_set: An iterable of operators to which the search for satisfying assignments is restricted. This differs from ``search_in_gens`` in that it specifies the entire set, not a generating set. When this parameter is specified, a brute-force search is executed. Use only when the search set is small, and cannot be expressed using its generating set. :returns: An iterator ``it`` such that ``list(it)`` contains all operators within the group :math:`G = \langle N_1, \dots, N_k \rangle` given by ``search_in_gens``, consistent with the commutation and anticommutation constraints. This function is based on finding the generators of the centralizer groups of each commutation constraint, and is thus faster than a predicate-based search over the entire group of interest. The resulting iterator can be used in conjunction with other filters, however. >>> import qecc as q >>> list(q.solve_commutation_constraints(q.PauliList('XXI', 'IZZ', 'IYI'), q.PauliList('YIY'))) [i^0 XII, i^0 IIZ, i^0 YYX, i^0 ZYY] >>> from itertools import ifilter >>> list(ifilter(lambda P: P.wt <= 2, q.solve_commutation_constraints(q.PauliList('XXI', 'IZZ', 'IYI'), q.PauliList('YIY')))) [i^0 XII, i^0 IIZ] """ # Normalize our arguments to be PauliLists, so that we can obtain # centralizers easily. if not isinstance(commutation_constraints, PauliList): commutation_constraints = PauliList(commutation_constraints) if not isinstance(anticommutation_constraints, PauliList): # This is probably not necessary, strictly speaking, but it keeps me # slightly more sane to have both constraints represented by the same # sequence type. anticommutation_constraints = PauliList(anticommutation_constraints) # Then check that the arguments make sense. if len(commutation_constraints) == 0 and len(anticommutation_constraints) == 0: raise ValueError("At least one constraint must be specified.") #We default to executing a brute-force search if the search set is #explicitly specified: if search_in_set is not None: commutation_predicate = AllPredicate(*map( lambda acc: (lambda P: pc.com(P, acc) == 0), commutation_constraints )) commuters = filter(commutation_predicate, search_in_set) anticommutation_predicate = AllPredicate(*map( lambda acc: (lambda P: pc.com(P, acc) == 1), anticommutation_constraints )) return filter(anticommutation_predicate, commuters) # We finish putting arguments in the right form by defaulting to searching # over the pc.Pauli group on $n$ qubits. if search_in_gens is None: nq = len(commutation_constraints[0] if len(commutation_constraints) > 0 else anticommutation_constraints[0]) Xs, Zs = pc.elem_gens(nq) search_in_gens = Xs + Zs # Now we update our search by restricting to the centralizer of the # commutation constraints. search_in_gens = commutation_constraints.centralizer_gens(group_gens=search_in_gens) # Finally, we return a filter iterator on the elements of the given # centralizer that selects elements which anticommute appropriately. anticommutation_predicate = AllPredicate(*map( lambda acc: (lambda P: pc.com(P, acc) == 1), anticommutation_constraints )) assert len(search_in_gens) > 0 return ifilter(anticommutation_predicate, pc.from_generators(search_in_gens))