This release reworks the DQI performance estimation end to end. The
interference estimator was rewritten and is now exact and fast on every prime
field, the decoder interface was redesigned around syndromes, the minimum
distance computation was made practical for large instances, and a test suite
with 128 tests pinned and then fixed a long list of correctness defects, some
of which changed every non-binary estimate.
Added
compute_expectationindqi.py: a single exact interference estimator for
every prime field. It buckets the decodable errors by syndrome and decodes
the interference partner of each benchmarked error instead of enumerating
error pairs. It replaces the Monte-Carlo pair sampler on GF(2) and the
quadratic double loop on GF(p). On the ternary Golay code atl = 3it
takes 8 ms where the old loop took 54 s, and on GF(2) withm = 24,
l = 3it is exact where the old sampler had an error of up to 0.3 in the
interference matrix.Dqi.estimate_solution_quality(method=...)selects the estimator
explicitly:"auto","analytical"(closed form, raises outside the
perfect-decoding regime),"interference", or"average_v_bound"(the old
cheap GF(2) bound).Dqi.estimate_solution_quality(details=True)returns aQualityEstimate
record with the value, theland method used, the measured decoding
failure rates, and whether the result isguaranteed,exactor
clamped. The record is also kept inDqi.last_estimate.semicircle_law_solution_quality(l=None, w=None)acceptslandw.- Sampled decoding benchmarks (
n_decoding_samples) are allowed on every
field."auto"means 500 samples per error weight; non-binary fields no
longer require exhaustive benchmarks. - Decoders can implement either
decode_syndromeordecode_codeword
(exactly one);AbstractDecoder.error_estimateturns a codeword decoder
into a syndrome decoder.AbstractDecoder._complete_decoding_radius()is
available to custom complete decoders. InformationSetDecodertakesseed(default0) andsearch_size.
SageMath's information-set decoder is randomised and calibrates its search
size by timing, so results depended on the run and on machine load; both
can now be fixed so that the decoder is a function of the syndrome, as the
DQI analysis assumes.SyndromeDecoder.get_syndrome_error_arrayexposes the coset-leader table
as a NumPy array.MaxConstraintSat.to_max_linsattakesequal_size_F_i,merge_strategy
anddefault_decoder_constructorand forwards them toMaxLinSat.OptimalPolynomialIntersection.get_codereturns the DQI codeker(Bᵀ),
so every decoder, including the default one, can be used with it.make_Aand the Fourier coefficients reject the degenerate casesr = 0
andr = pwith aValueErrorinstead of dividing by zero.- Test suite in
tests/: a brute-force oracle for the DQI state, an
independent reference implementation of the estimator, equivalence tests
against the pre-rewrite estimators over nine codes and four prime fields,
and one regression test per fixed defect.tests/bench_dqi.pytimes the
estimator. Run withpython -m pytest tests. pyproject.tomlwith aruffconfiguration; the code base is formatted
and lint-clean.- The Docker image is reproducible: the base image is pinned by digest, the
Arch packages are resolved against the Arch Linux Archive snapshot of
2026-10-02 (SageMath 10.10, GAP 4.16.1, Python 3.14.7, NumPy 2.5.3,
SciPy 1.18.1), and the pip packages are pinned inrequirements.txt
(ldpc 2.4.1, ortools 9.15.6755, simanneal 0.5.0). It also installs
pytest.scripts/update_docker_pins.shmoves the pins to a newer
snapshot. CHANGELOG.md; the README has a citation and a license section.
Changed
Dqi.estimate_solution_quality: every argument afterlis keyword-only.
Therhs_approximationflag was replaced bymethod="average_v_bound",
andn_interference_sampleswas removed because nothing is sampled on the
interference side any more. The defaultlis now the largest degree in
the perfect-decoding regime,d // 2 - 1, capped by the decoder's radius.method="auto"no longer falls back to the average-right-hand-side bound
in the imperfect regime. That bound degenerates tom / 2, the value of
random guessing, on most instances (including the README example); it is
now reachable only on request, and a clamped result warns. On the README
vertex-cover example the default estimate went from 10.0 to about 11.9.- Decoders are benchmarked as a function of the syndrome, not of the
corrupted codeword, which is what the DQI circuit does. The previous
benchmark let a decoder break ties towards the zero codeword and reported
unrealistically high success rates. AbstractDecoder.decodewas replaced bydecode_syndrome/
decode_codeword;get_benchmarkstakes(l, n_errors, n_tries).NearestNeighborDecoderandSyndromeDecoderreport their decoding
radius(d - 1) // 2instead ofNone, so the closed form is used when it
applies. Computing it requires the minimum distance.MaxLinSat.compute_minimum_distanceis much faster on large instances: a
graph-based upper bound and a bound from single-variable constraints are
tried first, and non-binary codes use a sampling-based approximation when
approximate=True.MaxLinSat.add_constraintrecognises scaled and reordered copies of a
constraint as duplicates. Constraints with the same left-hand side up to
variable order and scaling are merged into one canonical row with leading
coefficient one; a constraint without a duplicate keeps its scaling.MaxLinSat.add_constraintraisesTypeErrorfor a weight that is not an
int. Before,Fractionandfloatweights were silently truncated.MaxLinSat.new_varrequires a name. An unnamed variable crashed onstr().IntTerms.scaleandIntTerms.scalar_divraiseTypeErrorinstead of
ValueErrorfor a scalar of the wrong type.MaxLinSatAnnealno longer overridesupdate; progress printing is
disabled through the annealer'supdates = 0setting so that callers can
re-enable it on an instance.- The gadget library file is now called
gadget_library.json. - Python 3.12 or newer is required.
- The README was rewritten: every code block runs as written, the API names
match the code (to_max_linsat,add_boolean_constraint,
get_solution_quality), andMergeStrategy,equal_size_F_i,
add_gadget, the prime-field restriction of the estimator and the test
command are documented.
Fixed
make_Ahad the wrong diagonal for p > 2:(p - 2r)/p · kinstead of
(p - 2r)/√(r(p - r)) · k. Every non-binary semicircle-law value and every
automatically chosenwfor p > 2 was affected; the binary case was
unchanged because both expressions vanish there.- The exact non-binary estimator returned the random-guess value
m·r/p:
the degree-lterm was dropped from every sum and the norm used
|g̃|instead of|g̃|². - All Fourier-coefficient closures captured the last constraint's right-hand
side, so every constraint was treated like the last one. - A
NameErrormade the GF(2) interference path unreachable. semicircle_law_solution_qualityignoredlandw, and the
perfect-decoding gate was oneltoo large for oddd. The condition is
now2l + 2 <= d.- The average-right-hand-side bound indexed the failure rates in reverse
weight order. - For non-binary fields
estimate_solution_qualitysilently ignored
rhs_approximation,n_decoding_samples,n_interference_samplesandw. MergeStrategy.USE_LOOSESTraisedTypeErroron every instance.add_objective(minimize=True)was a no-op, so the README example maximised
the vertex cover;add_boolean_constraintdropped its weight;>=and>
excluded the upper bound.MaxLinSat.add_constraintnever rejected variables from another instance:
the membership test used==, which the variable DSL overloads to build a
constraint, so it was always true. The check now compares by identity.MaxLinSat.compute_B_F_weightschecked the wrong length when removing a
constant offset from a constraint whose right-hand side contains every
field element, and it divided the weight gcd out of the user's constraint
objects in place. The function no longer mutates the instance.- The graph-based minimum-distance bound assumed that every cycle of
two-variable constraints is a linear dependency. That is false over GF(p)
for p > 2, where it returned a distance of 3 for codes of distance 4 and
could admit the closed form outside its regime. The method was rewritten
for binary and non-binary fields. MaxLinSat.get_vandget_random_solution_valueread the cached
right-hand sides directly and crashed on a fresh instance.UnweightedIsingcould not be constructed.IntConstraint.__init__ignored itsrequired_field_orderargument.- The decoder benchmark cache ignored
l, so a second call with a different
lreturned the first result. GeneralizedReedSolomonDecoderdecoded the primal Reed-Solomon code
instead of the dual code that DQI uses, reporting weight-2 errors as
correctly decoded. Guruswami-Sudan now falls back to Berlekamp-Welch when
it cannot be constructed for the requested radius.- Decoding failures (
DecodingError, empty list-decoding results) aborted
the benchmark instead of counting as incorrect decodings, and
compute_benchmarksdivided by zero when no error of the requested weight
exists. - The belief-propagation decoders relied on
ldpc's automatic input
detection; they now pass a syndrome explicitly, built fromBᵀ. SyndromeDecoder.constructor(**kwargs)forwarded parameters its
constructor did not accept, and the benchmark object was not initialised,
which crashed DQI simulation.OrToolsSolver.is_optimal()raisedTypeErrorwhen called before
get_solution(), and then solved twice.BruteForceSolverreturned a tuple where every other solver returns a Sage
vector.MaxLinSatAnneal.movecould pick a change of zero, so a fraction 1/p of
the annealing moves were no-ops.- Debug output in
get_minimum_distancewas removed. - The
np.matrixdeprecation warning emitted by every conversion of a Sage
matrix to NumPy is gone.
Removed
approximate_A_barandDqi._dqi_compute_exact_performance_non_binary,
superseded bycompute_expectation. The pre-rewrite estimators are
preserved intests/legacy_estimators.pyfor the equivalence tests.MaxLinSat.get_decoding_radius; the decoder'sdecoding_radius()is the
single source of truth.AbstractDecoder.decode, see the decoder interface change above.- The
rhs_approximationandn_interference_samplesarguments of
estimate_solution_quality.