Add congruence support to IntegerSet, fixing "multiple of" completeness errors - #2
Add congruence support to IntegerSet, fixing "multiple of" completeness errors#2roed-math wants to merge 18 commits into
Conversation
"Multiple of conductor/level" searches produce {"$mod": [r, m]} query
constraints, which made to_rset raise ValueError in the completeness
checker (a production error, now caught but still logged and flashed on
every such search). Following the suggestion in the issue, IntegerSet
now carries a Congruence (modulus + allowed residues, with Python %
semantics matching psycodict's SQL): query parsing tracks congruences
exactly through $and/$or/$not/$ne, integer_normalize sharpens interval
endpoints to satisfy the congruence, and union/intersection/difference/
is_subset/iteration are congruence-aware. Where a set is not exactly
representable we always over-approximate, so completeness is never
claimed incorrectly; in particular [1, 1000] is not considered a subset
of the multiples of 7.
Verified by new unit tests in lmfdb/tests/test_utils.py (including
results_complete on the queries from the issue and sharp $mod+$lte
cases) and a page regression test in test_ell_curves.py; the URLs from
the issue now return 200 with no completeness error flash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…MFDB#6822) to_rset_cong reports whether the (RealSet, Congruence) pair describes the query exactly, but IntegerSet.__init__ discarded the flag, so an over-approximated union (e.g. branches with different ranges and congruences, like even-in-[0,2] OR odd-in-[10,12]) was later treated as exact: is_subset could certify containment of elements in neither branch, and difference could subtract too much, under-approximating the result. NumberSet and IntegerSet now carry an exact attribute, propagated conservatively through union/intersection/difference/negation/arithmetic (with empty representations upgraded to exact, since an empty over-approximation forces the described set to be empty). is_subset never certifies containment in an inexact right-hand side, and difference never subtracts an inexact set, keeping the (still safe) over-approximation and marking it inexact. bound_under subtracts the exact static interval instead of the derived intersection so it keeps working on inexact inputs. Inexact sets remain safe on the left of is_subset, in bounds and in iteration, so all previous completeness certifications are retained. Verified: new mixed-range/mixed-congruence regression tests (including the reviewer's counterexample) in test_utils.py; lmfdb/tests/test_utils.py 20 passed; lmfdb/tests/test_cmdline_search.py 62 passed; ECQ test_Cond_multiple_search passed; ECQ/CMF/modular-curve multiple-of pages show no completeness-error flash; pyflakes clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed the external review's P1 finding (inexact over-approximations treated as exact): commit 1e4b78c makes NumberSet/IntegerSet store the exactness flag that to_rset_cong already computed, propagates it conservatively through union/intersection/difference/negation/arithmetic, and refuses the two unsound operations — The reviewer's counterexample — (even in [0,2]) OR (odd in [10,12]) — is now inexact: {1} is no longer certified as a subset and difference no longer wrongly removes 1. Mixed-range/mixed-congruence regression tests added. No lost functionality: inexact sets remain usable on the LEFT of is_subset, so every previous certification survives — verified against the original multiple-of-conductor issue queries, the full completeness test suites (test_utils 20, cmdline_search 62), and PR #31's usage patterns simulated directly. The tradeoff is deliberate conservatism: genuine members of inexact unions are no longer certified, which only ever weakens a claim, never falsifies one. |
| CONGRUENCE_CAP = 10000 | ||
|
|
||
|
|
||
| class Congruence: |
There was a problem hiding this comment.
I think this would be better as an extra layer on top of an IntegerSet (so this inherits from IntegerSet).
There was a problem hiding this comment.
In particular, a layer on top of IntegerSet (which has an underlying rset) would allow the easy representation of numbers congruent to a mod n in an interval, which is one of the main use cases.
There was a problem hiding this comment.
And if the interval is short enough, this can collapse into a finite list (solving the problem that you can't represent unions in some cases).
There was a problem hiding this comment.
It also makes your _pair constructions unnecessary, since they can be folded into methods.
There was a problem hiding this comment.
Done in 33d1153. CongruenceSet now inherits from IntegerSet and adds the modulus and residues; IntegerSet(query) returns one when parsing needs it (the way pathlib.Path() returns a PosixPath), so ordinary interval and finite sets keep using the plain IntegerSet. Congruence survives, but only as the value object describing a residue system: it is no longer meant to be a set, and the set-with-a-range is CongruenceSet.
Following the rest of the thread:
Collapse into a finite list. CongruenceSet.collapse() lists the elements when there are at most COLLAPSE_CAP (1000) of them, and union, difference, complement and is_subset use it. So your counterexample, (even in [0,2]) OR (odd in [10,12]), is now the exact set {0, 2, 11} instead of an inexact [0,2] ∪ [10,12]: 1 is no longer in the representation at all, and 2 is certified rather than refused. Beyond that size the union still falls back to one range plus one congruence, which is now marked exact under a sharper condition than before (no integer of the combined range may satisfy one input's congruence while lying only in the other input's range). That condition replaces the old "the two ranges are equal" test, which stopped working once parsing normalized endpoints onto the congruence, and it keeps cases like 0 mod 3 in [0,3000] ∪ 1 mod 3 in [0,3000] exact.
The _pair constructions are gone. Parsing is now parse_set(query, cls), which builds set objects and combines them with the sets' own union, intersection and complement methods; _pair_empty, _pair_intersection, _pair_union, _pair_complement and to_rset_cong are all deleted. IntegerSet._build(rset, cong, exact) is the single constructor (it normalizes the range, picks the class and records exactness), so the exactness invariant has one implementation rather than one for triples and one for sets. to_rset is unchanged for the callers that want a Sage RealSet.
The soundness properties from the previous round are preserved (the mixed-branch case is still a regression test, in both its collapsing and non-collapsing forms), and there is now an exhaustive small-domain test that compares the represented set against a naive evaluation of the query on [-20, 20] for union, intersection, difference, complement and subset certification.
| def is_empty(self): | ||
| return not self.residues | ||
|
|
||
| def intersection(self, other): |
There was a problem hiding this comment.
This doesn't handle the case that other is not a Congruence.
There was a problem hiding this comment.
Fixed in 33d1153:
def __eq__(self, other):
if not isinstance(other, Congruence):
return NotImplemented
return self.modulus == other.modulus and self.residues == other.residuesReturning NotImplemented lets Python try the reflected operation and fall back on identity, instead of the class asserting that it is unequal to something it knows nothing about. The custom __ne__ is removed (Python derives it from __eq__), and __hash__ is kept so that congruences stay usable in sets and dict keys.
test_congruence_comparison covers the NotImplemented return itself, equality and inequality against an unrelated object in both operand orders, and hashing.
Signed-off-by: ahmadalguydi <ahmadalgaidy@hotmail.com>
…rectly Addresses the two open review threads and the sparse-iteration blocker. Architecture (review thread on Congruence): congruence-aware sets are now a layer on top of IntegerSet. CongruenceSet(IntegerSet) carries the modulus and residues; IntegerSet dispatches to it when parsing needs one (as pathlib.Path produces a PosixPath), and bounded congruence sets with at most COLLAPSE_CAP elements collapse to an exact finite IntegerSet, which makes unions of branches with different congruences exact rather than over-approximations. Parsing is now parse_set(query, cls), which builds set objects and combines them with the sets' own union/intersection/complement, so the _pair_* triples are gone and the exactness invariant has a single implementation. to_rset is unchanged for callers that want a Sage RealSet; to_rset_cong is removed. Iteration: CongruenceSet.__iter__ steps from each element to the next via Congruence.iter_up/iter_down rather than walking the ambient interval and filtering, so the cost is proportional to the number of values produced. A user-supplied modulus could previously make a completeness check scan billions of integers between two matches. Bounded, one-sided and two-sided unbounded ranges are all handled without testing nonmembers. Checker guards: PrimeBound, CPrimeBound, Smooth and MaassBound now decline to certify (via IntegerSet.enumerable, backed by an arithmetic cardinality) rather than enumerating an unbounded or enormous query set. IntegerSet.is_subset uses the same bound, which also lets it enumerate sparse sets with a huge span. Comparison: Congruence.__eq__ returns NotImplemented for other types and __ne__ is derived. $mod values follow psycodict's contract: the modulus must be positive (0 and negatives are rejected instead of being given a meaning the database query does not share) and the residue is reduced to r % m. Tests: sparse-iteration regressions (including a structural check that no ambient integer is tested for membership), checker guards, $mod semantics with negative values, comparison protocol, and an exhaustive small-domain comparison against a naive evaluator for union, intersection, difference, complement and subset certification.
…ai/t02-ecq-conductor-mult
|
Second review round addressed in 33d1153 (plus a merge of Merge blocker: iteration is now proportional to matches, not interval width
All three interval shapes are handled without touching nonmembers: bounded (increasing), bounded above only (decreasing, as before), and unbounded in both directions (a fair alternation between nonnegative and negative values, matching what iterating S = IntegerSet({"$mod": [0, 10**12], "$gte": 0, "$lte": 10**12})
it = iter(S)
next(it) # 0
next(it) # 10**12, previously ~10**12 candidates later
next(it) # StopIterationThe regression tests are structural rather than timing based: as well as checking the values, Defensive checker guards
The other consumers were audited and left alone because their inputs are already small by construction:
|
…ation
NFBound._one_n iterates over the discriminants in a range via
IntegerSet.stickelberger, relying on clear_S to reject one and break out. For
a congruence-free range that happens within a few candidates, but a congruence
set can skip arbitrarily many elements in a row without producing a candidate
at all: every multiple of 10**6 fails Stickelberger's condition at 4, so
results_complete("nf_fields", {"degree": 2,
"disc_abs": {"$mod": [0, 10**6], "$gte": 1}}, db)
never returned. Only run the loop when the range is enumerable, which leaves
congruence-free queries alone (they were already breaking out immediately) and
still falls through to the Minkowski bound. Regression tests added for both
the unbounded case and a bounded congruence range that is certified.
|
Follow-up in 47e923a: one more non-terminating iteration, found while stress testing congruence queries against the other tables.
results_complete("nf_fields", {"degree": 2, "disc_abs": {"$mod": [0, 10**6], "$gte": 1}}, db)never returned (with direct iteration it spins in the generator; with the old filtering iterator it would have been just as stuck, one modulus further down). It now returns Extra local validation on top of what I listed above, all against devmirror:
The one failure is CI on this repo is currently backed up (every branch's run has been queued for the last hour), so the CI verdict will land later than this comment. |
|
GPT signed off. |
PrimeBound.__call__ verified that the queried values were finite sets of
primes, but never checked them against the bound it was constructed with.
So the registered ec_curvedata checker
("conductor", PrimeBound(300000000), "elliptic curves with prime conductor at most 300 million")
certified any prime conductor as complete, however large:
sage: results_complete("ec_curvedata", {"conductor": 1000000007}, db)
(True, 'elliptic curves with prime conductor at most 300 million', None)
Delegate the range check to Bound.__call__, as the sibling CPrimeBound
already does, and add regression tests for a prime past the bound.
The bound is checked before primality so that a large range is rejected
without iterating over it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Check the bound in PrimeBound
…nents-7038 fix: show exponent one for cusp orbit sizes
Set random seed before generating the code snippet log files
…r-mult # Conflicts: # lmfdb/tests/test_utils.py # lmfdb/utils/completeness.py
|
Superseded by LMFDB#7139, opened upstream from this same branch (with |
"Multiple of conductor/level" searches (elliptic curves over Q, classical modular forms, modular
curves, mod-ell Galois representations) produce
{"$mod": [r, m]}query constraints, which madeto_rsetraiseValueError: Unsupported key $modin the completeness checker; since LMFDB#6879 theerror is caught but every such search still logs it and flashes "There was an error in the
completeness checking code". This adds congruence support to the checker's set machinery.
Representation.
CongruenceSetis a layer on top ofIntegerSet: it inherits from it and addsa modulus and a set of allowed residues.
IntegerSet(query)returns one when parsing needs it (theway
pathlib.Path()returns aPosixPath), so ordinary interval and finite sets keep using theplain
IntegerSet. A bounded congruence set with few enough elements collapses into an exact finiteIntegerSet, which makes unions of branches with different congruences exact instead ofover-approximations.
Congruenceremains only as the value object describing a residue system.Parsing.
parse_set(query, cls)builds set objects and combines them with the sets' ownunion,intersectionandcomplement, tracking congruences exactly through$and,$or,$notand$nevia the CRT. There is a single implementation of the set algebra and of theexactness invariant.
to_rsetstill returns a SageRealSetfor the callers that want one, andinteger_normalizesharpens interval endpoints onto the congruence.Soundness. Every set records whether it describes the query exactly; where a set is not exactly
representable the code over-approximates and marks it inexact, and
is_subsetnever certifiescontainment in an inexact set while
differencenever subtracts one. So completeness is neverclaimed incorrectly: an interval like [1, 1000] is not considered a subset of the multiples of 7
(the pathology that blocked LMFDB#6853), while bounded searches such as conductor a multiple of 7 and at
most 400000 are correctly certified complete.
Iteration. Iterating a congruence set steps from each element to the next rather than walking
the ambient interval and filtering, so the cost is proportional to the number of values produced.
A user-supplied modulus could otherwise make a completeness check scan billions of integers between
two matches. Bounded, one-sided and two-sided unbounded ranges are all handled without testing
nonmembers, and the checkers that consume every element (
PrimeBound,CPrimeBound,Smooth,MaassBound) decline to certify rather than enumerating an unbounded or enormous query set.$modsemantics follow psycodict's query language: the modulus must be positive and the residueis reduced to
r % m, so negative database values match by their nonnegative residue.Unit tests cover the congruence and set behavior, sparse iteration (including a structural check
that no ambient integer is tested for membership), the checker guards, the comparison protocol, an
exhaustive small-domain comparison against a naive evaluation of the query, and the queries from the
issue; a page regression test checks that multiple-of-conductor searches no longer flash the error.
Addresses LMFDB#6822.
🤖 Generated with Claude Code