def h_node_value(self, prob_dist, f, n, e, s, w): """Return horizontal edge tensor element value.""" paulis = ('I', 'X', 'Y', 'Z') op_to_pr = dict(zip(paulis, prob_dist)) f = pt.pauli_to_bsf(f) I, X, Y, Z = pt.pauli_to_bsf(paulis) # n,e,s,w are in {0,1} so multiply op to turn on or off op = (f + (n * Z) + (e * X) + (s * Z) + (w * X)) % 2 return op_to_pr[pt.bsf_to_pauli(op)]
def q_node_value(self, prob_dist, f, n, e, s, w): """Return qubit tensor element value.""" paulis = ('I', 'X', 'Y', 'Z') op_to_pr = dict(zip(paulis, prob_dist)) f = pt.pauli_to_bsf(f) # n, e, s, w are in {0, 1, 2, 3} so create dict from index to op index_to_op = dict(zip((0, 1, 2, 3), pt.pauli_to_bsf(paulis))) # apply ops from indices to f op = (f + index_to_op[n] + index_to_op[e] + index_to_op[s] + index_to_op[w]) % 2 # return probability of op return op_to_pr[pt.bsf_to_pauli(op)]
def generate(self, code, probability, rng=None): """ See :meth:`qecsim.model.ErrorModel.generate` Notes: * This method delegates to :meth:`probability_distribution` to find the probability of I, X, Y, Z operators on each qubit, assuming an IID error model. """ n_qubits = code.n_k_d[0] rng = np.random.default_rng() if rng is None else rng error_pauli = ''.join(rng.choice( ('I', 'X', 'Y', 'Z'), size=n_qubits, p=self.probability_distribution(probability) )) # (pI,pX,pY,pZ)=error_model.probability_distribution(probability) # error_Pauli=[] # error_Pauli.extend('X'*round(n_qubits*pX)) # error_Pauli.extend('Y'*round(n_qubits*pY)) # error_Pauli.extend('Z'*round(n_qubits*pZ)) # error_Pauli.extend('I'*(n_qubits-len(error_Pauli))) # shuffle(error_Pauli) # error_Pauli=''.join(error_Pauli) return pt.pauli_to_bsf(error_pauli)
def generate(self, code, probability, rng=None): """ See :meth:`qecsim.model.ErrorModel.generate` Notes: * This method delegates to :meth:`probability_distribution` to find the probability of I, X, Y, Z operators on each qubit, assuming an IID error model. """ rng = np.random.default_rng() if rng is None else rng n_qubits = code.n_k_d[0] error_pauli = ''.join(rng.choice( ('I', 'X', 'Y', 'Z'), size=n_qubits, p=self.probability_distribution(probability) )) return pt.pauli_to_bsf(error_pauli)
def permute_error_Pauli(error_Pauli, perm_vec): #XYZ,ZYX,XZY,YXZ,YZX,ZXY n_qubits = len(error_Pauli) for i in range(n_qubits): #if perm_vec[i]==0: XYZ if perm_vec[i] == 1: #ZYX if error_Pauli[i] == 'X': error_Pauli[i] = 'Z' elif error_Pauli[i] == 'Z': error_Pauli[i] = 'X' elif perm_vec[i] == 2: #XZY if error_Pauli[i] == 'Y': error_Pauli[i] = 'Z' elif error_Pauli[i] == 'Z': error_Pauli[i] = 'Y' elif perm_vec[i] == 3: #YXZ if error_Pauli[i] == 'X': error_Pauli[i] = 'Y' elif error_Pauli[i] == 'Y': error_Pauli[i] = 'X' elif perm_vec[i] == 4: #XYZ->YZX Schrodinger if error_Pauli[i] == 'X': error_Pauli[i] = 'Z' elif error_Pauli[i] == 'Y': error_Pauli[i] = 'X' elif error_Pauli[i] == 'Z': error_Pauli[i] = 'Y' elif perm_vec[i] == 5: #XYZ->ZXY Schrodinger if error_Pauli[i] == 'X': error_Pauli[i] = 'Y' elif error_Pauli[i] == 'Y': error_Pauli[i] = 'Z' elif error_Pauli[i] == 'Z': error_Pauli[i] = 'X' step_error = pt.pauli_to_bsf(''.join(error_Pauli)) return step_error
def _coset_probabilities(self, prob_dist, hadamard_vec,hadamard_mat, sample_pauli): r""" Return the (approximate) probability and sample Pauli for the left coset :math:`fG` of the stabilizer group :math:`G` of the planar code with respect to the given sample Pauli :math:`f`, as well as for the cosets :math:`f\bar{X}G`, :math:`f\bar{Y}G` and :math:`f\bar{Z}G`. :param prob_dist: Tuple of probability distribution in the format (P(I), P(X), P(Y), P(Z)). :type prob_dist: 4-tuple of float :param sample_pauli: Sample planar Pauli. :type sample_pauli: PlanarPauli :return: Coset probabilities, Sample Paulis (both in order I, X, Y, Z) E.g. (0.20, 0.10, 0.05, 0.10), (PlanarPauli(...), PlanarPauli(...), PlanarPauli(...), PlanarPauli(...)) :rtype: 4-tuple of mp.mpf, 4-tuple of PlanarPauli """ # NOTE: all list/tuples in this method are ordered (i, x, y, z) # empty log warnings log_warnings = [] # sample paulis sample_paulis = ( sample_pauli, sample_pauli.copy().logical_x(), sample_pauli.copy().logical_x().logical_z(), sample_pauli.copy().logical_z() ) # tensor networks: tns are common to both contraction by column and by row (after transposition) tns = [self._tnc.create_tn(prob_dist, sp) for sp in sample_paulis] tns= [self._tnc.modify_tn(tn, had_prob_dist, hadamard_mat,sample_pauli) # probabilities coset_ps = (0.0, 0.0, 0.0, 0.0) # default coset probabilities coset_ps_col = coset_ps_row = None # undefined coset probabilities by column and row # N.B. After multiplication by mult, coset_ps will be of type mp.mpf so don't process with numpy! if self._mode in ('c', 'a'): # evaluate coset probabilities by column coset_ps_col = [0.0, 0.0, 0.0, 0.0] # default coset probabilities for i in range(len(tns)): try: coset_ps_col[i] = tt.mps2d.contract(tns[i], chi=self._chi, tol=self._tol) except (ValueError, np.linalg.LinAlgError) as ex: log_warnings.append('CONTRACTION BY COL FOR {} COSET FAILED: {!r}'.format('IXYZ'[i], ex)) # treat nan as inf so it doesn't get lost coset_ps_col = [mp.inf if mp.isnan(coset_p) else coset_p for coset_p in coset_ps_col] if self._mode in ('r', 'a'): # evaluate coset probabilities by row coset_ps_row = [0.0, 0.0, 0.0, 0.0] # default coset probabilities # transpose tensor networks tns = [tt.mps2d.transpose(tn) for tn in tns] for i in range(len(tns)): try: coset_ps_row[i] = tt.mps2d.contract(tns[i], chi=self._chi, tol=self._tol) except (ValueError, np.linalg.LinAlgError) as ex: log_warnings.append('CONTRACTION BY ROW FOR {} COSET FAILED: {!r}'.format('IXYZ'[i], ex)) # treat nan as inf so it doesn't get lost coset_ps_row = [mp.inf if mp.isnan(coset_p) else coset_p for coset_p in coset_ps_row] if self._mode == 'c': coset_ps = coset_ps_col elif self._mode == 'r': coset_ps = coset_ps_row elif self._mode == 'a': # average coset probabilities coset_ps = [sum(coset_p) / len(coset_p) for coset_p in zip(coset_ps_col, coset_ps_row)] # logging if log_warnings: log_data = { # instance 'decoder': repr(self), # method parameters 'prob_dist': prob_dist, 'sample_pauli': pt.pack(sample_pauli.to_bsf()), # variables (convert to string because mp.mpf) 'coset_ps_col': [repr(p) for p in coset_ps_col] if coset_ps_col else None, 'coset_ps_row': [repr(p) for p in coset_ps_row] if coset_ps_row else None, 'coset_ps': [repr(p) for p in coset_ps], } logger.warning('{}: {}'.format(' | '.join(log_warnings), json.dumps(log_data, sort_keys=True))) # results return tuple(coset_ps), sample_paulis def decode(self, code, hadamard_vec,hadamard_mat, syndrome, error_model=DepolarizingErrorModel(), # noqa: B008 error_probability=0.1, **kwargs): """ See :meth:`qecsim.model.Decoder.decode` Note: The optional keyword parameters ``error_model`` and ``error_probability`` are used to determine the prior probability distribution for use in the decoding algorithm. Any provided error model must implement :meth:`~qecsim.model.ErrorModel.probability_distribution`. :param code: Rotated planar code. :type code: RotatedPlanarCode :param syndrome: Syndrome as binary vector. :type syndrome: numpy.array (1d) :param error_model: Error model. (default=DepolarizingErrorModel()) :type error_model: ErrorModel :param error_probability: Overall probability of an error on a single qubit. (default=0.1) :type error_probability: float :return: Recovery operation as binary symplectic vector. :rtype: numpy.array (1d) """ # any recovery any_recovery = self.sample_recovery(code, syndrome) # probability distribution prob_dist = error_model.probability_distribution(error_probability) # coset probabilities, recovery operations coset_ps, recoveries = self._coset_probabilities(prob_dist, hadamard_vec,hadamard_mat, any_recovery) # most likely recovery operation max_coset_p, max_recovery = max(zip(coset_ps, recoveries), key=lambda coset_p_recovery: coset_p_recovery[0]) # logging if not (mp.isfinite(max_coset_p) and max_coset_p > 0): log_data = { # instance 'decoder': repr(self), # method parameters 'code': repr(code), 'syndrome': pt.pack(syndrome), 'error_model': repr(error_model), 'error_probability': error_probability, # variables 'prob_dist': prob_dist, 'coset_ps': [repr(p) for p in coset_ps], # convert to string because mp.mpf # context 'error': pt.pack(kwargs['error']) if 'error' in kwargs else None, } logger.warning('NON-POSITIVE-FINITE MAX COSET PROBABILITY: {}'.format(json.dumps(log_data, sort_keys=True))) # return most likely recovery operation as bsf return max_recovery.to_bsf() @property def label(self): """See :meth:`qecsim.model.Decoder.label`""" params = [('chi', self._chi), ('mode', self._mode), ('tol', self._tol), ] return 'Rotated planar MPS ({})'.format(', '.join('{}={}'.format(k, v) for k, v in params if v)) def __repr__(self): return '{}({!r}, {!r}, {!r})'.format(type(self).__name__, self._chi, self._mode, self._tol) class TNC: """Tensor network creator""" @functools.lru_cache() def h_node_value(self, prob_dist, f, n, e, s, w): """Return horizontal edge tensor element value.""" paulis = ('I', 'X', 'Y', 'Z') op_to_pr = dict(zip(paulis, prob_dist)) f = pt.pauli_to_bsf(f) I, X, Y, Z = pt.pauli_to_bsf(paulis) # n, e, s, w are in {0, 1} so multiply op to turn on or off op = (f + (n * Z) + (e * X) + (s * Z) + (w * X)) % 2 return op_to_pr[pt.bsf_to_pauli(op)] @functools.lru_cache() def v_node_value(self, prob_dist, f, n, e, s, w): """Return vertical edge tensor element value.""" # N.B. for v_node order of nesw is rotated relative to h_node return self.h_node_value(prob_dist, f, e, s, w, n) @functools.lru_cache() def create_h_node(self, prob_dist, f, compass_direction=None): """Return horizontal qubit tensor, i.e. has X plaquettes to left/right and Z plaquettes above/below.""" def _shape(compass_direction=None): """Return shape of tensor including dummy indices.""" return { # (ne, se, sw, nw) 'n': (2, 2, 2, 1), 'ne': (1, 2, 2, 1), 'e': (1, 2, 2, 2), 'se': (1, 1, 2, 2), 's': (2, 1, 2, 2), 'sw': (2, 1, 1, 2), 'w': (2, 2, 1, 2), 'nw': (2, 2, 1, 1), }.get(compass_direction, (2, 2, 2, 2)) # create bare h_node node = np.empty(_shape(compass_direction), dtype=np.float64) # fill values for n, e, s, w in np.ndindex(node.shape): node[(n, e, s, w)] = self.h_node_value(prob_dist, f, n, e, s, w) return node @functools.lru_cache() def create_v_node(self, prob_dist, f, compass_direction=None): """Return vertical qubit tensor, i.e. has Z plaquettes to left/right and X plaquettes above/below.""" def _shape(compass_direction=None): """Return shape of tensor including dummy indices.""" return { # (ne, se, sw, nw) 'n': (1, 2, 2, 2), 'ne': (1, 1, 2, 2), 'e': (2, 1, 2, 2), 'se': (2, 1, 1, 2), 's': (2, 2, 1, 2), # 'sw': (2, 2, 1, 1), # cannot happen 'w': (2, 2, 2, 1), 'nw': (1, 2, 2, 1), }.get(compass_direction, (2, 2, 2, 2)) # create bare v_node node = np.empty(_shape(compass_direction), dtype=np.float64) # fill values for n, e, s, w in np.ndindex(node.shape): node[(n, e, s, w)] = self.v_node_value(prob_dist, f, n, e, s, w) return node @functools.lru_cache() def create_s_node(self, compass_direction=None): """Return stabilizer tensor.""" def _shape(compass_direction=None): """Return shape of tensor including dummy indices.""" return { # (ne, se, sw, nw) 'n': (1, 2, 2, 1), 'e': (1, 1, 2, 2), 's': (2, 1, 1, 2), 'w': (2, 2, 1, 1), }.get(compass_direction, (2, 2, 2, 2)) node = tt.tsr.delta(_shape(compass_direction)) return node def create_tn(self, prob_dist, sample_pauli): """Return a network (numpy.array 2d) of tensors (numpy.array 4d). Note: The network contracts to the coset probability of the given sample_pauli. """ def _rotate_q_index(index, code): """Convert code site index in format (x, y) to tensor network q-node index in format (r, c)""" site_x, site_y = index # qubit index in (x, y) site_r, site_c = code.site_bounds[1] - site_y, site_x # qubit index in (r, c) return code.site_bounds[0] - site_c + site_r, site_r + site_c # q-node index in (r, c) def _rotate_p_index(index, code): """Convert code plaquette index in format (x, y) to tensor network s-node index in format (r, c)""" q_node_r, q_node_c = _rotate_q_index(index, code) # q-node index in (r, c) return q_node_r - 1, q_node_c # s-node index in (r, c) def _compass_q_direction(index, code): """if the code site index lies on border of lattice then give that direction, else empty string.""" direction = {code.site_bounds[1]: 'n', 0: 's'}.get(index[1], '') direction += {0: 'w', code.site_bounds[0]: 'e'}.get(index[0], '') return direction def _compass_p_direction(index, code): """if the code plaquette index lies on border of lattice then give that direction, else empty string.""" direction = {code.site_bounds[1]: 'n', -1: 's'}.get(index[1], '') direction += {-1: 'w', code.site_bounds[0]: 'e'}.get(index[0], '') return direction pi,px,py,pz=prob_dist had_prob_dist= pi,pz,py,px # extract code code = sample_pauli.code # initialise empty tn tn_max_r, _ = _rotate_q_index((0, 0), code) _, tn_max_c = _rotate_q_index((code.site_bounds[0], 0), code) tn = np.empty((tn_max_r + 1, tn_max_c + 1), dtype=object) # iterate over max_site_x, max_site_y = code.site_bounds for code_index in itertools.product(range(-1, max_site_x + 1), range(-1, max_site_y + 1)): is_z_plaquette = code.is_z_plaquette(code_index) if code.is_in_site_bounds(code_index): q_node_index = _rotate_q_index(code_index, code) q_pauli = sample_pauli.operator(code_index) if is_z_plaquette: #print(code_index) q_node = self.create_h_node(prob_dist, q_pauli, _compass_q_direction(code_index, code)) else: q_node = self.create_v_node(prob_dist, q_pauli, _compass_q_direction(code_index, code)) tn[q_node_index] = q_node if code.is_in_plaquette_bounds(code_index): s_node_index = _rotate_p_index(code_index, code) s_node = self.create_s_node(_compass_p_direction(code_index, code)) tn[s_node_index] = s_node return tn def modify_tn(tn, had_prob_dist, hadamard_mat,sample_pauli): """Return a network (numpy.array 2d) of tensors (numpy.array 4d). Note: The network contracts to the coset probability of the given sample_pauli. """ def _rotate_q_index(index, code): """Convert code site index in format (x, y) to tensor network q-node index in format (r, c)""" site_x, site_y = index # qubit index in (x, y) site_r, site_c = code.site_bounds[1] - site_y, site_x # qubit index in (r, c) return code.site_bounds[0] - site_c + site_r, site_r + site_c # q-node index in (r, c) def _compass_q_direction(index, code): """if the code site index lies on border of lattice then give that direction, else empty string.""" direction = {code.site_bounds[1]: 'n', 0: 's'}.get(index[1], '') direction += {0: 'w', code.site_bounds[0]: 'e'}.get(index[0], '') return direction code = sample_pauli.code max_site_x, max_site_y = code.site_bounds for code_index in itertools.product(range(-1, max_site_x + 1), range(-1, max_site_y + 1)): is_z_plaquette = code.is_z_plaquette(code_index) if code.is_in_site_bounds(code_index): q_node_index = _rotate_q_index(code_index, code) q_pauli = sample_pauli.operator(code_index) if is_z_plaquette: #print(code_index) if hadamard_mat[code_index]: q_node = self.create_h_node(had_prob_dist, q_pauli, _compass_q_direction(code_index, code)) else: if hadamard_mat[code_index]: q_node = self.create_v_node(had_prob_dist, q_pauli, _compass_q_direction(code_index, code)) tn[q_node_index] = q_node return tn
def test_pauli_to_bsf(p, expected): assert np.array_equal(pt.pauli_to_bsf(p), expected)
(np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]), 'ZZZZZ'), (np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), 'YYYYY'), (np.array([[1, 0, 0, 0, 1, 0, 0, 1, 0, 1], [0, 1, 0, 1, 0, 0, 0, 1, 1, 0] ]), ['XIZIY', 'IXZYI']), (np.array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), ['XXXXX', 'ZZZZZ', 'YYYYY']), (np.array([1, 0]), 'X'), (np.array([0, 1, 0, 1, 0, 0, 1, 1]), 'IXZY'), ]) def test_bsf_to_pauli(b, expected): assert pt.bsf_to_pauli(b) == expected @pytest.mark.parametrize( 'b, expected', [(pt.pauli_to_bsf('IIIII'), 0), (pt.pauli_to_bsf('XIZIY'), 3), (pt.pauli_to_bsf('XXXXX'), 5), (pt.pauli_to_bsf('ZZZZZ'), 5), (pt.pauli_to_bsf('ZZZZZ'), 5), (np.array([pt.pauli_to_bsf('XIZIY'), pt.pauli_to_bsf('XXXXX')]), 8)]) def test_bsf_weight(b, expected): assert pt.bsf_wt(b) == expected @pytest.mark.parametrize( 'a, b, expected', [ (np.array([0, 0, 0, 0, 0, 0]), np.array([0, 0, 0, 0, 0, 0 ]), 0), # III bsp III commute (np.array([1, 0, 0, 0, 0, 0]), np.array([1, 0, 0, 0, 0, 0 ]), 0), # XII bsp XII commute
@pytest.mark.parametrize('max_qubits', [ (-1), (12.5), ('asdf'), ]) def test_naive_decoder_new_invalid_parameters(max_qubits): with pytest.raises((ValueError, TypeError), match=r"^NaiveDecoder") as exc_info: NaiveDecoder(max_qubits) print(exc_info) @pytest.mark.parametrize('error', [ pt.pauli_to_bsf('IIIII'), pt.pauli_to_bsf('IXIII'), pt.pauli_to_bsf('IIYII'), pt.pauli_to_bsf('IIIIZ'), pt.pauli_to_bsf('IZYXI'), pt.pauli_to_bsf('YZYXX'), ]) def test_naive_decoder_decode(error): code = FiveQubitCode() decoder = NaiveDecoder() syndrome = pt.bsp(error, code.stabilizers.T) recovery = decoder.decode(code, syndrome) assert np.array_equal(pt.bsp(recovery, code.stabilizers.T), syndrome), ( 'recovery {} does not give the same syndrome as the error {}'.format( recovery, error)) assert np.all(pt.bsp(recovery ^ error, code.stabilizers.T) == 0), (
def _run_once_defN(mode, code,hadamard_mat, time_steps, error_model, decoder, n_errors_code, measurement_error_probability, rng): """Implements run_once and run_once_ftp functions""" # assumptions assert (mode == 'ideal' and time_steps == 1) or mode == 'ftp' # generate step_error, step_syndrome and step_measurement_error for each time step error_probability_sample=0.1 (pI,pX,pY,pZ)=error_model.probability_distribution(error_probability_sample) error_Pauli=[] error_Pauli.extend('X'*round(n_errors_code*pX/error_probability_sample)) error_Pauli.extend('Y'*round(n_errors_code*pY/error_probability_sample)) error_Pauli.extend('Z'*round(n_errors_code*pZ/error_probability_sample)) error_Pauli.extend('I'*(n_qubits-len(error_Pauli))) n_qubits = code.n_k_d[0] hadamard_vec=np.zeros(n_qubits) Nx=code.site_bounds[0]+1 Ny=code.site_bounds[1]+1 for i,j in np.ndindex(hadamard_mat.shape): if hadamard_mat[i,j]==1: hadamard_vec[j+(Ny-1-i)*Nx]=1 for _ in range(time_steps): step_errors, step_syndromes, step_measurement_errors = [], [], [] # step_error: random error based on error probability shuffle(error_Pauli) # for i in range(n_qubits): # if hadamard_vec[i]==1: # if error_Pauli[i]=='X': # error_Pauli[i]='Z' # elif error_Pauli[i]=='Z': # error_Pauli[i]='X' step_error=pt.pauli_to_bsf(''.join(error_Pauli)) # step_error = error_model.generate(code, error_probability, rng) for i in range(n_qubits): if hadamard_vec[i]==1: step_error_temp=step_error[i] step_error[i]=step_error[n_qubits+i] step_error[n_qubits+i]=step_error_temp step_errors.append(step_error) # step_syndrome: stabilizers that do not commute with the error step_syndrome = pt.bsp(step_error, code.stabilizers.T) step_syndromes.append(step_syndrome) # step_measurement_error: random syndrome bit flips based on measurement_error_probability if measurement_error_probability: step_measurement_error = rng.choice( (0, 1), size=step_syndrome.shape, p=(1 - measurement_error_probability, measurement_error_probability) ) else: step_measurement_error = np.zeros(step_syndrome.shape, dtype=int) step_measurement_errors.append(step_measurement_error) if logger.isEnabledFor(logging.DEBUG): logger.debug('run: step_errors={}'.format(step_errors)) logger.debug('run: step_syndromes={}'.format(step_syndromes)) logger.debug('run: step_measurement_errors={}'.format(step_measurement_errors)) # error: sum of errors at each time step error = np.bitwise_xor.reduce(step_errors) if logger.isEnabledFor(logging.DEBUG): logger.debug('run: error={}'.format(error)) # syndrome: apply measurement_error at times t-1 and t to syndrome at time t syndrome = [] for t in range(time_steps): syndrome.append(step_measurement_errors[t - 1] ^ step_syndromes[t] ^ step_measurement_errors[t]) # convert syndrome to 2d numpy array syndrome = np.array(syndrome) if logger.isEnabledFor(logging.DEBUG): logger.debug('run: syndrome={}'.format(syndrome)) # decoding: boolean or best match recovery operation based on decoder ctx = {'error_model': error_model, 'n_errors_code': n_errors_code, 'error': error, 'step_errors': step_errors, 'measurement_error_probability': measurement_error_probability, 'step_measurement_errors': step_measurement_errors} # convert syndrome to 1d if mode is 'ideal' if mode == 'ideal': # convert syndrome to 1d and call decode decoding = decoder.decode(code,hadamard_mat, syndrome[0], **ctx) if mode == 'ftp': # call decode_ftp decoding = decoder.decode_ftp(code, time_steps, syndrome, **ctx) if logger.isEnabledFor(logging.DEBUG): logger.debug('run: decoding={}'.format(decoding)) # if decoding is not DecodeResult, convert to DecodeResult if not isinstance(decoding, DecodeResult): # decoding is recovery, so wrap in DecodeResult decoding = DecodeResult(recovery=decoding) # raises error if recovery is None # extract outcomes from decoding success = decoding.success logical_commutations = decoding.logical_commutations custom_values = decoding.custom_values # if recovery specified, resolve success and logical_commutations if decoding.recovery is not None: # recovered code recovered = decoding.recovery ^ error # success checks commutes_with_stabilizers = np.all(pt.bsp(recovered, code.stabilizers.T) == 0) if not commutes_with_stabilizers: log_data = { # enough data to recreate issue # models 'code': repr(code), 'error_model': repr(error_model), 'decoder': repr(decoder), # variables 'error': pt.pack(error), 'recovery': pt.pack(decoding.recovery), # step variables 'step_errors': [pt.pack(v) for v in step_errors], 'step_measurement_errors': [pt.pack(v) for v in step_measurement_errors], } logger.warning('RECOVERY DOES NOT RETURN TO CODESPACE: {}'.format(json.dumps(log_data, sort_keys=True))) resolved_logical_commutations = pt.bsp(recovered, code.logicals.T) commutes_with_logicals = np.all(resolved_logical_commutations == 0) resolved_success = commutes_with_stabilizers and commutes_with_logicals # fill in unspecified outcomes success = resolved_success if success is None else success logical_commutations = resolved_logical_commutations if logical_commutations is None else logical_commutations if logger.isEnabledFor(logging.DEBUG): logger.debug('run: success={}'.format(success)) logger.debug('run: logical_commutations={!r}'.format(logical_commutations)) logger.debug('run: custom_values={!r}'.format(custom_values)) data = { 'error_weight': pt.bsf_wt(np.array(step_errors)), 'success': bool(success), 'logical_commutations': logical_commutations, 'custom_values': custom_values, } return data
def logical_zs(self): """See :meth:`qecsim.model.StabilizerCode.logical_zs`""" return pt.pauli_to_bsf(self._pauli_logical_zs)
def stabilizers(self): """See :meth:`qecsim.model.StabilizerCode.stabilizers`""" return pt.pauli_to_bsf(self._pauli_stabilizers)