Source code for ssip.coequalisers

# Functions to perform "internal merges" on a code.
# Follows basically the same prescription as pushouts
# and is strictly more general.

# One can prove that these quotients on rows and columns are the correct ones
# using the quotient chain map.
import numpy as np

from ssip.basic_functions import (
    BinMatrix,
    CSScode,
    direct_sum_matrices,
    find_homology_basis,
    find_paired_basis,
    num_data_qubits,
    num_logical_qubits,
)
from ssip.merge_result import MergeResult, find_logical_splitting
from ssip.monic_span_checker import monic_span, restricted_matrix
from ssip.pushouts import sandwich_middle_code


# Assumes that there are no qubits which appear twice in the qubit_map;
# same for checks in the syndrome_map.
# A and B are differentials.
[docs] def coequaliser( A: BinMatrix, B: BinMatrix, qubit_map: dict, syndrome_map: dict, return_data: bool = False, ) -> CSScode | MergeResult: # First differential # Quotient rows together A2 = A.copy() B2 = B.copy() for item in qubit_map.items(): # XOR, as two qubits being quotiented can share a Z-check A2[item[0]] = np.array( list(map(lambda x, y: bool((x + y) % 2), A2[item[0]], A2[item[1]])) ) del2 = np.delete(A2, list(qubit_map.values()), 0) # Quotient rows together. Also an XOR for item in syndrome_map.items(): B2[item[0]] = np.array( list(map(lambda x, y: bool((x + y) % 2), B2[item[0]], B2[item[1]])) ) del1 = np.delete(B2, list(syndrome_map.values()), 0) # Second differential # Quotient columns together. Just an OR this time. for item in qubit_map.items(): del1[:, item[0]] = np.array( list(map(lambda x, y: bool(x + y), del1[:, item[0]], del1[:, item[1]])) ) del1 = np.delete(del1, list(qubit_map.values()), 1) new_code = CSScode(del2.T, del1) if return_data: coeq = np.eye(A.shape[0]) for key, val in qubit_map.items(): coeq[key] = np.array( list(map(lambda x, y: bool(x + y), coeq[key], coeq[val])) ) coeq = np.delete(coeq, list(qubit_map.values()), 0) num_before_logicals = len(find_homology_basis(A, B)) num_after_logicals = num_logical_qubits(new_code) num_new_logicals = num_after_logicals - num_before_logicals + 1 new_Z_logicals = [] new_X_logicals = [] old_Z_logicals = [] old_X_logicals = [] if num_new_logicals < 0: raise ValueError("Coequaliser error, logical operators are not irreducible") if num_new_logicals: old_code = CSScode(A.T, B) new_Z_logicals, new_X_logicals, old_Z_logicals, old_X_logicals = ( find_logical_splitting( old_code, new_code, coeq, num_before_logicals, num_after_logicals ) ) else: old_Z_logicals = find_homology_basis(del2, del1) trial_X_basis = find_homology_basis(del1.T, del2.T) old_X_logicals = find_paired_basis(old_Z_logicals, trial_X_basis) return MergeResult( new_code, coeq, [], [], [], new_Z_logicals, new_X_logicals, old_Z_logicals, old_X_logicals, ) return new_code
[docs] def coequaliser_by_indices( C: CSScode, indices1: list, indices2: list, basis: str = "Z", return_data: bool = False, ) -> CSScode | MergeResult | None: restr1 = None restr2 = None if basis == "Z": (restr1, rows_to_keep1) = restricted_matrix(indices1, C.PX) (restr2, rows_to_keep2) = restricted_matrix(indices2, C.PX) elif basis == "X": (restr1, rows_to_keep1) = restricted_matrix(indices1, C.PZ) (restr2, rows_to_keep2) = restricted_matrix(indices2, C.PZ) else: raise ValueError("Must enter a valid Pauli string.") span = monic_span(restr1, restr2) if span is None: return None qubit_map = {} for qb1, qb2 in span[0].items(): qubit_map[indices1[qb1]] = indices2[qb2] syndrome_map = {} for syndrome1, syndrome2 in span[1].items(): syndrome_map[rows_to_keep1[syndrome1]] = rows_to_keep2[syndrome2] if basis == "Z": return coequaliser( C.PZ.T.copy(), C.PX.copy(), qubit_map, syndrome_map, return_data ) elif basis == "X": temp_code = coequaliser( C.PX.T.copy(), C.PZ.copy(), qubit_map, syndrome_map, return_data ) if return_data: temp_new_Z_logicals = temp_code.NewZLogicals temp_code.NewZLogicals = temp_code.NewXLogicals temp_code.NewXLogicals = temp_new_Z_logicals temp_old_Z_logicals = temp_code.OldZLogicals temp_code.OldZLogicals = temp_code.OldXLogicals temp_code.OldXLogicals = temp_old_Z_logicals temp_code.Code = CSScode(temp_code.Code.PX, temp_code.Code.PZ) return temp_code return CSScode(temp_code.PX, temp_code.PZ)
# V the logical operator subcomplex, C the base code. Does not work if # there are duplicate qubits or syndromes. The duplicates in the syndrome # can be resolved mathematically if they appear in the same entry, # but I do not know how to code this right now.
[docs] def internal_merge( V: BinMatrix, C: CSScode, qubit_map: dict, syndrome_map: dict, depth: int = 1, return_data: str = False, ) -> CSScode | MergeResult: tens = sandwich_middle_code(V, depth) direct_sum1 = direct_sum_matrices(tens.PZ.T, C.PZ.T) direct_sum2 = direct_sum_matrices(tens.PX, C.PX) num_new_qubits = num_data_qubits(tens) num_new_X_checks = tens.PX.shape[0] qmap1 = {} qmap2 = {} for count, item in enumerate(qubit_map.items()): qmap1[item[0] + num_new_qubits] = count qmap2[item[1] + num_new_qubits - len(qubit_map)] = (depth - 1) * len( qubit_map ) + count smap1 = {} smap2 = {} for count, item in enumerate(syndrome_map.items()): smap1[item[0] + num_new_X_checks] = count smap2[item[1] + num_new_X_checks - len(syndrome_map)] = (depth - 1) * len( syndrome_map ) + count first_coeq = coequaliser(direct_sum1, direct_sum2, qmap1, smap1) second_coeq = coequaliser(first_coeq.PZ.T, first_coeq.PX, qmap2, smap2) if return_data: n_before = num_data_qubits(C) n_after = num_data_qubits(second_coeq) if not n_after == num_data_qubits(C) + (depth - 1) * len( qubit_map ) + depth * len(syndrome_map): raise RuntimeError("Dimensions do not match up when returning merge data") new_qubits = list( range((depth - 1) * len(qubit_map) + depth * len(syndrome_map)) ) merge_map = np.zeros((n_after, n_before)) for i in range(n_before): merge_map[i + len(new_qubits)][i] = 1 new_Z_stabilisers = list(range(depth * len(qubit_map))) new_X_stabilisers = list(range((depth - 1) * len(syndrome_map))) num_before_logicals = num_logical_qubits(C) num_after_logicals = num_logical_qubits(second_coeq) num_new_logicals = num_after_logicals - num_before_logicals + 1 new_Z_logicals = [] new_X_logicals = [] old_Z_logicals = [] old_X_logicals = [] if num_new_logicals < 0: raise ValueError( "Internal merge error, logical operators are not irreducible" ) if num_new_logicals: new_Z_logicals, new_X_logicals, old_Z_logicals, old_X_logicals = ( find_logical_splitting( C, second_coeq, merge_map, num_before_logicals, num_after_logicals ) ) else: old_Z_logicals = find_homology_basis(second_coeq.PZ.T, second_coeq.PX) old_X_logicals = find_homology_basis(second_coeq.PX.T, second_coeq.PZ) old_X_logicals = find_paired_basis(old_Z_logicals, old_X_logicals) return MergeResult( second_coeq, merge_map, new_Z_stabilisers, new_X_stabilisers, new_qubits, new_Z_logicals, new_X_logicals, old_Z_logicals, old_X_logicals, ) return second_coeq
[docs] def internal_merge_by_indices( C: CSScode, indices1: list[int], indices2: list[int], basis: str = "Z", depth: int = 1, return_data: bool = False, ) -> CSScode | MergeResult | None: """Construct an internal merge within one CSS codeblock, along two disjoint logical operators. Args: C: The CSScode to merge within. indices1: The qubits in the support of the first logical operator. indices2: The qubits in the support of the second logical operator. basis: The basis, X or Z, to perform the logical measurement in. depth: The depth of the merge, i.e. how large to make the tensor product intermediate code. return_data: Whether to calculate a MergeResult rather than a CSScode output. The MergeResult includes substantially more data about the merge. Return: The merged CSScode, or the MergeResult object which contains the CSScode, or None if no merge can be found. """ restr1 = None restr2 = None if basis == "Z": (restr1, rows_to_keep1) = restricted_matrix(indices1, C.PX) (restr2, rows_to_keep2) = restricted_matrix(indices2, C.PX) elif basis == "X": (restr1, rows_to_keep1) = restricted_matrix(indices1, C.PZ) (restr2, rows_to_keep2) = restricted_matrix(indices2, C.PZ) else: raise ValueError("Must enter a valid Pauli string.") span = monic_span(restr1, restr2) if span is None: return None qubit_map = {} for qb1, qb2 in span[0].items(): qubit_map[indices1[qb1]] = indices2[qb2] syndrome_map = {} for syndrome1, syndrome2 in span[1].items(): syndrome_map[rows_to_keep1[syndrome1]] = rows_to_keep2[syndrome2] qubit_map = dict(sorted(qubit_map.items())) syndrome_map = dict(sorted(syndrome_map.items())) for val in qubit_map.values(): if val in qubit_map: return None # if there are duplicate syndromes do not merge, as it yields a # qubit with no X-checks, hence the code is distance 1 for val in syndrome_map.values(): if val in syndrome_map: return None if basis == "Z": return internal_merge(restr1, C, qubit_map, syndrome_map, depth, return_data) elif basis == "X": temp_code = internal_merge( restr1, CSScode(C.PX, C.PZ), qubit_map, syndrome_map, depth, return_data ) if return_data: temp_code.Code = CSScode(temp_code.Code.PX, temp_code.Code.PZ) temp_new_X_stabs = temp_code.NewXStabs.copy() temp_code.NewXStabs = temp_code.NewZStabs.copy() temp_code.NewZStabs = temp_new_X_stabs temp_new_Z_logicals = temp_code.NewZLogicals temp_code.NewZLogicals = temp_code.NewXLogicals temp_code.NewXLogicals = temp_new_Z_logicals temp_old_Z_logicals = temp_code.OldZLogicals temp_code.OldZLogicals = temp_code.OldXLogicals temp_code.OldXLogicals = temp_old_Z_logicals return temp_code return CSScode(temp_code.PX, temp_code.PZ)