66
77from flask import abort , render_template , request , url_for , redirect , make_response
88from sage .all import (
9- PolynomialRing , ZZ , QQ , RR , latex , cached_function , Integers , euler_phi , is_prime )
9+ PolynomialRing , ZZ , QQ , RR , GF , gcd , latex , cached_function , Integers , euler_phi , is_prime )
1010from sage .plot .all import line , points , text , Graphics , polygon
1111
1212from lmfdb import db
@@ -141,8 +141,9 @@ def local_field_data(label):
141141 else :
142142 return "Invalid label %s" % label
143143 nicename = ''
144- if f ['n' ] < 3 :
145- nicename = ' = ' + prettyname (f )
144+ nick = field_nickname (f )
145+ if nick is not None :
146+ nicename = ' = ' + nick
146147 ans = '$p$-adic field %s%s<br><br>' % (label , nicename )
147148 ans += r'Extension of $\Q_{%s}$ defined by %s<br>' % (str (f ['p' ]),web_latex (coeff_to_poly (f ['coeffs' ])))
148149 gn = f ['n' ]
@@ -295,10 +296,11 @@ def ctx_local_fields():
295296
296297# Utilities for subfield display
297298def format_lfield (label , p ):
299+ cols = ["n" , "p" , "e" , "f" , "rf" , "eisen" , "unram" , "old_label" , "new_label" ]
298300 if OLD_LF_RE .fullmatch (label ):
299- data = db .lf_fields .lucky ({"old_label" : label }, [ "n" , "p" , "rf" , "old_label" , "new_label" ] )
301+ data = db .lf_fields .lucky ({"old_label" : label }, cols )
300302 else :
301- data = db .lf_fields .lucky ({"new_label" : label }, [ "n" , "p" , "rf" , "old_label" , "new_label" ] )
303+ data = db .lf_fields .lucky ({"new_label" : label }, cols )
302304 return lf_display_knowl (label , name = prettyname (data ))
303305
304306
@@ -591,13 +593,16 @@ def display(self, rec):
591593 return r"$100\%$"
592594 return fr"${ 100 * x :.2f} \%$"
593595
594- def pretty_link (label , p , n , rf ):
595- if OLD_LF_RE .fullmatch (label ):
596- name = {"old_label" : label }
597- else :
598- name = {"new_label" : label }
599- name .update ({"p" : p , "n" : n , "rf" : rf })
600- name = prettyname (name )
596+ def pretty_link (label , p , n , rf , data = None ):
597+ if data is None :
598+ # Minimal record: enough for the Q_p(sqrt(d)) nickname of quadratics, and a
599+ # label fallback otherwise.
600+ if OLD_LF_RE .fullmatch (label ):
601+ data = {"old_label" : label }
602+ else :
603+ data = {"new_label" : label }
604+ data .update ({"p" : p , "n" : n , "rf" : rf })
605+ name = prettyname (data )
601606 return f'<a href="{ url_for_label (label )} ">{ name } </a>'
602607
603608families_columns = SearchColumns ([
@@ -616,9 +621,9 @@ def pretty_link(label, p, n, rf):
616621 MathCol ("c0" , "lf.discriminant_exponent" , "$c_0$" , short_title = "base disc. exponent" , default = False , contingent = lambda info : "relative" in info ),
617622 MathCol ("c_absolute" , "lf.discriminant_exponent" , r"$c_{\mathrm{abs}}$" , short_title = "abs. disc. exponent" , default = False , contingent = lambda info : "relative" in info ),
618623 MultiProcessedCol ("base_field" , "lf.family_base" , "Base" ,
619- ["base" , "p" , "n0" , "rf0" ],
624+ ["base" , "p" , "n0" , "rf0" , "base_data" ],
620625 pretty_link ,
621- apply_download = lambda base , p , n0 , rf0 : base ,
626+ apply_download = lambda base , p , n0 , rf0 , base_data : base ,
622627 contingent = lambda info : "relative" in info ),
623628 RationalListCol ("visible" , "lf.slopes" , "Abs. Artin slopes" ,
624629 show_slopes2 , default = False , short_title = "abs. Artin slopes" ),
@@ -648,14 +653,20 @@ def lf_postprocess(res, info, query):
648653 return res
649654
650655def families_postprocess (res , info , query ):
651- quads = list (set (rec ["base" ] for rec in res if rec ["n0" ] == 2 ))
652- if quads :
653- rflook = {rec ["new_label" ]: rec ["rf" ] for rec in db .lf_fields .search ({"new_label" :{"$in" :quads }}, ["new_label" , "rf" ])}
656+ # Fetch the base field of each family so its "Base" column shows a nickname.
657+ bases = list ({rec ["base" ] for rec in res })
658+ base_lookup = {}
659+ if bases :
660+ cols = ["new_label" , "old_label" , "p" , "n" , "e" , "f" , "rf" , "eisen" , "unram" ]
661+ base_lookup = {rec ["new_label" ]: rec
662+ for rec in db .lf_fields .search ({"new_label" : {"$in" : bases }}, cols )}
654663 for rec in res :
664+ bdata = base_lookup .get (rec ["base" ])
665+ rec ["base_data" ] = bdata
655666 if rec ["n0" ] == 1 :
656667 rec ["rf0" ] = [1 , 0 ]
657- elif rec [ "n0" ] == 2 :
658- rec ["rf0" ] = rflook [ rec [ "base" ]]
668+ elif bdata is not None :
669+ rec ["rf0" ] = bdata . get ( "rf" )
659670 else :
660671 rec ["rf0" ] = None
661672 return res
@@ -1195,12 +1206,117 @@ def render_field_webpage(args):
11951206 KNOWL_ID = "lf.%s" % label , # TODO: BROKEN
11961207 )
11971208
1209+ def _field_label (ent ):
1210+ return ent .get ('new_label' ) or ent .get ('old_label' )
1211+
1212+ @cached_function
1213+ def _lf_residue_field (p , f , unram ):
1214+ # Fq together with tbar, the image in Fq of a root of the (Conway) polynomial
1215+ # `unram`. tbar is a multiplicative generator of Fq^* (Conway polynomials are
1216+ # primitive), so its Teichmuller lift is our chosen zeta_{p^f-1}.
1217+ modpoly = PolynomialRing (GF (p ), 't' )(unram )
1218+ if f == 1 :
1219+ Fp = GF (p )
1220+ return Fp , - Fp (modpoly [0 ]) # root of the monic linear t + c is -c
1221+ Fq = GF (p ** f , 'tt' , modulus = modpoly )
1222+ return Fq , Fq .gen ()
1223+
1224+ def _frob_min (k , p , g ):
1225+ # Smallest element of the Frobenius orbit {k*p^j mod g}: k and p*k index
1226+ # Q_p-isomorphic tame fields (the Frobenius twist zeta -> zeta^p of U/Q_p).
1227+ orbit = set ()
1228+ x = k % g
1229+ while x not in orbit :
1230+ orbit .add (x )
1231+ x = (x * p ) % g
1232+ return min (orbit )
1233+
1234+ def _tame_nickname (p , e , f , eisen , unram ):
1235+ # Nickname for a tamely and genuinely ramified field (e > 1, p does not divide e).
1236+ # Such a field is U(pi) with pi^e = zeta^k*p up to an e-th power, U = Q_p(zeta_m),
1237+ # zeta = zeta_m the Teichmuller lift of tbar, m = p^f-1. From the Eisenstein
1238+ # polynomial, pi^e = -a0*(1+M) with M in the maximal ideal, and 1+M is an e-th
1239+ # power (tame), so zeta^k*p ~ -a0 = p*w gives zeta^k ~ w mod (K^*)^e, i.e.
1240+ # tbar^k = wbar mod (Fq^*)^g with g = gcd(e, m).
1241+ try :
1242+ Fq , tbar = _lf_residue_field (p , f , unram )
1243+ m = p ** f - 1
1244+ g = gcd (e , m )
1245+ Ptx = PolynomialRing (PolynomialRing (ZZ , 't' ), 'x' )
1246+ a0 = Ptx (str (eisen ))[0 ] # constant term, an element of Z[t] = Z_q
1247+ wbar = Fq (0 )
1248+ for i , c in enumerate (a0 .list ()):
1249+ c = ZZ (c )
1250+ if c % p != 0 : # a0 should have p-adic valuation 1 (Eisenstein)
1251+ return None
1252+ wbar += Fq (- (c // p )) * tbar ** i
1253+ if wbar == 0 :
1254+ return None
1255+ if g == 1 :
1256+ k = 0
1257+ else :
1258+ exp = m // g
1259+ target = wbar ** exp
1260+ base = tbar ** exp
1261+ cur = Fq (1 )
1262+ k = None
1263+ for kk in range (g ):
1264+ if cur == target :
1265+ k = kk
1266+ break
1267+ cur *= base
1268+ if k is None :
1269+ return None
1270+ k = _frob_min (k , p , g )
1271+ except (TypeError , ValueError , ZeroDivisionError ):
1272+ return None
1273+ Qp = r'\Q_{%s}' % p
1274+ root = r'\sqrt' if e == 2 else r'\sqrt[%s]' % e
1275+ if f == 1 :
1276+ # zeta_{p-1} lies in Q_p; zeta^k is the Teichmuller lift of tbar^k, and any
1277+ # integer r = tbar^k mod p differs from it by an e-th power, so use radicand p*r.
1278+ r = ZZ (tbar ** k )
1279+ if r == 1 :
1280+ rad = str (p )
1281+ elif r == p - 1 :
1282+ rad = '-' + str (p )
1283+ else :
1284+ rad = r'%s \cdot %s' % (p , r )
1285+ return r'$%s(%s{%s})$' % (Qp , root , rad )
1286+ if k == 0 :
1287+ rad = str (p )
1288+ elif 2 * k == m :
1289+ rad = '-' + str (p ) # zeta_m^{m/2} = -1
1290+ else :
1291+ zpow = r'\zeta_{%s}' % m if k == 1 else r'\zeta_{%s}^{%s}' % (m , k )
1292+ rad = r'%s \cdot %s' % (zpow , p )
1293+ return r'$%s(\zeta_{%s}, %s{%s})$' % (Qp , m , root , rad )
1294+
1295+ def field_nickname (ent ):
1296+ # A human-readable name for a p-adic field, or None if we have no nice one.
1297+ # Quadratics keep the existing Q_p(sqrt(d)) form; unramified extensions become
1298+ # Q_p(zeta_{p^f-1}); tame extensions Q_p(zeta_{p^f-1}, root(zeta^k p)); wildly
1299+ # ramified fields have no nickname (fall back to the label).
1300+ p , n = ent .get ('p' ), ent .get ('n' )
1301+ if p is None or n is None :
1302+ return None
1303+ if n <= 2 :
1304+ rf = ent .get ('rf' )
1305+ return printquad (rf , p ) if rf is not None else None
1306+ e , f = ent .get ('e' ), ent .get ('f' )
1307+ if e is None or f is None :
1308+ return None
1309+ if e % p == 0 :
1310+ return None # wildly ramified: no nice nickname
1311+ if e == 1 :
1312+ return r'$\Q_{%s}(\zeta_{%s})$' % (p , p ** f - 1 ) # unramified
1313+ eisen , unram = ent .get ('eisen' ), ent .get ('unram' )
1314+ if eisen is None or unram is None :
1315+ return None
1316+ return _tame_nickname (p , e , f , eisen , unram )
1317+
11981318def prettyname (ent ):
1199- if ent ['n' ] <= 2 :
1200- return printquad (ent ['rf' ], ent ['p' ])
1201- if ent .get ('new_label' ):
1202- return ent ['new_label' ]
1203- return ent ['old_label' ]
1319+ return field_nickname (ent ) or _field_label (ent )
12041320
12051321@cached_function
12061322def getu (p ):
0 commit comments