Source code for ssip.lifted_product

# Functions to generate tensor & lifted product CSS codes, following https://arxiv.org/abs/2012.04068
import numpy as np

from ssip.basic_functions import BinMatrix
from ssip.code_examples import CSScode


[docs] def tensor_product(A: BinMatrix, B: BinMatrix) -> CSScode: """Calculate the tensor product in the category of chain complexes given two length 1 chain complexes, i.e. classical codes. This produces a lengeth 2 chain complex, i.e. CSS code. Args: A: The first classical code. B: The second classical code. Return: The tensor product qubit CSS code. """ del2a = np.kron(A, np.eye(B.shape[1])) del2b = np.kron(np.eye(A.shape[1]), B) del2 = np.vstack((del2a, del2b)) del1a = np.kron(np.eye(A.shape[0]), B) del1b = np.kron(A, np.eye(B.shape[0])) del1 = np.hstack((del1a, del1b)) return CSScode(del2.T, del1)
[docs] def lift_connected_surface_codes(l: int, L: int) -> CSScode: """Construct the lift-connected surface code with base code paramaterised by l and lift matrix parameterised by L. See p6, https://arxiv.org/abs/2401.02911. Assumes L > 1. Args: l: Parameter for base code. L: Parameter for lift matrix. Return: The lift-connected surface code LCS(l, L). """ H_rep = np.zeros((l, l + 1)) H_int = np.zeros((l, l + 1)) for i in np.arange(l): H_rep[i][i] = 1 H_rep[i][i + 1] = 1 H_int[i][i + 1] = 1 identity_L = np.eye(L) permutation = np.roll(identity_L, 1, axis=1) identity_l = np.eye(l) identity_l1 = np.eye(l + 1) P_X_rep = np.kron( np.hstack((np.kron(identity_l1, H_rep), np.kron(H_rep.T, identity_l))), identity_L, ) P_X_int = np.hstack( ( np.kron(np.kron(identity_l1, H_int), permutation), np.kron(np.kron(H_int.T, identity_l), permutation.T), ) ) P_X = P_X_rep + P_X_int P_Z_rep = np.kron( np.hstack((np.kron(H_rep, identity_l1), np.kron(identity_l, H_rep.T))), identity_L, ) P_Z_int = np.hstack( ( np.kron(np.kron(H_int, identity_l1), permutation), np.kron(np.kron(identity_l, H_int.T), permutation.T), ) ) P_Z = P_Z_rep + P_Z_int return CSScode(P_Z, P_X)
[docs] def bivariate_bicycle_code( l: int, m: int, powers_A: tuple[list[int]], powers_B: tuple[list[int]] ) -> CSScode: """Construct the bivariate bicycle code with circulants of size l and m respectively. See p6, https://arxiv.org/abs/2401.02911. Args: l: Size of first circulants in the tensor product. m: Size of second circulants in the tensor product. powers_A: Powers of x and y representing first circulant matrix A. powers_B: Powers of x and y representing second circulant matrix B. Return: The bivariate bicycle code. """ identity_l = np.eye(l) permutation_l = np.roll(identity_l, 1, axis=1) identity_m = np.eye(m) permutation_m = np.roll(identity_m, 1, axis=1) x = np.kron(permutation_l, identity_m) y = np.kron(identity_l, permutation_m) A = np.zeros((x.shape[0], x.shape[1])) for i in powers_A[0]: A += np.linalg.matrix_power(x, i) for i in powers_A[1]: A += np.linalg.matrix_power(y, i) B = np.zeros((x.shape[0], x.shape[1])) for i in powers_B[0]: B += np.linalg.matrix_power(x, i) for i in powers_B[1]: B += np.linalg.matrix_power(y, i) P_Z = np.hstack((B.T, A.T)) P_X = np.hstack((A, B)) return CSScode(P_Z, P_X)
[docs] def generalised_bicycle_code( l: int, powers_A: list[int], powers_B: list[int] ) -> CSScode: """Construct the generalised bicycle code with circulant of size l, see eg https://arxiv.org/abs/1904.02703, https://arxiv.org/abs/1212.6703. Args: l: Size of circulant. powers_A: Powers of x representing first circulant matrix A. powers_B: Powers of xrepresenting second circulant matrix B. Return: The bivariate bicycle code. """ identity_l = np.eye(l) A = np.zeros((l, l)) for i in powers_A: A += np.roll(identity_l, i, axis=1) B = np.zeros((l, l)) for i in powers_B: B += np.roll(identity_l, i, axis=1) P_Z = np.hstack((B.T, A.T)) P_X = np.hstack((A, B)) return CSScode(P_Z, P_X)
### Functions specific to bivariate bicycle codes ###
[docs] def polynomial_to_qubits( poly: list[tuple[int]], l: int, m: int, primed: bool = False ) -> list[int]: """Converts a polynomial in two variables into a set of qubits in a bivariate bicycle code. Args: poly: the polynomial in x and y to be converted. l: Size of first circulants in the tensor product. m: Size of second circulants in the tensor product. primed: Whether to index into the primed or unprimed block. Return: The set of indices i.e. qubits in the code. """ indices = [] for term in poly: index = term[0] * m + term[1] if primed: index += l * m indices.append(index) return indices
# See https://arxiv.org/abs/2308.07915 p23. # alpha is a monomial in F_2[x, y], i.e. a pair of integers being powers of (x, y). # f is a polynomial in F_2[x, y], i.e. # a list of pairs of integers being powers of (x, y)
[docs] def unprimed_X_logical( alpha: tuple[int], f: list[tuple[int]], l: int, m: int ) -> list[int]: """Picks out a particular X logical in the unprimed block of a bivariate bicycle code, see p23 of https://arxiv.org/abs/2308.07915. Args: alpha: A monomial in F_2[x, y], i.e. a pair of integers being powers of x, y. f: A polynomial in F_2[x, y]. l: Size of first circulants in the tensor product. m: Size of second circulants in the tensor product. Return: The set of indices i.e. qubits in the code, which the X logical has support on. """ new_poly = [] for mono in f: x_power = (alpha[0] + mono[0]) % l y_power = (alpha[1] + mono[1]) % m new_poly.append((x_power, y_power)) return polynomial_to_qubits(new_poly, l, m, False)
[docs] def primed_X_logical( alpha: tuple[int], g: list[tuple[int]], h: list[tuple[int]], l: int, m: int ) -> list[int]: """Picks out a particular X logical in the primed block of a bivariate bicycle code, see p23 of https://arxiv.org/abs/2308.07915. Args: alpha: A monomial in F_2[x, y], i.e. a pair of integers being powers of x, y. g: A polynomial in F_2[x, y]. h: A polynomial in F_2[x, y]. l: Size of first circulants in the tensor product. m: Size of second circulants in the tensor product. Return: The set of indices i.e. qubits in the code, which the X logical has support on. """ new_poly = [] for mono in g: x_power = (alpha[0] + mono[0]) % l y_power = (alpha[1] + mono[1]) % m new_poly.append((x_power, y_power)) indices1 = polynomial_to_qubits(new_poly, l, m, False) new_poly = [] for mono in h: x_power = (alpha[0] + mono[0]) % l y_power = (alpha[1] + mono[1]) % m new_poly.append((x_power, y_power)) indices2 = polynomial_to_qubits(new_poly, l, m, True) return indices1 + indices2
[docs] def unprimed_Z_logical( alpha: tuple[int], h: list[tuple[int]], g: list[tuple[int]], l: int, m: int ) -> list[int]: """Picks out a particular Z logical in the unprimed block of a bivariate bicycle code, see p23 of https://arxiv.org/abs/2308.07915. Args: alpha: A monomial in F_2[x, y], i.e. a pair of integers being powers of x, y. h: A polynomial in F_2[x, y]. g: A polynomial in F_2[x, y]. l: Size of first circulants in the tensor product. m: Size of second circulants in the tensor product. Return: The set of indices i.e. qubits in the code, which the Z logical has support on. """ new_poly = [] for mono in h: x_power = (alpha[0] - mono[0]) % l y_power = (alpha[1] - mono[1]) % m new_poly.append((x_power, y_power)) indices1 = polynomial_to_qubits(new_poly, l, m, False) new_poly = [] for mono in g: x_power = (alpha[0] - mono[0]) % l y_power = (alpha[1] - mono[1]) % m new_poly.append((x_power, y_power)) indices2 = polynomial_to_qubits(new_poly, l, m, True) return indices1 + indices2
[docs] def primed_Z_logical( alpha: tuple[int], f: list[tuple[int]], l: int, m: int ) -> list[int]: """Picks out a particular Z logical in the primed block of a bivariate bicycle code, see p23 of https://arxiv.org/abs/2308.07915. Args: alpha: A monomial in F_2[x, y], i.e. a pair of integers being powers of x, y. f: A polynomial in F_2[x, y]. l: Size of first circulants in the tensor product. m: Size of second circulants in the tensor product. Return: The set of indices i.e. qubits in the code, which the Z logical has support on. """ new_poly = [] for mono in f: x_power = (alpha[0] - mono[0]) % l y_power = (alpha[1] - mono[1]) % m new_poly.append((x_power, y_power)) return polynomial_to_qubits(new_poly, l, m, True)