Repository navigation
Fix exact-distance bugs, report a missing distance with one sentinel, and merge duplicated coset kernels - #841
Merged
Merged
Conversation
When the vectorized block of a brute-force distance search held no logical operator, the search compared its starting value, the number of columns (or 2n for symplectic rows), with the cutoff, and could return it without weighing any operator. With a large cutoff, QuditCode(SurfaceCode(5).matrix) gave 50. The search now starts at one more than any weight, and compares only weights it has computed with the cutoff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
QDistRnd returns a bound at or below its argument mindist, which qLDPC sets to the cutoff, with a negative sign to mark an early stop. qLDPC returned and cached that negative value as a distance bound. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A code or circuit with no nontrivial logical operator has no distance. Code objects reported np.nan, the public raw distance functions n + 1 (capped at max_weight + 1), and logical counts the real-looking weight n. All of them now report NO_DISTANCE, the only member of the new public enum NoDistance. It is a float nan, so it prints, compares, and propagates as nan did, and results that can be NO_DISTANCE are annotated int | Literal[NoDistance.NAN], which type checkers narrow to int once the sentinel is ruled out. Inside the searches, a problem without nontrivial words still counts as n + 1, so that it never lowers the minimum over problems. CSSLogicalCounts compares its fields as a tuple, so that two counts with weight NO_DISTANCE are equal; dataclasses compare fields one by one from Python 3.13. CSSCode.get_distance_bound_with_decoder now returns NO_DISTANCE for a code without logical qudits, instead of drawing random nonzero logical coefficients from an empty set forever. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- get_distance_brouwer_zimmermann_many documents its arguments and result, and needs a problem or an upper bound, instead of failing in min([]). A comment in get_distance_connected_support claimed that both functions treat problems without witnesses alike. - as_binary_matrix names the rows in its errors, which named the generators even for logical operators and stabilizers, and get_nested_code_basis says how callers must treat a trivial quotient. - get_distance_classical states the early exit at cutoff in its result, and get_distance_brouwer_zimmermann no longer calls its result exact. - brouwer_zimmermann.md no longer describes the removed shortcut to exhaustive enumeration, or counts that scan with affine_search; its worked example counts the step at which qLDPC stops within layer four (135,710 evaluations, not 338,250); it cites an existing test and the missing exit of get_overlapping_family. - The README and _tuning say that a certificate bounds the words neither visited nor skipped, and which word an early exit returns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`scan_coset_layer_by_tails_with_tables_below_best` differed from `scan_coset_layer_by_tails_with_tables` only in skipping completions whose bound reaches the best weight so far rather than a skip bound. The merged scan skips at the lesser of the two. A coset query passes a skip bound of at most its cutoff plus one, and its scans return once the best weight is at most the cutoff, so it still skips at the skip bound. A search for minimum weights passes the skip bound of its maximum weight, which its incumbent never exceeds, so it still skips at the best weight. The one-set scan of the distance search passes its best weight as its skip bound, so the `threshold` flag of `_scan` goes away. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Five pairs of compiled functions of `coset_leader_search.py` repeated each other's control flow, including the stopping step, for searches of whole words and searches by tails. Each pair is now one function that takes the fields of the record of tails of its route, or None, and numba compiles it separately for each, leaving out the branches that the type of the argument rules out. The searches keep their order of words, their bounds, and their representatives. `_keep_lighter` goes, since the merged search weighs the offsets of both kinds of route with `_get_weight`. Python passes the fields as a plain tuple: numba types a named tuple argument at each call about half a microsecond more slowly, which cheap queries on routes by tails would notice. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`CosetLeaderSearch` built its checks with `qldpc.math.symplectic_conjugate` and a parity, through an import that shadowed the standard `math` module, while the same file swaps the X and Z halves of checks elsewhere with `np.roll`. For binary stabilizers both give the same uint8 matrix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The coset searches, the coset routes, and `Certificate.plan` called `plan_steps` with empty placeholder arrays only to get its stopping step. The binary search that finds that step is now `information_sets.find_stopping_step`, which they call directly, and which `plan_steps` calls too. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The stage returns early unless the family has fewer full sets than `max_sets`, so the number of open sets is always at least one, and `max(max_sets - num_full, 1)` never raised it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`get_overlapping_family_policy` returned an unnamed 5-tuple that every caller splatted into `get_overlapping_family`, and `_select_basis_family` of the counts declared its five parameters only to forward them. The policy is now an `OverlappingFamilyPolicy` named tuple, which both take whole; numba compiles it, generator included, when they are compiled on request. The tuning test records the tuning that reaches `get_overlapping_family_policy`, which reads the attempts per set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
An architecture, correctness, and code-health review of
qldpc.codes.distanceturned up three bugs, three inconsistent conventions for "no distance", several false statements in docstrings and the Brouwer–Zimmermann guide, and about 800 lines of duplicated compiled coset code. This PR fixes those within the current module layout, and leaves the layout itself unchanged.Changes
cutoffbefore weighing any logical operator, so a large cutoff could return an unobserved weight. For example,QuditCode(SurfaceCode(5).matrix).get_distance_exact(method="brute_force", cutoff=50)gave 50.mindist(ourcutoff) with a negative sign, which qLDPC returned and cached as a distance bound.CSSCode.get_distance_bound_with_decoderlooped forever on codes without logical qubits.nan, the raw distance functions returnedn + 1, and counts reported the weightn. All of them now returnNO_DISTANCE, the only member of a new public float enumNoDistance. It behaves exactly likenanat run time, including how it prints, and results are annotatedint | Literal[NoDistance.NAN], so type checkers narrow them tointafterdistance is NO_DISTANCEis ruled out. Inside the searches,n + 1stays as the value that acts like +∞.CSSLogicalCountsgets an explicit__eq__and__hash__, because Python 3.13 compares dataclass fields one by one, andNO_DISTANCEwould otherwise make equal counts unequal. This changes return values without a deprecation period, at the maintainer's request.get_distance_brouwer_zimmermann_manydocuments its contract and rejects calls with neither a problem nor an upper bound.as_binary_matrixnames its rows in its shape errors.brouwer_zimmermann.mdno longer describes a removed enumeration shortcut, or says that counts use the distance scan. Its worked example counts the step at which qLDPC actually stops.tails.find_stopping_stepreplaces calls ofplan_stepswith placeholder arrays.OverlappingFamilyPolicyreplaces an unnamed 5-tuple.qldpc.mathimport that shadowedmathare removed.Validation
main, they give identical representatives, lower bounds, batch indices, and minimum weights on 568,854 coset answers. The comparison covered both methods, both weightings, and forced routes by tails and by tables. Independent exhaustive enumeration agreed as well.main: about +1% with default tuning, at most +4.7% on cheap queries forced onto routes by tails, and about 6% less first-call compile time.Notes
scan_coset_layerwithscan_coset_layer_by_css_weightwas deliberately left out. They are already the two constant-flag entry points of one body, and merging them would need a runtime flag or a fragileNonemask.get_distance_classicalandget_distance_quantumstay public for now, returningNO_DISTANCE. A later review of the package's public API may change that.