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)
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)
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)
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)
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)
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))
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()
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)
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))
def as_clifford(self, nq): return cc.phase(nq, *self.qubits)
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
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))
def as_clifford(self, nq): return cc.cnot(nq, *self.qubits)
def as_clifford(self, nq): return cc.eye_c(nq)
def as_clifford(self, nq): return cc.hadamard(nq, *self.qubits)