def __init__(self, group_generators, logical_xs, logical_zs, label=None): self.group_generators = pc.PauliList(*group_generators) if (Unspecified in logical_xs) or (Unspecified in logical_zs): raise ValueError("Logical operators must be specified.") self.logical_xs = pc.PauliList(*logical_xs) self.logical_zs = pc.PauliList(*logical_zs) self.label = label
def permute_gen_ops(self, perm): r""" Returns a stabilizer code with generators related to the generators of `self`, with every instance of {X,Y,Z} replaced with {perm[0],perm[1],perm[2]}. :param list perm: A list containing 'X','Y', and 'Z' in any order, indicating which permutation is to be applied. >>> new_stab = StabilizerCode.bit_flip_code(1).permute_gen_ops('ZYX') >>> assert new_stab.group_generators == StabilizerCode.phase_flip_code(1).group_generators """ new_group_generators = pc.PauliList() for pauli in self.group_generators: new_group_generators.append(pauli.permute_op(perm)) new_log_xs = pc.PauliList() for pauli in self.logical_xs: new_log_xs.append(pauli.permute_op(perm)) new_log_zs = pc.PauliList() for pauli in self.logical_zs: new_log_zs.append(pauli.permute_op(perm)) return StabilizerCode(new_group_generators, new_log_xs, new_log_zs)
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)))
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
def logical_ys(self): """Derives logical :math:`Y` operators, given logical :math:`X` and :math:`Z` operators.""" return pc.PauliList( (ex * zed).mul_phase(1) for (ex, zed) in zip(self.logical_xs, self.logical_zs))