@@ -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