Skip to content

Commit 2cf1082

Browse files
roed314claude
andcommitted
Mention ramified prime count in number field completeness message (LMFDB#7020)
The completeness bounds in NFBound.clear_S keyed on num_ram (_nSp, _nSGp) were displayed as if completeness held for all fields unramified outside S, omitting the essential bound on the number of ramified primes. Extend the reason tuples with an nram slot (recorded only when nram < len(S), so all other messages are unchanged) and display it as 'at most k ramified prime(s)'. Verified against devmirror: the query from the issue now reports 'number fields with degree 5, unramified outside {2,3,5,7,11,13}, at most 1 ramified prime', the same search without num_ram remains incomplete, and lmfdb/tests/test_utils.py (including a new regression pair) passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5ef81bd commit 2cf1082

2 files changed

Lines changed: 38 additions & 23 deletions

File tree

lmfdb/tests/test_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ def test_complete(self):
342342
("nf_fields", {'degree': 5, 'galois_label': '5T4', 'disc_abs': 920627786839041}, "number fields with degree 5, Galois group 5T(1,2,4), unramified outside {3,1201}"),
343343
("nf_fields", {'degree': 5, 'galois_label': '5T4', 'gal_is_abelian': True, 'disc_abs': 920627786839041}, "number fields with incompatible conditions: Galois group"),
344344
("nf_fields", {'degree': 5, 'galois_label': '5T4', 'disc_rad': 1254}, "number fields with degree 5, Galois group 5T(1,2,4), unramified outside {2,3,11,19}"),
345+
("nf_fields", {'degree': 5, 'num_ram': 1, 'ramps': {'$containedin': [2, 3, 5, 7, 11, 13]}}, "number fields with degree 5, unramified outside {2,3,5,7,11,13}, at most 1 ramified prime"),
345346
("nf_fields", {'degree': 8, 'galois_label': '8T25', 'rd': {'$gte': 1, '$lte': 100}}, "number fields with degree 8, Galois group 8T(25,36), Galois root discriminant at most 200"),
346347
("nf_fields", {'degree': 2, 'r2': 0, 'regulator': {'$gte': 0, '$lte': 7}}, "number fields with degree 2, signature [2,0], regulator less than 7.25"),
347348
("nf_fields", {'degree': 2, 'r2': 1, 'regulator': {'$gte': 0, '$lte': 0.999}}, "number fields with degree 2, signature [0,1], regulator less than 1.00"),
@@ -394,6 +395,7 @@ def test_complete(self):
394395
("bmf_forms", {'field_disc': {'$gte': -120, '$lte': -3}, 'level_norm': {'$gte': 1, '$lte': 4000}}),
395396
("ec_nfcurves", {'field_label': '7.7.20134393.1', 'conductor_norm': {'$gte': 1, '$lte': 50}}),
396397
("nf_fields", {'degree': 6, 'disc_abs': {'$gte': 1, '$lte': 20000000}}),
398+
("nf_fields", {'degree': 5, 'ramps': {'$containedin': [2, 3, 5, 7, 11, 13]}}),
397399
("nf_fields", {'degree': 2, 'r2': 1, 'regulator': 1}),
398400
("nf_fields", {'degree': 4, 'r2': 2, 'regulator': {'$gte': 0.962, '$lte': 0.963}}), # Infinitely many degree 4 CM fields with regulator 0.962423650119
399401
("nf_fields", {'degree': 6, 'r2': 3, 'regulator': {'$gte': 2.101, '$lte': 2.102}}), # Infinitely many degree 6 CM fields with regulator 2.10181872849

lmfdb/utils/completeness.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,16 +1991,18 @@ def display_reason(self, reasons):
19911991
INPUT:
19921992
19931993
- ``reasons`` -- a set of reasons, which are either a string or
1994-
a tuple of the form ``(n, r2, galt, ramps, D_bound, grd_bound, reg)``,
1994+
a tuple of the form ``(n, r2, galt, ramps, nram, D_bound, grd_bound, reg)``,
19951995
where entries are None if they are not applicable.
19961996
"""
19971997
# Current tuples created:
1998-
# (n, r2, None, None, M, None, None)
1999-
# (n, r2, Gs, None, M, None, None)
2000-
# (n, None, Gs, None, None, M, None)
2001-
# (n, None, None, S, None, None, None)
2002-
# (n, None, Gs, S, None, None, None)
2003-
# (n, r2, None, None, None, None, R)
1998+
# (n, r2, None, None, None, M, None, None)
1999+
# (n, r2, Gs, None, None, M, None, None)
2000+
# (n, None, Gs, None, None, None, M, None)
2001+
# (n, None, None, S, k, None, None, None)
2002+
# (n, None, Gs, S, k, None, None, None)
2003+
# (n, r2, None, None, None, None, None, R)
2004+
# Here k (a bound on the number of ramified primes) is None unless
2005+
# completeness depends on it (i.e. unless k < len(S)).
20042006
# We group by None pattern
20052007
def describe(tups):
20062008
ans = []
@@ -2030,19 +2032,26 @@ def describe(tups):
20302032
else:
20312033
ans.append(f"unramified outside {','.join()}")
20322034
if tups[0][4] is not None:
2033-
Dbounds = [str(tup[4]) for tup in tups]
2035+
nrams = [str(tup[4]) for tup in tups]
2036+
if len(set(nrams)) == 1:
2037+
plural = "" if tups[0][4] == 1 else "s"
2038+
ans.append(f"at most {nrams[0]} ramified prime{plural}")
2039+
else:
2040+
ans.append(f"at most {','.join(nrams)} ramified primes")
2041+
if tups[0][5] is not None:
2042+
Dbounds = [str(tup[5]) for tup in tups]
20342043
if len(set(Dbounds)) == 1:
20352044
ans.append(f"absolute discriminant at most {Dbounds[0]}")
20362045
else:
20372046
ans.append(f"absolute discriminant at most {','.join(Dbounds)}")
2038-
if tups[0][5] is not None:
2039-
grd = [str(tup[5]) for tup in tups]
2047+
if tups[0][6] is not None:
2048+
grd = [str(tup[6]) for tup in tups]
20402049
if len(set(grd)) == 1:
20412050
ans.append(f"Galois root discriminant at most {grd[0]}")
20422051
else:
20432052
ans.append(f"Galois root discriminant at most {','.join(grd)}")
2044-
if tups[0][6] is not None:
2045-
reg_bounds = [RR(tup[6]) for tup in tups]
2053+
if tups[0][7] is not None:
2054+
reg_bounds = [RR(tup[7]) for tup in tups]
20462055
if len(set(reg_bounds)) == 1:
20472056
ans.append(f"regulator less than {float(reg_bounds[0]):.2f}")
20482057
else:
@@ -2058,7 +2067,7 @@ def describe(tups):
20582067
if not reason.startswith("incompatible conditions"):
20592068
non_incomp.append(reason)
20602069
else:
2061-
by_pattern[tuple(i for i in range(7) if reason[i] is None)].append(reason)
2070+
by_pattern[tuple(i for i in range(8) if reason[i] is None)].append(reason)
20622071
if len(non_incomp) + len(by_pattern) > 0:
20632072
strings = non_incomp
20642073
return "number fields with " + "; ".join(strings + [describe(V) for V in by_pattern.values()])
@@ -2074,7 +2083,7 @@ def clear_signatures(self, n, D, r2opts, reasons):
20742083
M = self._maxD[n][r2]
20752084
if D.bounded(M):
20762085
r2opts.remove(r2)
2077-
reasons.add((n, r2, None, None, M, None, None))
2086+
reasons.add((n, r2, None, None, None, M, None, None))
20782087
m = min(m, M)
20792088
if m is not infinity:
20802089
D = D.intersection(bottom(m + 1))
@@ -2096,7 +2105,7 @@ def clear_regulator(self, n, R, r2opts, reasons):
20962105
M = maxReg - 0.00001 # Completeness only guaranteed if R *strictly less* than M
20972106
if R.bounded(M):
20982107
r2opts.remove(r2)
2099-
reasons.add((n, r2, None, None, None, None, maxReg))
2108+
reasons.add((n, r2, None, None, None, None, None, maxReg))
21002109
m = min(m, M)
21012110
if m is not infinity:
21022111
R = R.intersection(bottom(m))
@@ -2115,7 +2124,7 @@ def clear_r2G(self, n, D, r2opts, galt, reasons):
21152124
if set(r2G[t]) == set(r2opts):
21162125
galt.remove(t)
21172126
for r2, Gs, M in r2G[t].values():
2118-
reasons.add((n, r2, Gs, None, M, None, None))
2127+
reasons.add((n, r2, Gs, None, None, M, None, None))
21192128

21202129
def clear_grd(self, n, grd, galt, reasons):
21212130
"""
@@ -2129,7 +2138,7 @@ def clear_grd(self, n, grd, galt, reasons):
21292138
for t in galt.intersection(by_t):
21302139
galt.remove(t)
21312140
Gs, M = by_t[t]
2132-
reasons.add((n, None, Gs, None, None, M, None))
2141+
reasons.add((n, None, Gs, None, None, None, M, None))
21332142

21342143
def clear_S(self, n, S, nram, galt, reasons, update_galt=True):
21352144
"""
@@ -2139,14 +2148,18 @@ def clear_S(self, n, S, nram, galt, reasons, update_galt=True):
21392148
galt = set(galt)
21402149
if nram is None:
21412150
nram = len(S)
2151+
# The completeness bounds keyed on nram below only apply because the number of
2152+
# ramified primes is bounded; when nram < len(S) this constraint does not follow
2153+
# from being unramified outside S, so we record it for display in the message.
2154+
shown_nram = nram if nram < len(S) else None
21422155

21432156
if S in self._nS.get(n, {}):
2144-
reasons.add((n, None, None, S, None, None, None))
2157+
reasons.add((n, None, None, S, None, None, None, None))
21452158
return True
21462159

21472160
M = self._nSp.get(n, {}).get(nram)
21482161
if M is not None and all(p < M for p in S):
2149-
reasons.add((n, None, None, S, None, None, None))
2162+
reasons.add((n, None, None, S, shown_nram, None, None, None))
21502163
return True
21512164

21522165
if galt is None:
@@ -2156,7 +2169,7 @@ def clear_S(self, n, S, nram, galt, reasons, update_galt=True):
21562169
if all(p < M for p in S):
21572170
I = galt.intersection(Gs)
21582171
if I:
2159-
reasons.add((n, None, Gs, S, None, None, None))
2172+
reasons.add((n, None, Gs, S, shown_nram, None, None, None))
21602173
galt.difference_update(I)
21612174
if not galt:
21622175
return True
@@ -2166,7 +2179,7 @@ def clear_S(self, n, S, nram, galt, reasons, update_galt=True):
21662179
if min(S) == p0 and max(S) < M:
21672180
I = galt.intersection(Gs)
21682181
if I:
2169-
reasons.add((n, None, Gs, S, None, None, None))
2182+
reasons.add((n, None, Gs, S, None, None, None, None))
21702183
galt.difference_update(I)
21712184
if not galt:
21722185
return True
@@ -2176,7 +2189,7 @@ def clear_S(self, n, S, nram, galt, reasons, update_galt=True):
21762189
if SS.issubset(T):
21772190
I = galt.intersection(Gs)
21782191
if I:
2179-
reasons.add((n, None, Gs, S, None, None, None))
2192+
reasons.add((n, None, Gs, S, None, None, None, None))
21802193
galt.difference_update(I)
21812194
if not galt:
21822195
return True
@@ -2504,7 +2517,7 @@ def __call__(self, db, query):
25042517

25052518
# We collect reasons that have contributed to completeness
25062519
# These have the following format:
2507-
# (n, r2, galt, ramps, D_bound, grd_bound, Reg)
2520+
# (n, r2, galt, ramps, nram, D_bound, grd_bound, Reg)
25082521
# Where entries can be None if they are not applicable
25092522
reasons = set()
25102523
# First check completeness without splitting on degree

0 commit comments

Comments
 (0)