From d0513daa728f2b499382460e26214d4fa4be5be5 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Fri, 10 Jul 2026 15:51:42 +0100 Subject: [PATCH 01/12] feat: replace functionality of ldpc independent logical base extraction with Galois implementation --- .../src/deltakit_explorer/codes/_logicals.py | 33 ++++- .../codes/test_css_code_compute_logicals.py | 136 ++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py diff --git a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py index 079a2204..aa413c8c 100644 --- a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py +++ b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py @@ -7,7 +7,9 @@ from __future__ import annotations from collections.abc import Collection, Iterable +from typing import NamedTuple +import galois import numpy as np from deltakit_circuit import PauliX, PauliY, PauliZ, Qubit from deltakit_circuit._qubit_identifiers import _PauliGate @@ -241,6 +243,31 @@ def get_logical_operators_from_css_parity_check_matrices( ) +def compute_lz_galois(_hx: NDArray[np.floating], _hz: NDArray[np.floating]) -> NDArray[np.floating]: + + _hx_gf = galois.GF2(np.asarray(_hx,dtype=np.int_)) + _hz_gf = galois.GF2(np.asarray(_hz, dtype=np.int_)) + + ker_hx_gf = _hx_gf.null_space() + rank_hz_gf = np.linalg.matrix_rank(_hz_gf) + + log_stack = np.vstack((_hz_gf, ker_hx_gf)) + pivots = get_pivots(log_stack)[rank_hz_gf:] + + return np.asarray(log_stack[pivots]) + + +def get_pivots(check: galois.FieldArray) -> NDArray[np.int_]: + rr = check.T.row_reduce() + pivots: list[int] = [] + for row in rr: + nz = np.flatnonzero(row == 1) + if nz.size: + pivots.append(int(nz[0])) + return np.asarray(pivots, dtype=np.int_) + + + def css_code_compute_logicals( hx: NDArray[np.floating], hz: NDArray[np.floating] ) -> tuple[NDArray[np.floating], NDArray[np.floating]]: @@ -292,4 +319,8 @@ def compute_lz( return np.asarray(log_stack[pivots]) - return compute_lz(hz, hx), compute_lz(hx, hz) + return compute_lz_galois(hz, hx), compute_lz_galois(hx, hz) + + + + diff --git a/deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py b/deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py new file mode 100644 index 00000000..81a7bdd8 --- /dev/null +++ b/deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py @@ -0,0 +1,136 @@ +# (c) Copyright Riverlane 2020-2025. +from __future__ import annotations + +import math +from itertools import product + +import numpy as np +import pytest + +from deltakit_explorer.codes._logicals import css_code_compute_logicals + + +def _binary_matrix(rows: list[list[int]], n_cols: int) -> np.ndarray: + if len(rows) == 0: + return np.zeros((0, n_cols), dtype=np.uint8) + return np.asarray(rows, dtype=np.uint8) + + +def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: + num_rows, num_cols = matrix.shape + + if num_rows == 0: + return {tuple([0] * num_cols)} + + vectors: set[tuple[int, ...]] = set() + for coefficients in product((0, 1), repeat=num_rows): + vector = np.zeros(num_cols, dtype=np.uint8) + for coefficient, row in zip(coefficients, matrix, strict=True): + if coefficient: + vector ^= row + vectors.add(tuple(int(value) for value in vector)) + + return vectors + + +def _binary_null_space(matrix: np.ndarray) -> set[tuple[int, ...]]: + _, num_cols = matrix.shape + + vectors: set[tuple[int, ...]] = set() + for entries in product((0, 1), repeat=num_cols): + vector = np.asarray(entries, dtype=np.uint8) + if np.all((matrix @ vector) % 2 == 0): + vectors.add(entries) + + return vectors + + +def _dimension(space: set[tuple[int, ...]]) -> int: + return int(math.log2(len(space))) + + +def _assert_logical_basis( + logicals: np.ndarray, kernel_checks: np.ndarray, image_checks: np.ndarray +) -> None: + assert logicals.ndim == 2 + assert logicals.shape[1] == kernel_checks.shape[1] == image_checks.shape[1] + assert np.all(np.isin(logicals, [0, 1])) + + null_space = _binary_null_space(kernel_checks) + image_space = _binary_row_space(image_checks) + generated_space = _binary_row_space(np.vstack([image_checks, logicals])) + + # CSS logicals should extend the image space into a basis for the kernel. + assert generated_space == null_space + + expected_num_logicals = _dimension(null_space) - _dimension(image_space) + assert logicals.shape[0] == expected_num_logicals + + +@pytest.mark.parametrize( + ("hx", "hz"), + [ + (_binary_matrix([], 3), _binary_matrix([], 3)), + (_binary_matrix([[1, 1, 0], [0, 1, 1]], 3), _binary_matrix([], 3)), + (_binary_matrix(np.eye(3, dtype=np.uint8).tolist(), 3), _binary_matrix([], 3)), + ( + _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3), + _binary_matrix([], 3), + ), + ( + _binary_matrix([[1, 1, 1, 1]], 4), + _binary_matrix([[1, 1, 0, 0], [0, 0, 1, 1]], 4), + ), + ], +) +def test_css_code_compute_logicals_returns_valid_css_logical_bases( + hx: np.ndarray, hz: np.ndarray +): + assert np.all((hx @ hz.T) % 2 == 0) + + lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) + + _assert_logical_basis(np.asarray(lx, dtype=np.uint8), hz, hx) + _assert_logical_basis(np.asarray(lz, dtype=np.uint8), hx, hz) + + +def test_css_code_compute_logicals_is_invariant_to_redundant_rows(): + hx = _binary_matrix([[1, 1, 0], [0, 1, 1]], 3) + redundant_hx = _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3) + hz = _binary_matrix([], 3) + + lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) + redundant_lx, redundant_lz = css_code_compute_logicals( + redundant_hx.astype(float), hz.astype(float) + ) + + assert _binary_row_space(np.asarray(lx, dtype=np.uint8)) == _binary_row_space( + np.asarray(redundant_lx, dtype=np.uint8) + ) + assert _binary_row_space(np.asarray(lz, dtype=np.uint8)) == _binary_row_space( + np.asarray(redundant_lz, dtype=np.uint8) + ) + + +def test_css_code_compute_logicals_preserves_logical_dimension_for_known_rank_checks(): + # Adapted from the explicit GF(2) row-reduction fixture in galois/tests/fields/test_linalg.py. + hx = _binary_matrix( + [ + [1, 0, 1, 0, 1, 0, 1, 0], + [0, 1, 1, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1], + ], + 8, + ) + hz = _binary_matrix([], 8) + + rank_hx = _dimension(_binary_row_space(hx)) + assert rank_hx == 4 + + lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) + + assert lx.shape == (8 - rank_hx, 8) + assert lz.shape == (8 - rank_hx, 8) + _assert_logical_basis(np.asarray(lx, dtype=np.uint8), hz, hx) + _assert_logical_basis(np.asarray(lz, dtype=np.uint8), hx, hz) From ec02449cd0f578b7c5c65d851ada4c94301ea6dc Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Fri, 10 Jul 2026 15:57:25 +0100 Subject: [PATCH 02/12] chore: move tests into logicals... its only logical. --- .../codes/test_css_code_compute_logicals.py | 136 ------------------ .../tests/unit/codes/test_logicals.py | 129 +++++++++++++++++ 2 files changed, 129 insertions(+), 136 deletions(-) delete mode 100644 deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py diff --git a/deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py b/deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py deleted file mode 100644 index 81a7bdd8..00000000 --- a/deltakit-explorer/tests/unit/codes/test_css_code_compute_logicals.py +++ /dev/null @@ -1,136 +0,0 @@ -# (c) Copyright Riverlane 2020-2025. -from __future__ import annotations - -import math -from itertools import product - -import numpy as np -import pytest - -from deltakit_explorer.codes._logicals import css_code_compute_logicals - - -def _binary_matrix(rows: list[list[int]], n_cols: int) -> np.ndarray: - if len(rows) == 0: - return np.zeros((0, n_cols), dtype=np.uint8) - return np.asarray(rows, dtype=np.uint8) - - -def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: - num_rows, num_cols = matrix.shape - - if num_rows == 0: - return {tuple([0] * num_cols)} - - vectors: set[tuple[int, ...]] = set() - for coefficients in product((0, 1), repeat=num_rows): - vector = np.zeros(num_cols, dtype=np.uint8) - for coefficient, row in zip(coefficients, matrix, strict=True): - if coefficient: - vector ^= row - vectors.add(tuple(int(value) for value in vector)) - - return vectors - - -def _binary_null_space(matrix: np.ndarray) -> set[tuple[int, ...]]: - _, num_cols = matrix.shape - - vectors: set[tuple[int, ...]] = set() - for entries in product((0, 1), repeat=num_cols): - vector = np.asarray(entries, dtype=np.uint8) - if np.all((matrix @ vector) % 2 == 0): - vectors.add(entries) - - return vectors - - -def _dimension(space: set[tuple[int, ...]]) -> int: - return int(math.log2(len(space))) - - -def _assert_logical_basis( - logicals: np.ndarray, kernel_checks: np.ndarray, image_checks: np.ndarray -) -> None: - assert logicals.ndim == 2 - assert logicals.shape[1] == kernel_checks.shape[1] == image_checks.shape[1] - assert np.all(np.isin(logicals, [0, 1])) - - null_space = _binary_null_space(kernel_checks) - image_space = _binary_row_space(image_checks) - generated_space = _binary_row_space(np.vstack([image_checks, logicals])) - - # CSS logicals should extend the image space into a basis for the kernel. - assert generated_space == null_space - - expected_num_logicals = _dimension(null_space) - _dimension(image_space) - assert logicals.shape[0] == expected_num_logicals - - -@pytest.mark.parametrize( - ("hx", "hz"), - [ - (_binary_matrix([], 3), _binary_matrix([], 3)), - (_binary_matrix([[1, 1, 0], [0, 1, 1]], 3), _binary_matrix([], 3)), - (_binary_matrix(np.eye(3, dtype=np.uint8).tolist(), 3), _binary_matrix([], 3)), - ( - _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3), - _binary_matrix([], 3), - ), - ( - _binary_matrix([[1, 1, 1, 1]], 4), - _binary_matrix([[1, 1, 0, 0], [0, 0, 1, 1]], 4), - ), - ], -) -def test_css_code_compute_logicals_returns_valid_css_logical_bases( - hx: np.ndarray, hz: np.ndarray -): - assert np.all((hx @ hz.T) % 2 == 0) - - lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) - - _assert_logical_basis(np.asarray(lx, dtype=np.uint8), hz, hx) - _assert_logical_basis(np.asarray(lz, dtype=np.uint8), hx, hz) - - -def test_css_code_compute_logicals_is_invariant_to_redundant_rows(): - hx = _binary_matrix([[1, 1, 0], [0, 1, 1]], 3) - redundant_hx = _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3) - hz = _binary_matrix([], 3) - - lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) - redundant_lx, redundant_lz = css_code_compute_logicals( - redundant_hx.astype(float), hz.astype(float) - ) - - assert _binary_row_space(np.asarray(lx, dtype=np.uint8)) == _binary_row_space( - np.asarray(redundant_lx, dtype=np.uint8) - ) - assert _binary_row_space(np.asarray(lz, dtype=np.uint8)) == _binary_row_space( - np.asarray(redundant_lz, dtype=np.uint8) - ) - - -def test_css_code_compute_logicals_preserves_logical_dimension_for_known_rank_checks(): - # Adapted from the explicit GF(2) row-reduction fixture in galois/tests/fields/test_linalg.py. - hx = _binary_matrix( - [ - [1, 0, 1, 0, 1, 0, 1, 0], - [0, 1, 1, 0, 0, 1, 1, 0], - [0, 0, 0, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1, 1], - ], - 8, - ) - hz = _binary_matrix([], 8) - - rank_hx = _dimension(_binary_row_space(hx)) - assert rank_hx == 4 - - lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) - - assert lx.shape == (8 - rank_hx, 8) - assert lz.shape == (8 - rank_hx, 8) - _assert_logical_basis(np.asarray(lx, dtype=np.uint8), hz, hx) - _assert_logical_basis(np.asarray(lz, dtype=np.uint8), hx, hz) diff --git a/deltakit-explorer/tests/unit/codes/test_logicals.py b/deltakit-explorer/tests/unit/codes/test_logicals.py index 5c7bde22..2cb30106 100644 --- a/deltakit-explorer/tests/unit/codes/test_logicals.py +++ b/deltakit-explorer/tests/unit/codes/test_logicals.py @@ -1,9 +1,12 @@ import deltakit_circuit as circuit import deltakit_stim as stim import pytest +import numpy as np +from itertools import product from deltakit_explorer.codes._logicals import ( paulistring_to_operator, + css_code_compute_logicals ) @@ -32,3 +35,129 @@ def test_paulistring_to_operator(string): # operators_ref = [tuple(stim.PauliString(i) for i in j) for j in operators] # operators_res = get_str_logical_operators_from_tableau(stabilisers) # operators_res == operators_ref + + +def _binary_matrix(rows: list[list[int]], n_cols: int) -> np.ndarray: + if len(rows) == 0: + return np.zeros((0, n_cols), dtype=np.uint8) + return np.asarray(rows, dtype=np.uint8) + + +def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: + num_rows, num_cols = matrix.shape + + if num_rows == 0: + return {tuple([0] * num_cols)} + + vectors: set[tuple[int, ...]] = set() + for coefficients in product((0, 1), repeat=num_rows): + vector = np.zeros(num_cols, dtype=np.uint8) + for coefficient, row in zip(coefficients, matrix, strict=True): + if coefficient: + vector ^= row + vectors.add(tuple(int(value) for value in vector)) + + return vectors + + +def _binary_null_space(matrix: np.ndarray) -> set[tuple[int, ...]]: + _, num_cols = matrix.shape + + vectors: set[tuple[int, ...]] = set() + for entries in product((0, 1), repeat=num_cols): + vector = np.asarray(entries, dtype=np.uint8) + if np.all((matrix @ vector) % 2 == 0): + vectors.add(entries) + + return vectors + + +def _dimension(space: set[tuple[int, ...]]) -> int: + return int(np.log2(len(space))) + + +def _assert_logical_basis( + logicals: np.ndarray, kernel_checks: np.ndarray, image_checks: np.ndarray +) -> None: + assert logicals.ndim == 2 + assert logicals.shape[1] == kernel_checks.shape[1] == image_checks.shape[1] + assert np.all(np.isin(logicals, [0, 1])) + + null_space = _binary_null_space(kernel_checks) + image_space = _binary_row_space(image_checks) + generated_space = _binary_row_space(np.vstack([image_checks, logicals])) + + # CSS logicals should extend the image space into a basis for the kernel. + assert generated_space == null_space + + expected_num_logicals = _dimension(null_space) - _dimension(image_space) + assert logicals.shape[0] == expected_num_logicals + + +@pytest.mark.parametrize( + ("hx", "hz"), + [ + (_binary_matrix([], 3), _binary_matrix([], 3)), + (_binary_matrix([[1, 1, 0], [0, 1, 1]], 3), _binary_matrix([], 3)), + (_binary_matrix(np.eye(3, dtype=np.uint8).tolist(), 3), _binary_matrix([], 3)), + ( + _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3), + _binary_matrix([], 3), + ), + ( + _binary_matrix([[1, 1, 1, 1]], 4), + _binary_matrix([[1, 1, 0, 0], [0, 0, 1, 1]], 4), + ), + ], +) +def test_css_code_compute_logicals_returns_valid_css_logical_bases( + hx: np.ndarray, hz: np.ndarray +): + assert np.all((hx @ hz.T) % 2 == 0) + + lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) + + _assert_logical_basis(np.asarray(lx, dtype=np.uint8), hz, hx) + _assert_logical_basis(np.asarray(lz, dtype=np.uint8), hx, hz) + + +def test_css_code_compute_logicals_is_invariant_to_redundant_rows(): + hx = _binary_matrix([[1, 1, 0], [0, 1, 1]], 3) + redundant_hx = _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3) + hz = _binary_matrix([], 3) + + lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) + redundant_lx, redundant_lz = css_code_compute_logicals( + redundant_hx.astype(float), hz.astype(float) + ) + + assert _binary_row_space(np.asarray(lx, dtype=np.uint8)) == _binary_row_space( + np.asarray(redundant_lx, dtype=np.uint8) + ) + assert _binary_row_space(np.asarray(lz, dtype=np.uint8)) == _binary_row_space( + np.asarray(redundant_lz, dtype=np.uint8) + ) + + +def test_css_code_compute_logicals_preserves_logical_dimension_for_known_rank_checks(): + # Adapted from the explicit GF(2) row-reduction fixture in galois/tests/fields/test_linalg.py. + hx = _binary_matrix( + [ + [1, 0, 1, 0, 1, 0, 1, 0], + [0, 1, 1, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1], + ], + 8, + ) + hz = _binary_matrix([], 8) + + rank_hx = _dimension(_binary_row_space(hx)) + assert rank_hx == 4 + + lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) + + assert lx.shape == (8 - rank_hx, 8) + assert lz.shape == (8 - rank_hx, 8) + _assert_logical_basis(np.asarray(lx, dtype=np.uint8), hz, hx) + _assert_logical_basis(np.asarray(lz, dtype=np.uint8), hx, hz) From eb09ea6cfa1d6a98c8e44ed71574a23b997ee94b Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Fri, 10 Jul 2026 16:01:43 +0100 Subject: [PATCH 03/12] style: trigger pre-commit. --- deltakit-explorer/tests/unit/codes/test_logicals.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deltakit-explorer/tests/unit/codes/test_logicals.py b/deltakit-explorer/tests/unit/codes/test_logicals.py index 2cb30106..fe875b24 100644 --- a/deltakit-explorer/tests/unit/codes/test_logicals.py +++ b/deltakit-explorer/tests/unit/codes/test_logicals.py @@ -1,12 +1,13 @@ +from itertools import product + import deltakit_circuit as circuit import deltakit_stim as stim -import pytest import numpy as np -from itertools import product +import pytest from deltakit_explorer.codes._logicals import ( + css_code_compute_logicals, paulistring_to_operator, - css_code_compute_logicals ) From ed8163e49400eb1ec13afab8bfac8b3b28d27a96 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Fri, 10 Jul 2026 16:33:49 +0100 Subject: [PATCH 04/12] refactor: replace compute_lz code with new galois variant, and generalize pivot_rows over finite field by checking != 0 instead of GF2 specific 1. --- .../src/deltakit_explorer/codes/_logicals.py | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py index aa413c8c..77755c4e 100644 --- a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py +++ b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py @@ -7,14 +7,12 @@ from __future__ import annotations from collections.abc import Collection, Iterable -from typing import NamedTuple import galois import numpy as np from deltakit_circuit import PauliX, PauliY, PauliZ, Qubit from deltakit_circuit._qubit_identifiers import _PauliGate from deltakit_stim import PauliString, Tableau -from ldpc import mod2 from numpy.typing import NDArray from deltakit_explorer.codes._css._stabiliser_helper_functions import ( @@ -243,29 +241,25 @@ def get_logical_operators_from_css_parity_check_matrices( ) -def compute_lz_galois(_hx: NDArray[np.floating], _hz: NDArray[np.floating]) -> NDArray[np.floating]: - - _hx_gf = galois.GF2(np.asarray(_hx,dtype=np.int_)) - _hz_gf = galois.GF2(np.asarray(_hz, dtype=np.int_)) - - ker_hx_gf = _hx_gf.null_space() - rank_hz_gf = np.linalg.matrix_rank(_hz_gf) - - log_stack = np.vstack((_hz_gf, ker_hx_gf)) - pivots = get_pivots(log_stack)[rank_hz_gf:] - - return np.asarray(log_stack[pivots]) +def pivot_rows(check: galois.FieldArray) -> NDArray[np.int_]: + """ + Compute the pivot row indices of a dense binary check matrix over GF(2). + Args: + check: A dense binary check matrix represented as a ``galois.FieldArray`` + over GF(2). -def get_pivots(check: galois.FieldArray) -> NDArray[np.int_]: + Returns: + The pivot row indices of ``check``. + """ + # Pivot rows of ``check`` are the pivot columns of ``check.T``. rr = check.T.row_reduce() pivots: list[int] = [] for row in rr: - nz = np.flatnonzero(row == 1) + nz = np.flatnonzero(row != 0) if nz.size: pivots.append(int(nz[0])) return np.asarray(pivots, dtype=np.int_) - def css_code_compute_logicals( @@ -301,26 +295,18 @@ def css_code_compute_logicals( a tuple ``(lx, lz)`` representing the X and Z logicals. """ - def compute_lz( + def compute_lz_galois( _hx: NDArray[np.floating], _hz: NDArray[np.floating] ) -> NDArray[np.floating]: - # lz logical operators - # lz\in ker{hx} AND \notin Im(Hz.T) + _hx_gf = galois.GF2(np.asarray(_hx, dtype=np.int_)) + _hz_gf = galois.GF2(np.asarray(_hz, dtype=np.int_)) - # compute the kernel basis of hx - # Note that because inputs are dense arrays, it is fine for every array to be dense in this - # function. - ker_hx = mod2.nullspace(_hx).todense() - # Row reduce to find vectors in kx that are not in the image of hz.T. - log_stack = np.vstack([_hz, ker_hx]) + ker_hx_gf = _hx_gf.null_space() + rank_hz_gf = np.linalg.matrix_rank(_hz_gf) - rank_hz = mod2.rank(_hz) - pivots = mod2.pivot_rows(log_stack)[rank_hz:] + log_stack = np.vstack((_hz_gf, ker_hx_gf)) + pivots = pivot_rows(log_stack)[rank_hz_gf:] return np.asarray(log_stack[pivots]) return compute_lz_galois(hz, hx), compute_lz_galois(hx, hz) - - - - From e03f03cc9be0199cdf718538672ab0545b14c456 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Sat, 11 Jul 2026 21:39:56 +0100 Subject: [PATCH 05/12] feat: actually do the thing and remove ldpc from explorer dependencies. --- deltakit-explorer/pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/deltakit-explorer/pyproject.toml b/deltakit-explorer/pyproject.toml index e6d04874..a91802f6 100644 --- a/deltakit-explorer/pyproject.toml +++ b/deltakit-explorer/pyproject.toml @@ -50,7 +50,6 @@ dependencies = [ "adjustText~=1.3", "pandas>=2.2.2", "pandas~=2.3; python_version >= '3.13'", - "ldpc~=2.2", # >=3.2.3: earlier 3.2 releases import the deprecated ``numpy.core`` in # ``unumpy``, which fails under the test suite's ``filterwarnings = error``. "uncertainties>=3.2.3,<4", From 63f24b9b05f9b91bcaa25d1b72a1a2e6f29ac652 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Mon, 13 Jul 2026 20:53:02 +0100 Subject: [PATCH 06/12] fix(test): use full distribution name for resolving detlakit_stim --- deltakit-explorer/tests/unit/circuits/test_tableau_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py b/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py index 3741c95b..3ec30b5a 100644 --- a/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py +++ b/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py @@ -102,7 +102,7 @@ ) from deltakit_explorer.qpu._native_gate_set import NativeGateSet -CURRENT_STIM_VERSION = Version(version("stim")) +CURRENT_STIM_VERSION = Version(version("deltakit_stim")) STIM_VERSION_V1_13_0 = Version("1.13.0") From 87b52d26767059404e7a6a1066c383d5f4f39fe9 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Tue, 14 Jul 2026 12:05:02 +0100 Subject: [PATCH 07/12] fix(test): use distribution name instead of package name in importlib.metadata.version call. --- deltakit-explorer/tests/unit/circuits/test_tableau_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py b/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py index 3ec30b5a..e83392e5 100644 --- a/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py +++ b/deltakit-explorer/tests/unit/circuits/test_tableau_functions.py @@ -102,7 +102,7 @@ ) from deltakit_explorer.qpu._native_gate_set import NativeGateSet -CURRENT_STIM_VERSION = Version(version("deltakit_stim")) +CURRENT_STIM_VERSION = Version(version("deltakit-stim")) STIM_VERSION_V1_13_0 = Version("1.13.0") From 9ef8398de6c4fe11979eb0fa28979d64e9abc127 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Tue, 14 Jul 2026 12:18:07 +0100 Subject: [PATCH 08/12] chore: remove pyproject.toml mypy override config for ldpc since its removed as part of this feature. --- deltakit-explorer/pyproject.toml | 7 ------- pyproject.toml | 7 ------- 2 files changed, 14 deletions(-) diff --git a/deltakit-explorer/pyproject.toml b/deltakit-explorer/pyproject.toml index a91802f6..93f35c30 100644 --- a/deltakit-explorer/pyproject.toml +++ b/deltakit-explorer/pyproject.toml @@ -104,13 +104,6 @@ files = "src/**/*.py" # The below list of disabled checks should eventually be empty. disable_error_code = "arg-type,assignment,attr-defined,dict-item,index,list-item,misc,operator,override,return-value,union-attr,var-annotated" -# The ldpc.mod2 stubs files contain syntax errors, and so make mypy fail earlier than expected -# with a SyntaxError. Ignoring the whole module for mypy. -[[tool.mypy.overrides]] -module = ["ldpc.mod2"] -follow_imports = "skip" -follow_imports_for_stubs = true - [[tool.mypy.overrides]] module = ["adjustText"] follow_untyped_imports = true diff --git a/pyproject.toml b/pyproject.toml index 43369719..34e745f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,13 +120,6 @@ deltakit-compile = { workspace = true } [tool.mypy] files = "src/**/*.py" -# The ldpc.mod2 stubs files contain syntax errors, and so make mypy fail earlier than expected -# with a SyntaxError. Ignoring the whole module for mypy. -[[tool.mypy.overrides]] -module = ["ldpc.mod2"] -follow_imports = "skip" -follow_imports_for_stubs = true - # for ruff configuration, see `ruff.toml` [tool.pytest.ini_options] From eb8362d53828be192a74b1581ae20b14b88c4eef Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Wed, 15 Jul 2026 08:07:45 +0100 Subject: [PATCH 09/12] fix(explorer): address review comments --- .../src/deltakit_explorer/codes/_logicals.py | 28 +++-- .../tests/unit/codes/test_logicals.py | 110 +++++++++++++----- 2 files changed, 103 insertions(+), 35 deletions(-) diff --git a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py index 77755c4e..602073c8 100644 --- a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py +++ b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py @@ -252,13 +252,13 @@ def pivot_rows(check: galois.FieldArray) -> NDArray[np.int_]: Returns: The pivot row indices of ``check``. """ - # Pivot rows of ``check`` are the pivot columns of ``check.T``. - rr = check.T.row_reduce() + # Pivot rows of ``check`` are the non-zero pivot columns of ``check.T``. + row_reduced = check.T.row_reduce() pivots: list[int] = [] - for row in rr: - nz = np.flatnonzero(row != 0) - if nz.size: - pivots.append(int(nz[0])) + for row in row_reduced: + non_zero_row = np.flatnonzero(row != 0) + if non_zero_row.size: + pivots.append(int(non_zero_row[0])) return np.asarray(pivots, dtype=np.int_) @@ -295,9 +295,21 @@ def css_code_compute_logicals( a tuple ``(lx, lz)`` representing the X and Z logicals. """ - def compute_lz_galois( + def compute_lz( _hx: NDArray[np.floating], _hz: NDArray[np.floating] ) -> NDArray[np.floating]: + """Compute a basis of logical operators for one side of a CSS code. + + This function finds operators that satisfy the checks in ``_hx`` but are + not just combinations of the checks in ``_hz``. + + Args: + _hx: Check matrix used to define valid operators. + _hz: Check matrix used to remove redundant operators. + + Returns: + A binary matrix whose rows are logical operators. + """ _hx_gf = galois.GF2(np.asarray(_hx, dtype=np.int_)) _hz_gf = galois.GF2(np.asarray(_hz, dtype=np.int_)) @@ -309,4 +321,4 @@ def compute_lz_galois( return np.asarray(log_stack[pivots]) - return compute_lz_galois(hz, hx), compute_lz_galois(hx, hz) + return compute_lz(hz, hx), compute_lz(hx, hz) diff --git a/deltakit-explorer/tests/unit/codes/test_logicals.py b/deltakit-explorer/tests/unit/codes/test_logicals.py index fe875b24..252cb9aa 100644 --- a/deltakit-explorer/tests/unit/codes/test_logicals.py +++ b/deltakit-explorer/tests/unit/codes/test_logicals.py @@ -38,13 +38,45 @@ def test_paulistring_to_operator(string): # operators_res == operators_ref -def _binary_matrix(rows: list[list[int]], n_cols: int) -> np.ndarray: - if len(rows) == 0: - return np.zeros((0, n_cols), dtype=np.uint8) +def _binary_matrix(rows: np.ndarray) -> np.ndarray: + """Convert binary rows into a dense ``uint8`` array. + + Args: + rows: Row vectors to place in the matrix. + + Returns: + A dense ``uint8`` array built from ``rows``. + """ return np.asarray(rows, dtype=np.uint8) +def _empty_binary_matrix(n_cols: int) -> np.ndarray: + """Construct an empty dense ``uint8`` array with a fixed column count. + + Args: + n_cols: Number of columns in the returned matrix. + + Returns: + A dense ``uint8`` array of shape ``(0, n_cols)``. + """ + return np.zeros((0, n_cols), dtype=np.uint8) + + def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: + """Enumerate the row space of a binary array using GF(2) arithmetic. + + Args: + matrix: A dense binary ``uint8`` array. + + Returns: + The set of all binary vectors obtainable as mod-2 sums of rows of + ``matrix``, represented as tuples. + + Notes: + This helper uses a brute-force enumeration over all binary choices of row + coefficients, so it is only suitable for the small matrices used in these + tests. + """ num_rows, num_cols = matrix.shape if num_rows == 0: @@ -62,6 +94,20 @@ def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: def _binary_null_space(matrix: np.ndarray) -> set[tuple[int, ...]]: + """Enumerate the null space of a binary array using GF(2) arithmetic. + + Args: + matrix: A dense binary ``uint8`` array. + + Returns: + The set of all binary vectors ``v`` such that ``matrix @ v == 0`` over + GF(2), represented as tuples. + + Notes: + This helper checks every binary vector of the appropriate length, so it is + a brute-force construction intended only for the small test cases in this + module. + """ _, num_cols = matrix.shape vectors: set[tuple[int, ...]] = set() @@ -78,36 +124,47 @@ def _dimension(space: set[tuple[int, ...]]) -> int: def _assert_logical_basis( - logicals: np.ndarray, kernel_checks: np.ndarray, image_checks: np.ndarray + logicals: np.ndarray, + null_space_defining_checks: np.ndarray, + quotient_space_defining_checks: np.ndarray, ) -> None: - assert logicals.ndim == 2 - assert logicals.shape[1] == kernel_checks.shape[1] == image_checks.shape[1] - assert np.all(np.isin(logicals, [0, 1])) - - null_space = _binary_null_space(kernel_checks) - image_space = _binary_row_space(image_checks) - generated_space = _binary_row_space(np.vstack([image_checks, logicals])) + logicals_is_matrix = logicals.ndim == 2 + num_logical_columns = logicals.shape[1] + num_logical_rows = logicals.shape[0] + num_null_space_columns = null_space_defining_checks.shape[1] + num_quotient_space_columns = quotient_space_defining_checks.shape[1] + logicals_are_binary = np.all(np.isin(logicals, [0, 1])) + + assert logicals_is_matrix + assert num_logical_columns == num_null_space_columns == num_quotient_space_columns + assert logicals_are_binary + + null_space = _binary_null_space(null_space_defining_checks) + quotient_space = _binary_row_space(quotient_space_defining_checks) + generated_space = _binary_row_space( + np.vstack([quotient_space_defining_checks, logicals]) + ) - # CSS logicals should extend the image space into a basis for the kernel. + # CSS logicals should extend the quotient space into a basis for the null space. assert generated_space == null_space - expected_num_logicals = _dimension(null_space) - _dimension(image_space) - assert logicals.shape[0] == expected_num_logicals + expected_num_logicals = _dimension(null_space) - _dimension(quotient_space) + assert num_logical_rows == expected_num_logicals @pytest.mark.parametrize( ("hx", "hz"), [ - (_binary_matrix([], 3), _binary_matrix([], 3)), - (_binary_matrix([[1, 1, 0], [0, 1, 1]], 3), _binary_matrix([], 3)), - (_binary_matrix(np.eye(3, dtype=np.uint8).tolist(), 3), _binary_matrix([], 3)), + (_empty_binary_matrix(3), _empty_binary_matrix(3)), + (_binary_matrix([[1, 1, 0], [0, 1, 1]]), _empty_binary_matrix(3)), + (_binary_matrix(np.eye(3, dtype=np.uint8).tolist()), _empty_binary_matrix(3)), ( - _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3), - _binary_matrix([], 3), + _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]]), + _empty_binary_matrix(3), ), ( - _binary_matrix([[1, 1, 1, 1]], 4), - _binary_matrix([[1, 1, 0, 0], [0, 0, 1, 1]], 4), + _binary_matrix([[1, 1, 1, 1]]), + _binary_matrix([[1, 1, 0, 0], [0, 0, 1, 1]]), ), ], ) @@ -123,9 +180,9 @@ def test_css_code_compute_logicals_returns_valid_css_logical_bases( def test_css_code_compute_logicals_is_invariant_to_redundant_rows(): - hx = _binary_matrix([[1, 1, 0], [0, 1, 1]], 3) - redundant_hx = _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]], 3) - hz = _binary_matrix([], 3) + hx = _binary_matrix([[1, 1, 0], [0, 1, 1]]) + redundant_hx = _binary_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]]) + hz = _empty_binary_matrix(3) lx, lz = css_code_compute_logicals(hx.astype(float), hz.astype(float)) redundant_lx, redundant_lz = css_code_compute_logicals( @@ -148,10 +205,9 @@ def test_css_code_compute_logicals_preserves_logical_dimension_for_known_rank_ch [0, 1, 1, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1, 1], - ], - 8, + ] ) - hz = _binary_matrix([], 8) + hz = _empty_binary_matrix(8) rank_hx = _dimension(_binary_row_space(hx)) assert rank_hx == 4 From d788cefb85043c97353a5baa46ecc721ea167d01 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Wed, 15 Jul 2026 14:53:01 +0100 Subject: [PATCH 10/12] fix(explorer): Rename variable in _logicals.py::pivot_rows --- .../src/deltakit_explorer/codes/_logicals.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py index 602073c8..3205bf5a 100644 --- a/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py +++ b/deltakit-explorer/src/deltakit_explorer/codes/_logicals.py @@ -241,19 +241,20 @@ def get_logical_operators_from_css_parity_check_matrices( ) -def pivot_rows(check: galois.FieldArray) -> NDArray[np.int_]: +def pivot_rows(parity_check_matrix: galois.FieldArray) -> NDArray[np.int_]: """ Compute the pivot row indices of a dense binary check matrix over GF(2). Args: - check: A dense binary check matrix represented as a ``galois.FieldArray`` - over GF(2). + parity_check_matrix: A dense binary check matrix represented as a + ``galois.FieldArray`` over GF(2). Returns: - The pivot row indices of ``check``. + The pivot row indices of ``parity_check_matrix``. """ - # Pivot rows of ``check`` are the non-zero pivot columns of ``check.T``. - row_reduced = check.T.row_reduce() + # Pivot rows of ``parity_check_matrix`` are the non-zero pivot columns of + # ``parity_check_matrix.T``. + row_reduced = parity_check_matrix.T.row_reduce() pivots: list[int] = [] for row in row_reduced: non_zero_row = np.flatnonzero(row != 0) From a8534abe808a5b06087b92cfd0aad6f098301890 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Wed, 15 Jul 2026 15:16:31 +0100 Subject: [PATCH 11/12] refactor(explorer): clarify logical helper naming --- .../tests/unit/codes/test_logicals.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/deltakit-explorer/tests/unit/codes/test_logicals.py b/deltakit-explorer/tests/unit/codes/test_logicals.py index 252cb9aa..79e2e013 100644 --- a/deltakit-explorer/tests/unit/codes/test_logicals.py +++ b/deltakit-explorer/tests/unit/codes/test_logicals.py @@ -128,6 +128,22 @@ def _assert_logical_basis( null_space_defining_checks: np.ndarray, quotient_space_defining_checks: np.ndarray, ) -> None: + """Assert that returned logicals have the expected CSS logical structure. + + This checks that ``logicals`` is a well-formed binary basis for the + non-redundant rows allowed by ``null_space_defining_checks``, with the + expected number of independent rows once the rows generated by + ``quotient_space_defining_checks`` are treated as redundant. + + Args: + logicals: Candidate logical rows returned by ``css_code_compute_logicals``. + null_space_defining_checks: Checks that define which rows are valid. + quotient_space_defining_checks: Checks whose row space is treated as + redundant when determining the expected logical rows. + + Returns: + None. + """ logicals_is_matrix = logicals.ndim == 2 num_logical_columns = logicals.shape[1] num_logical_rows = logicals.shape[0] From 45201b5e9df4aa74e5edfcfe7e1268aa23157a31 Mon Sep 17 00:00:00 2001 From: Jordan Simbananiye Date: Wed, 15 Jul 2026 15:28:16 +0100 Subject: [PATCH 12/12] test(explorer): use typed ndarray annotations --- .../tests/unit/codes/test_logicals.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/deltakit-explorer/tests/unit/codes/test_logicals.py b/deltakit-explorer/tests/unit/codes/test_logicals.py index 79e2e013..11fa4312 100644 --- a/deltakit-explorer/tests/unit/codes/test_logicals.py +++ b/deltakit-explorer/tests/unit/codes/test_logicals.py @@ -3,6 +3,7 @@ import deltakit_circuit as circuit import deltakit_stim as stim import numpy as np +import numpy.typing as npt import pytest from deltakit_explorer.codes._logicals import ( @@ -38,7 +39,7 @@ def test_paulistring_to_operator(string): # operators_res == operators_ref -def _binary_matrix(rows: np.ndarray) -> np.ndarray: +def _binary_matrix(rows: npt.ArrayLike) -> npt.NDArray[np.uint8]: """Convert binary rows into a dense ``uint8`` array. Args: @@ -50,7 +51,7 @@ def _binary_matrix(rows: np.ndarray) -> np.ndarray: return np.asarray(rows, dtype=np.uint8) -def _empty_binary_matrix(n_cols: int) -> np.ndarray: +def _empty_binary_matrix(n_cols: int) -> npt.NDArray[np.uint8]: """Construct an empty dense ``uint8`` array with a fixed column count. Args: @@ -62,7 +63,7 @@ def _empty_binary_matrix(n_cols: int) -> np.ndarray: return np.zeros((0, n_cols), dtype=np.uint8) -def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: +def _binary_row_space(matrix: npt.NDArray[np.uint8]) -> set[tuple[int, ...]]: """Enumerate the row space of a binary array using GF(2) arithmetic. Args: @@ -93,7 +94,7 @@ def _binary_row_space(matrix: np.ndarray) -> set[tuple[int, ...]]: return vectors -def _binary_null_space(matrix: np.ndarray) -> set[tuple[int, ...]]: +def _binary_null_space(matrix: npt.NDArray[np.uint8]) -> set[tuple[int, ...]]: """Enumerate the null space of a binary array using GF(2) arithmetic. Args: @@ -124,9 +125,9 @@ def _dimension(space: set[tuple[int, ...]]) -> int: def _assert_logical_basis( - logicals: np.ndarray, - null_space_defining_checks: np.ndarray, - quotient_space_defining_checks: np.ndarray, + logicals: npt.NDArray[np.uint8], + null_space_defining_checks: npt.NDArray[np.uint8], + quotient_space_defining_checks: npt.NDArray[np.uint8], ) -> None: """Assert that returned logicals have the expected CSS logical structure. @@ -185,7 +186,7 @@ def _assert_logical_basis( ], ) def test_css_code_compute_logicals_returns_valid_css_logical_bases( - hx: np.ndarray, hz: np.ndarray + hx: npt.NDArray[np.uint8], hz: npt.NDArray[np.uint8] ): assert np.all((hx @ hz.T) % 2 == 0)