%s ' % (url_for_label(l),l)
+ ans += "Label Polynomial $e$ $f$ $c$ $G$ Artin slopes"
+ if all(OLD_LF_RE.fullmatch(lab) for lab in labs):
+ fall = {rec["old_label"]: rec for rec in db.lf_fields.search({"old_label":{"$in": labs}})}
+ elif all(NEW_LF_RE.fullmatch(lab) for lab in labs):
+ fall = {rec["new_label"]: rec for rec in db.lf_fields.search({"new_label":{"$in": labs}})}
+ else:
+ fall = {}
+ for lab in labs:
+ if OLD_LF_RE.fullmatch(lab):
+ fall[lab] = db.lf_fields.lucky({"old_label":lab})
+ elif NEW_LF_RE.fullmatch(lab):
+ fall[lab] = db.lf_fields.lucky({"new_label":lab})
+ else:
+ fall[lab] = None
+ for lab in labs:
+ f = fall[lab]
+ if f is None:
+ ans += ' Invalid label %s ' % lab
+ continue
+ if f.get('new_label'):
+ l = str(f['new_label'])
+ else:
+ l = str(f['old_label'])
+ ans += '%s '%(url_for_label(l),l)
ans += format_coeffs(f['coeffs'])
ans += ' %d %d %d ' % (f['e'],f['f'],f['c'])
ans += transitive_group_display_knowl(f['galois_label'])
- ans += ' $' + show_slope_content(f['slopes'],f['t'],f['u'])+'$'
+ if f.get('slopes') and f.get('t') and f.get('u'):
+ ans += ' $' + show_slope_content(f['slopes'],f['t'],f['u'])+'$'
ans += '
'
if len(labs) != len(set(labs)):
ans += 'Fields which appear more than once occur according to their given multiplicities in the algebra'
return ans
def local_field_data(label):
- f = db.lf_fields.lookup(label)
+ if OLD_LF_RE.fullmatch(label):
+ f = db.lf_fields.lucky({"old_label": label})
+ elif NEW_LF_RE.fullmatch(label):
+ f = db.lf_fields.lucky({"new_label": label})
+ else:
+ return "Invalid label %s" % label
nicename = ''
if f['n'] < 3:
nicename = ' = ' + prettyname(f)
@@ -96,7 +145,7 @@ def local_field_data(label):
ans += 'Ramification index $e$: %s ' % str(f['e'])
ans += 'Residue field degree $f$: %s ' % str(f['f'])
ans += 'Discriminant ideal: $(p^{%s})$ ' % str(f['c'])
- if f.get('galois_label'):
+ if f.get('galois_label') is not None:
gt = int(f['galois_label'].split('T')[1])
ans += 'Galois group $G$: %s ' % group_pretty_and_nTj(gn, gt, True)
else:
@@ -121,8 +170,10 @@ def eisensteinformlatex(pol, unram):
R = PolynomialRing(QQ, 'y')
Rx = PolynomialRing(R, 'x')
unram2 = R(unram.replace('t', 'y'))
- unram = latex(Rx(unram.replace('t', 'x')))
pol = R(pol)
+ if unram2.degree() == 1 or unram2.degree() == pol.degree():
+ return latex(pol).replace('y', 'x')
+ unram = latex(Rx(unram.replace('t', 'x')))
l = []
while pol != 0:
qr = pol.quo_rem(unram2)
@@ -133,7 +184,8 @@ def eisensteinformlatex(pol, unram):
newpol = newpol.replace('y', 'x')
return newpol
-def plot_polygon(verts, polys, inds, p):
+def plot_ramification_polygon(verts, p, polys=None, inds=None):
+ # print("VERTS", verts)
verts = [tuple(pt) for pt in verts]
if not verts:
# Unramified, so we won't be displaying the plot
@@ -142,9 +194,9 @@ def plot_polygon(verts, polys, inds, p):
ymax = verts[0][1]
xmax = verts[-1][0]
# How far we need to shift text depends on the scale
- txshift = xmax / 60
+ txshift = xmax / 80
tyshift = xmax / 48
- tick = xmax / 160
+ #tick = xmax / 160
nextq = p
L = Graphics()
if ymax > 0:
@@ -153,64 +205,81 @@ def plot_polygon(verts, polys, inds, p):
# Add in silly white dot
L += points([(0,1)], color="white")
asp_ratio = (xmax + 2*txshift) / (8 + 16*tyshift)
- L += line([(0,0), (0, ymax)], color="grey")
- L += line([(0,0), (xmax, 0)], color="grey")
- for i in range(1, ymax + 1):
- L += line([(0, i), (tick, i)], color="grey")
- for i in range(xmax + 1):
- L += line([(i, 0), (i, tick/asp_ratio)], color="grey")
- for P in verts:
+ for i in range(xmax+1):
+ L += line([(-i, 0), (-i, ymax)], color=(0.85,0.85,0.85), thickness=0.5)
+ for j in range(ymax+1):
+ L += line([(0,j), (-xmax, j)], color=(0.85,0.85,0.85), thickness=0.5)
+ #L += line([(0,0), (0, ymax)], color="grey")
+ #L += line([(0,0), (-xmax, 0)], color="grey")
+ #for i in range(1, ymax + 1):
+ # L += line([(0, i), (-tick, i)], color="grey")
+ #for i in range(0, xmax + 1):
+ # L += line([(-i, 0), (-i, tick/asp_ratio)], color="grey")
+ xticks = set(P[0] for P in verts)
+ yticks = set(P[1] for P in verts)
+ if inds is not None:
+ xticks = xticks.union(p**i for i in range(len(inds)))
+ yticks = yticks.union(ind for ind in inds)
+ for x in xticks:
L += text(
- f"${P[0]}$", (P[0], -tyshift/asp_ratio),
+ f"${-x}$", (-x, -tyshift/asp_ratio),
color="black")
+ for y in yticks:
L += text(
- f"${P[1]}$", (-txshift, P[1]),
- horizontal_alignment="right",
+ f"${y}$", (txshift, y),
+ horizontal_alignment="left",
color="black")
- R = ZZ["t"]["z"]
- polys = [R(poly) for poly in polys]
-
- def restag(c, a, b):
- return text(f"${latex(c)}$", (a + txshift, b + tyshift/asp_ratio),
- horizontal_alignment="left",
- color="black")
- L += restag(polys[0][0], 1, ymax)
+
+ if polys is not None:
+ R = ZZ["t"]["z"]
+ polys = [R(poly) for poly in reversed(polys)]
+ # print("POLYS", polys)
+
+ def restag(c, a, b):
+ return text(f"${latex(c)}$", (-a - txshift, b + tyshift/asp_ratio),
+ horizontal_alignment="left",
+ color="black")
+ L += restag(polys[0][0], 1, ymax)
for i in range(len(verts) - 1):
P = verts[i]
Q = verts[i+1]
slope = ZZ(P[1] - Q[1]) / ZZ(Q[0] - P[0]) # actually the negative of the slope
d = slope.denominator()
- poly = polys[i]
if slope != 0:
- while nextq <= Q[0]:
- i = (nextq - P[0]) / d
- if i in ZZ and poly[i]:
- L += restag(poly[i], nextq, P[1] - (nextq - P[0]) * slope)
- nextq *= p
+ if polys is not None:
+ # Need to check that this is compatible with the residual polynomial normalization
+ while nextq <= Q[0]:
+ j = (nextq - P[0]) / d
+ if j in ZZ and polys[i][j]:
+ L += restag(polys[i][j], nextq, P[1] - (nextq - P[0]) * slope)
+ nextq *= p
L += text(
- f"${slope}$", (P[0] - txshift, (P[1] + Q[1]) / 2),
- horizontal_alignment="right",
+ f"${slope}$", (-(P[0] + Q[0]) / 2 + txshift, (P[1] + Q[1]) / 2 - tyshift/(2*asp_ratio)),
+ horizontal_alignment="left",
color="blue")
- for x in range(P[0], Q[0] + 1):
- L += line(
- [(x, Q[1]), (x, P[1] - (x - P[0]) * slope)],
- color="grey",
- )
- for y in range(Q[1], P[1]):
- L += line(
- [(P[0] - (y - P[1]) / slope, y), (P[0], y)],
- color="grey",
- )
- else:
+ #for x in range(P[0], Q[0] + 1):
+ # L += line(
+ # [(-x, Q[1]), (-x, P[1] - (x - P[0]) * slope)],
+ # color="grey",
+ # )
+ #for y in range(Q[1], P[1]):
+ # L += line(
+ # [(-P[0] + (y - P[1]) / slope, y), (-P[0], y)],
+ # color="grey",
+ # )
+ elif polys:
# For tame inertia, the coefficients can occur at locations other than powers of p
- for i, c in enumerate(poly):
- if i and c:
- L += restag(c, P[0] + i, P[1])
- L += line(verts, thickness=2)
- L += points([(p**i, ind) for i, ind in enumerate(inds)], size=30, color="black")
+ for j, c in enumerate(polys[i]):
+ if j and c:
+ L += restag(c, P[0] + j, P[1])
+ L += line([(-x,y) for (x,y) in verts], thickness=2)
+ L += polygon([(-x,y) for (x,y) in verts] + [(-xmax, ymax)], alpha=0.08)
+ if inds is not None:
+ # print("INDS", inds)
+ L += points([(-p**i, ind) for (i, ind) in enumerate(inds)], size=30, color="black", zorder=5)
L.axes(False)
L.set_aspect_ratio(asp_ratio)
- return encode_plot(L, pad=0, pad_inches=0, bbox_inches="tight", figsize=(8,4))
+ return encode_plot(L, pad=0, pad_inches=0, bbox_inches="tight", figsize=(8,4), dpi=300)
@app.context_processor
@@ -221,7 +290,10 @@ def ctx_local_fields():
# Utilities for subfield display
def format_lfield(label, p):
- data = db.lf_fields.lookup(label)
+ if OLD_LF_RE.fullmatch(label):
+ data = db.lf_fields.lucky({"old_label": label}, ["n", "p", "rf", "old_label", "new_label"])
+ else:
+ data = db.lf_fields.lucky({"new_label": label}, ["n", "p", "rf", "old_label", "new_label"])
return lf_display_knowl(label, name=prettyname(data))
@@ -259,24 +331,48 @@ def show_slopes2(sl):
def show_slope_content(sl,t,u):
if sl is None or t is None or u is None:
- return ' $not computed$ ' # actually killing math mode
+ return 'not computed'
sc = str(sl)
- if sc == '[]':
- sc = r'[\ ]'
if t > 1:
sc += '_{%d}' % t
if u > 1:
sc += '^{%d}' % u
- return (sc)
+ return latex_content(sc)
+
+relative_columns = ["base", "n0", "e0", "f0", "c0", "label_absolute", "n_absolute", "e_absolute", "f_absolute", "c_absolute"]
@local_fields_page.route("/")
def index():
bread = get_bread()
info = to_dict(request.args, search_array=LFSearchArray(), stats=LFStats())
+ if any(col in info for col in relative_columns):
+ info["relative"] = 1
if len(request.args) != 0:
- return local_field_search(info)
+ info["search_type"] = search_type = info.get("search_type", info.get("hst", ""))
+ if search_type in ['Families', 'FamilyCounts']:
+ info['search_array'] = FamiliesSearchArray(relative=("relative" in info))
+ if search_type in ['Counts', 'FamilyCounts']:
+ return local_field_count(info)
+ elif search_type in ['Families', 'RandomFamily']:
+ return families_search(info)
+ elif search_type in ['List', '', 'Random']:
+ return local_field_search(info)
+ else:
+ flash_error("Invalid search type; if you did not enter it in the URL please report")
+ info["field_count"] = db.lf_fields.stats.column_counts(["n", "p"])
+ info["family_count"] = db.lf_families.count({"n0":1}, groupby=["n", "p"])
return render_template("lf-index.html", title="$p$-adic fields", titletag="p-adic fields", bread=bread, info=info, learnmore=learnmore_list())
+@local_fields_page.route("/families/")
+def family_redirect():
+ info = to_dict(request.args)
+ info["search_type"] = "Families"
+ if "relative" not in info:
+ # Check for the presence of any relative-only arguments
+ if any(x in info for x in relative_columns):
+ info["relative"] = 1
+ return redirect(url_for(".index", **info))
+
@local_fields_page.route("/")
def by_label(label):
@@ -290,8 +386,17 @@ def url_for_label(label):
return url_for('.random_field')
return url_for(".by_label", label=label)
+def url_for_family(label):
+ return url_for(".family_page", label=label)
+
+def url_for_packet(packet):
+ return url_for(".index", packet=packet)
+
def local_field_jump(info):
- return redirect(url_for_label(info['jump']), 301)
+ if FAMILY_RE.fullmatch(info['jump']):
+ return redirect(url_for_family(info['jump']), 301)
+ else:
+ return redirect(url_for_label(info['jump']), 301)
def unpack_slopes(slopes, t, u):
return eval_rational_list(slopes), t, u
@@ -332,80 +437,473 @@ def intcol(j):
return 'not computed'
return f'${j}$'
+#label_col = LinkCol("new_label", "lf.field.label", "Label", url_for_label)
+label_col = MultiProcessedCol("label", "lf.field_label", "Label", ["old_label", "new_label"], (lambda old_label, new_label: f'{new_label} ' if new_label else f'{old_label} '), apply_download=(lambda old_label, new_label: (new_label if new_label else old_label)))
+
+def poly_col(relative=False):
+ if relative:
+ title = lambda info: "Polynomial" if info['family'].n0 == 1 else r"Polynomial $/ \Q_p$"
+ else:
+ title = "Polynomial"
+ return MultiProcessedCol("coeffs", "lf.defining_polynomial", title, ["coeffs", "unram"], eisensteinformlatex, mathmode=True, short_title="polynomial", apply_download=lambda coeffs, unram: coeffs)
+p_col = MathCol("p", "lf.qp", "$p$", short_title="prime")
+c_col = MathCol("c", "lf.discriminant_exponent", "$c$", short_title="discriminant exponent")
+e_col = MathCol("e", "lf.ramification_index", "$e$", short_title="ramification index")
+f_col = MathCol("f", "lf.residue_field_degree", "$f$", short_title="residue field degree")
+def gal_col(relative=False):
+ if relative:
+ title = lambda info: "Galois group" if info['family'].n0 == 1 else r"Galois group $/ \Q_p$"
+ else:
+ title = "Galois group"
+ return MultiProcessedCol("gal", "nf.galois_group", title,
+ ["n", "gal", "cache"],
+ galcolresponse, short_title="Galois group",
+ apply_download=lambda n, t, cache: [n, t])
+def aut_col(default):
+ return MathCol("aut", "lf.automorphism_group", r"$\#\Aut(K/\Q_p)$", short_title="auts", default=default)
+def slopes_col(default=True, relative=False):
+ if relative:
+ title = lambda info: "Artin slope content" if info['family'].n0 == 1 else r"Artin slope content $/ \Q_p$"
+ else:
+ title = "Artin slope content"
+ return MultiProcessedCol("slopes", "lf.slopes", title,
+ ["slopes", "t", "u"],
+ show_slope_content, short_title="Artin slope content",
+ apply_download=unpack_slopes, default=default)
+def hidden_col(default=True, relative=False):
+ if relative:
+ title = lambda info: "Hidden Artin slopes" if info['family'].n0 == 1 else r"Hidden Artin slopes $/ \Q_p$"
+ else:
+ title = "Hidden Artin slopes"
+ return ProcessedCol("hidden", "lf.slopes",
+ title,
+ latex_content, short_title="hidden Artin slopes",
+ apply_download=False, default=default)
+
+def swanslopes_col(default=False, relative=False):
+ if relative:
+ title = lambda info: "Swan slope content" if info['family'].n0 == 1 else r"Swan slope content $/ \Q_p$"
+ else:
+ title = "Swan slope content"
+ return MultiProcessedCol("swanslopes", "lf.slopes", title,
+ ["slopes", "t", "u", "c"],
+ (lambda slopes, t, u, c: show_slope_content(artin2swan(slopes), t, u)),
+ short_title="Swan slope content",
+ apply_download=(lambda slopes, t, u: unpack_slopes(artin2swan(slopes), t, u)),
+ default=default)
+
+def hiddenswan_col(default=False, relative=False):
+ if relative:
+ title = lambda info: "Hidden Swan slopes" if info['family'].n0 == 1 else r"Hidden Swan slopes $/ \Q_p$"
+ else:
+ title = "Hidden Swan slopes"
+ return MultiProcessedCol("hiddenswan", "lf.slopes",
+ title,
+ ["hidden", "c"],
+ (lambda hidden, c: latex_content(hidden2swan(hidden))),
+ short_title="hidden Swan slopes",
+ apply_download=False,
+ default=default)
+
+def insep_col(default=True, relative=False):
+ if relative:
+ title = lambda info: "Ind. of Insep." if info['family'].n0 == 1 else r"Ind. of Insep. $/ \Q_p$"
+ else:
+ title = "Ind. of Insep."
+ return ProcessedCol("ind_of_insep", "lf.indices_of_inseparability", title, formatbracketcol, default=default, short_title="ind. of insep.")
+def assoc_col(default=True, relative=False):
+ if relative:
+ title = lambda info: "Assoc. Inertia" if info['family'].n0 == 1 else r"Assoc. Inertia $/ \Q_p$"
+ else:
+ title = "Assoc. Inertia"
+ return ProcessedCol("associated_inertia", "lf.associated_inertia", title, formatbracketcol, default=default)
+def jump_col(default=True):
+ return ProcessedCol("jump_set", "lf.jump_set", "Jump Set", func=lambda js: f"${js}$" if js else "undefined", default=default, mathmode=False)
+def respoly_col():
+ return ProcessedCol("residual_polynomials", "lf.residual_polynomials", "Resid. Poly", default=False, mathmode=True, func=lambda rp: ','.join(teXify_pol(f) for f in rp))
lf_columns = SearchColumns([
- LinkCol("label", "lf.field.label", "Label", url_for_label),
+ label_col,
MathCol("n", "lf.degree", "$n$", short_title="degree", default=False),
- ProcessedCol("coeffs", "lf.defining_polynomial", "Polynomial", format_coeffs, mathmode=True),
- MathCol("p", "lf.qp", "$p$", short_title="prime"),
- MathCol("e", "lf.ramification_index", "$e$", short_title="ramification index"),
- MathCol("f", "lf.residue_field_degree", "$f$", short_title="residue field degree"),
- MathCol("c", "lf.discriminant_exponent", "$c$", short_title="discriminant exponent"),
- MultiProcessedCol("gal", "nf.galois_group", "Galois group",
- ["n", "gal", "cache"],
- galcolresponse,
- apply_download=lambda n, t, cache: [n, t]),
+ poly_col(),
+ p_col,
+ f_col,
+ e_col,
+ c_col,
+ gal_col(False),
ProcessedCol("u", "lf.unramified_degree", "$u$", intcol, short_title="unramified degree", default=False),
ProcessedCol("t", "lf.tame_degree", "$t$", intcol, short_title="tame degree", default=False),
- ListCol("visible", "lf.visible_slopes", "Visible slopes",
- show_slopes2, default=lambda info: info.get("visible"), mathmode=True),
- MultiProcessedCol("slopes", "lf.slope_content", "Slope content",
- ["slopes", "t", "u"],
- show_slope_content,
- mathmode=True, apply_download=unpack_slopes),
+ RationalListCol("visible", "lf.slopes", "Visible Artin slopes",
+ show_slopes2, default=lambda info: info.get("visible"), short_title="visible Artin slopes"),
+ # throw in c as a trick to differentiate it from just visible
+ MultiProcessedCol("visibleswan", "lf.slopes", "Visible Swan slopes",
+ ["visible","c"],
+ (lambda visible, c: latex_content(show_slopes2(artin2swan(visible)))),
+ mathmode=False, default=False,
+ short_title="visible Swan slopes",
+ apply_download=(lambda slopes: eval_rational_list(artin2swan(slopes)))),
+ slopes_col(),
+ swanslopes_col(),
+ hidden_col(default=False),
+ hiddenswan_col(),
+ aut_col(lambda info:info.get("aut")),
# want apply_download for download conversion
PolynomialCol("unram", "lf.unramified_subfield", "Unram. Ext.", default=lambda info:info.get("visible")),
ProcessedCol("eisen", "lf.eisenstein_polynomial", "Eisen. Poly.", default=lambda info:info.get("visible"), mathmode=True, func=format_eisen),
- ProcessedCol("ind_of_insep", "lf.indices_of_inseparability", "Ind. of Insep.", formatbracketcol, default=lambda info: info.get("ind_of_insep")),
- ProcessedCol("associated_inertia", "lf.associated_inertia", "Assoc. Inertia", formatbracketcol, default=lambda info: info.get("associated_inertia"))],
- db_cols=["c", "coeffs", "e", "f", "gal", "label", "n", "p", "slopes", "t", "u", "visible", "ind_of_insep", "associated_inertia","unram","eisen"])
+ insep_col(default=lambda info: info.get("ind_of_insep")),
+ assoc_col(default=lambda info: info.get("associated_inertia")),
+ respoly_col(),
+ jump_col(default=lambda info: info.get("jump_set"))],
+ db_cols=["aut", "c", "coeffs", "e", "f", "gal", "old_label", "new_label", "n", "p", "slopes", "t", "u", "visible", "hidden", "ind_of_insep", "associated_inertia", "jump_set", "unram", "eisen", "family", "residual_polynomials"])
+
+family_columns = SearchColumns([
+ label_col,
+ MultiProcessedCol("packet_link", "lf.packet", "Packet size", ["packet", "packet_size"], (lambda packet, size: '' if size is None else f'{size} '), default=lambda info: info.get("one_per") == "packet", contingent=lambda info: info['family'].n0 == 1),
+ poly_col(relative=True),
+ gal_col(lambda info: "Galois group" if info['family'].n0 == 1 else r"Galois group $/ \Q_p$"),
+ MathCol("galsize", "nf.galois_group", lambda info: "Galois degree" if info['family'].n0 == 1 else r"Galois degree $/ \Q_p$", short_title="Galois degree"),
+ aut_col(True),
+ slopes_col(default=False, relative=True),
+ swanslopes_col(relative=True),
+ hidden_col(relative=True),
+ hiddenswan_col(relative=True),
+ insep_col(relative=True),
+ assoc_col(relative=True),
+ respoly_col(),
+ jump_col()],
+ db_cols=["old_label", "new_label", "packet", "packet_size", "coeffs", "unram", "n", "gal", "aut", "slopes", "t", "u", "c", "hidden", "ind_of_insep", "associated_inertia", "residual_polynomials", "jump_set"])
+
+class PercentCol(MathCol):
+ def display(self, rec):
+ x = self.get(rec)
+ if x == 0:
+ return r"$0\%$"
+ elif x == 1:
+ return r"$100\%$"
+ return fr"${100*x:.2f}\%$"
+
+def pretty_link(label, p, n, rf):
+ if OLD_LF_RE.fullmatch(label):
+ name = {"old_label": label}
+ else:
+ name = {"new_label": label}
+ name.update({"p": p, "n": n, "rf": rf})
+ name = prettyname(name)
+ return f'{name} '
+
+families_columns = SearchColumns([
+ LinkCol("label", "lf.family_label", "Label", url_for_family),
+ MathCol("p", "lf.residue_field", "$p$", short_title="prime"),
+ MathCol("n", "lf.degree", "$n$", short_title="degree"),
+ MathCol("n0", "lf.degree", "$n_0$", short_title="base degree", default=False, contingent=lambda info: "relative" in info),
+ MathCol("n_absolute", "lf.degree", r"$n_{\mathrm{abs}}$", short_title="abs. degree", default=False, contingent=lambda info: "relative" in info),
+ MathCol("f", "lf.residue_field_degree", "$f$", short_title="res. field degree"),
+ MathCol("f0", "lf.residue_field_degree", "$f_0$", short_title="base res. field degree", default=False, contingent=lambda info: "relative" in info),
+ MathCol("f_absolute", "lf.residue_field_degree", r"$f_{\mathrm{abs}}$", short_title="abs. residue field degree", default=False, contingent=lambda info: "relative" in info),
+ MathCol("e", "lf.ramification_index", "$e$", short_title="ram. index"),
+ MathCol("e0", "lf.ramification_index", "$e_0$", short_title="base ram. index", default=False, contingent=lambda info: "relative" in info),
+ MathCol("e_absolute", "lf.ramification_index", r"$e_{\mathrm{abs}}$", short_title="abs. ram. index", default=False, contingent=lambda info: "relative" in info),
+ MathCol("c", "lf.discriminant_exponent", "$c$", short_title="disc. exponent"),
+ MathCol("c0", "lf.discriminant_exponent", "$c_0$", short_title="base disc. exponent", default=False, contingent=lambda info: "relative" in info),
+ MathCol("c_absolute", "lf.discriminant_exponent", r"$c_{\mathrm{abs}}$", short_title="abs. disc. exponent", default=False, contingent=lambda info: "relative" in info),
+ MultiProcessedCol("base_field", "lf.family_base", "Base",
+ ["base", "p", "n0", "rf0"],
+ pretty_link, contingent=lambda info: "relative" in info),
+ RationalListCol("visible", "lf.slopes", "Abs. Artin slopes",
+ show_slopes2, default=False, short_title="abs. Artin slopes"),
+ RationalListCol("slopes", "lf.slopes", "Swan slopes", short_title="Swan slopes"),
+ RationalListCol("means", "lf.means", "Means", delim=[r"\langle", r"\rangle"]),
+ RationalListCol("rams", "lf.rams", "Rams", delim = "()"),
+ ProcessedCol("poly", "lf.family_polynomial", "Generic poly", lambda pol: teXify_pol(pol, greek_vars=True, subscript_vars=True), mathmode=True, default=False),
+ MathCol("ambiguity", "lf.family_ambiguity", "Ambiguity"),
+ MathCol("field_count", "lf.family_field_count", "Field count"),
+ MathCol("mass_relative", "lf.family_mass", "Mass", orig=["mass_relative_display"]),
+ MathCol("mass_absolute", "lf.family_mass", "Mass (absolute)", orig=["mass_absolute_display"], default=False),
+ MathCol("mass_stored", "lf.family_mass", "Mass stored", default=False),
+ PercentCol("mass_found", "lf.family_mass", "Mass found", default=False),
+ MathCol("wild_segments", "lf.wild_segments", "Wild segments", default=False),
+ MathCol("packet_count", "lf.packet", "Num. Packets", contingent=lambda info: "relative" not in info),
+])
def lf_postprocess(res, info, query):
- cache = knowl_cache(list({f"{rec['n']}T{rec['gal']}" for rec in res if 'gal' in rec}))
+ cache = knowl_cache(list({f"{rec['n']}T{rec['gal']}" for rec in res if rec.get('gal') is not None}))
for rec in res:
rec["cache"] = cache
+ if rec.get('gal') is not None:
+ gglabel = f"{rec['n']}T{rec['gal']}"
+ rec["galsize"] = cache[gglabel]["order"]
+ else:
+ rec["galsize"] = " $not computed$ " # undo mathmode
return res
-@search_wrap(table=db.lf_fields,
- title='$p$-adic field search results',
- titletag=lambda:'p-adic field search results',
- err_title='Local field search input error',
- columns=lf_columns,
- per_page=50,
- shortcuts={'jump': local_field_jump, 'download': LF_download()},
- postprocess=lf_postprocess,
- bread=lambda:get_bread([("Search results", ' ')]),
- learnmore=learnmore_list,
- url_for_label=url_for_label)
-def local_field_search(info,query):
+def families_postprocess(res, info, query):
+ quads = list(set(rec["base"] for rec in res if rec["n0"] == 2))
+ if quads:
+ rflook = {rec["new_label"]: rec["rf"] for rec in db.lf_fields.search({"new_label":{"$in":quads}}, ["new_label", "rf"])}
+ for rec in res:
+ if rec["n0"] == 1:
+ rec["rf0"] = [1, 0]
+ elif rec["n0"] == 2:
+ rec["rf0"] = rflook[rec["base"]]
+ else:
+ rec["rf0"] = None
+ return res
+
+slopes_re = re.compile(r"\[(\d+(/\d+)?)?(,\d+(/\d+)?)*\]")
+rams_re = re.compile(r"\((\d+(/\d+)?)?(,\d+(/\d+)?)*\)")
+means_re = re.compile(r"\{(\d+(/\d+)?)?(,\d+(/\d+)?)*\}") # clean_info changed "<>" to "{}" for html safety
+@search_parser(default_field='herbrand', angle_to_curly=True)
+def parse_herbrand(inp, query, qfield):
+ # We ignore qfield, since it is determined from the delimiters of the input
+ if slopes_re.fullmatch(inp):
+ query["slopes"] = inp.replace(",", ", ")
+ elif rams_re.fullmatch(inp):
+ query["rams"] = "[" + inp[1:-1].replace(",", ", ") + "]"
+ elif means_re.fullmatch(inp):
+ query["means"] = "[" + inp[1:-1].replace(",", ", ") + "]"
+ else:
+ print("INPINPINPINP", inp, len(inp))
+ raise ValueError("Improperly formatted Herbrand invariant")
+
+def common_parse(info, query):
parse_ints(info,query,'p',name='Prime p')
parse_ints(info,query,'n',name='Degree')
parse_ints(info,query,'u',name='Unramified degree')
parse_ints(info,query,'t',name='Tame degree')
parse_galgrp(info,query,'gal',qfield=('galois_label','n'))
+ parse_ints(info,query,'aut',name='Automorphisms')
parse_ints(info,query,'c',name='Discriminant exponent c')
parse_ints(info,query,'e',name='Ramification index e')
parse_ints(info,query,'f',name='Residue field degree f')
- parse_rats(info,query,'topslope',qfield='top_slope',name='Top slope', process=ratproc)
+ parse_rats(info,query,'topslope',qfield='top_slope',name='Top Artin slope', process=ratproc)
parse_newton_polygon(info,query,"slopes", qfield="slopes_tmp", mode=info.get('slopes_quantifier'))
parse_newton_polygon(info,query,"visible", qfield="visible_tmp", mode=info.get('visible_quantifier'))
parse_newton_polygon(info,query,"ind_of_insep", qfield="ind_of_insep_tmp", mode=info.get('insep_quantifier'), reversed=True)
parse_bracketed_posints(info,query,"associated_inertia")
+ parse_bracketed_posints(info,query,"jump_set")
parse_inertia(info,query,qfield=('inertia_gap','inertia'))
parse_inertia(info,query,qfield=('wild_gap','wild_gap'), field='wild_gap')
- info['search_array'] = LFSearchArray()
+ parse_noop(info,query,'packet')
+ parse_noop(info,query,'family')
+ parse_noop(info,query,'hidden')
+
+def count_fields(p, n=None, f=None, e=None, eopts=None):
+ # Implement a formula due to Monge for the number of fields with given n or e,f
+ if n is None and (f is None or e is None):
+ raise ValueError("Must specify n or (f and e)")
+ if f is None:
+ if e is None:
+ if eopts is None:
+ return sum(count_fields(p, e=e, f=n//e) for e in n.divisors())
+ return sum(count_fields(p, e=e, f=n//e) for e in n.divisors() if e in eopts)
+ elif n % e != 0:
+ return 0
+ f = n // e
+ elif e is None:
+ if n % f != 0:
+ return 0
+ e = n // f
+ def eps(i):
+ return sum(p**(-j) for j in range(1, i+1))
+ def ee(i):
+ return euler_phi(p**i)
+ def sig(n0, e, f, s):
+ nn = n0 * e * f
+ return 1 + sum(p**i * (p**(eps(i) * nn) - p**(eps(i-1) * nn)) for i in range(1,s+1))
+ def delta(m, s, i):
+ if s == i == 0:
+ return 1
+ if s > i == 0:
+ return (p**m - 1) * p**(m * (s-1))
+ if s > i > 0:
+ return (p - 1) * (p**m - 1) * p**(m * (s - 1) + i - 1)
+ if s == i > 0:
+ return (p - 1) * p**(m * s + s - 1)
+ return 0
+ def term(i, fp, ep):
+ ep_val = ep.valuation(p)
+ epp = e / (ee(i) * ep)
+ if not epp.is_integer():
+ return 0
+ epp_val, epp_unit = epp.val_unit(p)
+ fpp = f / fp
+ a = 1 if ((p**fp - 1) / epp_unit).is_integer() else 0
+ return a * euler_phi(epp_unit) * euler_phi(fpp) / ee(i) * sig(ee(i), ep, fp, ep_val) * delta(ee(i) * ep * fp, epp_val, i)
+
+ return 1/f * sum(term(i, fp, ep) for i in range(e.valuation(p)+1) for fp in f.divisors() for ep in e.divisors())
+
+def fix_top_slope(s):
+ if isinstance(s, float):
+ return QQ(s)
+ elif isinstance(s, str):
+ return QQ(s[12:])
+ return s
+
+def count_postprocess(res, info, query):
+ # We account for two possible ways of encoding top_slope
+ for key, val in list(res.items()):
+ res[key[0],fix_top_slope(key[1])] = res.pop(key)
+ # Fill in entries using field_count
+ if info["search_type"] == "Counts" and set(query).issubset("pne"):
+ groupby = info["groupby"]
+ if groupby == ["p", "n"]:
+ if "e" in info:
+ # We need to handle the possibility that there are constraints on e
+ eopts = integer_options(info["e"], upper_bound=47)
+ else:
+ eopts = None
+ func = lambda p, n: count_fields(p, n=n, eopts=eopts)
+ elif groupby == ["p", "e"]:
+ n = db.lf_fields.distinct("n", query)
+ if len(n) != 1:
+ # There were no results...
+ return res
+ n = ZZ(n[0])
+ func = lambda p, e: count_fields(p, n=n, e=e)
+ elif groupby == ["n", "e"]:
+ p = db.lf_fields.distinct("p", query)
+ if len(p) != 1:
+ # No results...
+ return res
+ p = ZZ(p[0])
+ func = lambda n, e: count_fields(p, n=n, e=e)
+ else:
+ return res
+ for a in info["row_heads"]:
+ for b in info["col_heads"]:
+ if (a,b) not in res:
+ cnt = func(ZZ(a),ZZ(b))
+ if cnt:
+ info["nolink"].add((a,b))
+ res[a,b] = cnt
+ return res
+
+@count_wrap(
+ template="lf-count-results.html",
+ table=db.lf_fields,
+ groupby=["p", "n"],
+ title="Local field count results",
+ err_title="Local field search input error",
+ postprocess=count_postprocess,
+ bread=lambda: get_bread([("Count results", " ")]),
+)
+def local_field_count(info, query):
+ if info["search_type"] == "Counts":
+ table = db.lf_fields
+ common_parse(info, query)
+ else:
+ common_family_parse(info, query)
+ table = db.lf_families
+ if "base" in query:
+ p = query["base"].split(".")[0]
+ if not p.isdigit():
+ raise ValueError(f"Invalid base {query['base']}")
+ p = int(p)
+ if "p" in query:
+ tmp = integer_options(info["p"], contained_in=table.distinct("p"))
+ if p not in tmp:
+ raise ValueError("Base prime not compatible with constraints on p")
+ info["p"] = str(p)
+ query["p"] = p
+ if "relative" not in info:
+ query["n0"] = 1
+ if "gal" in info and "n" not in info:
+ # parse_galgrp adds restrictions on n
+ if type(query["n"]) == int:
+ info["n"] = str(query["n"])
+ else:
+ info["n"] = ",".join(query["n"]["$in"])
+ groupby = []
+ heads = []
+ maxval = {"p": 200, "n": 47, "e": 47}
+ for col in ["p", "n", "e", "c", "top_slope"]:
+ if col in "pne" and info["search_type"] == "Counts":
+ # Allow user to get virtual counts outside the specified range
+ tmp = integer_options(info.get(col, f"1-{maxval[col]}"), upper_bound=maxval[col])
+ if col == "p":
+ tmp = [p for p in tmp if ZZ(p).is_prime()]
+ elif col == "n" and "e" in info:
+ # Constrain degrees to b only multiples of some e
+ eopts = integer_options(info["e"], upper_bound=47)
+ if 1 not in eopts:
+ emuls = set()
+ for e in eopts:
+ emuls.update([e*j for j in range(1, 47//e + 1)])
+ tmp = sorted(set(tmp).intersection(emuls))
+ else:
+ tmp = table.distinct(col, query)
+ if len(tmp) > 1:
+ if col == "top_slope":
+ tmp = sorted(fix_top_slope(s) for s in tmp)
+ groupby.append(col)
+ heads.append(tmp)
+ if len(groupby) == 2:
+ break
+ else:
+ raise ValueError("To generate count table, you must not specify all of p, n, e, and c")
+ query["__groupby__"] = info["groupby"] = groupby
+ if info["search_type"] == "FamilyCounts":
+ query["__table__"] = table
+ query["__title__"] = "Family count results"
+
+ info["nolink"] = set()
+ urlgen_info = dict(info)
+ urlgen_info.pop("hst", None)
+ urlgen_info.pop("stats", None)
+ if info["search_type"] == "FamilyCounts":
+ urlgen_info["search_type"] = "Families"
+ def url_generator(a, b):
+ if (a,b) in info["nolink"]:
+ return
+ info_copy = dict(urlgen_info)
+ info_copy.pop("search_array", None)
+ if info["search_type"] == "Counts":
+ info_copy.pop("search_type", None)
+ info_copy.pop("nolink", None)
+ info_copy.pop("groupby", None)
+ info_copy[groupby[0]] = a
+ info_copy[groupby[1]] = b
+ return url_for(".index", **info_copy)
+
+ info["row_heads"], info["col_heads"] = heads
+ names = {"p": "Prime", "n": "Degree", "e": "Ramification index", "c": "Discriminant exponent", "top_slope": "Top Artin slope"}
+ info["row_label"], info["col_label"] = [names[col] for col in groupby]
+ info["url_func"] = url_generator
+
+@search_wrap(table=db.lf_fields,
+ title='$p$-adic field search results',
+ titletag=lambda:'p-adic field search results',
+ err_title='Local field search input error',
+ columns=lf_columns,
+ per_page=50,
+ shortcuts={'jump': local_field_jump, 'download': LF_download()},
+ postprocess=lf_postprocess,
+ bread=lambda:get_bread([("Search results", ' ')]),
+ learnmore=learnmore_list,
+ url_for_label=url_for_label)
+def local_field_search(info,query):
+ common_parse(info, query)
def render_field_webpage(args):
data = None
info = {}
if 'label' in args:
label = clean_input(args['label'])
- data = db.lf_fields.lookup(label)
- if data is None:
- if LF_RE.fullmatch(label):
+ if NEW_LF_RE.fullmatch(label):
+ data = db.lf_fields.lucky({"new_label":label})
+ if data is None:
flash_error("Field %s was not found in the database.", label)
- else:
- flash_error("%s is not a valid label for a $p$-adic field.", label)
+ return redirect(url_for(".index"))
+ elif OLD_LF_RE.fullmatch(label):
+ data = db.lf_fields.lucky({"old_label": label})
+ if data is None:
+ flash_error("Field %s was not found in the database.", label)
+ return redirect(url_for(".index"))
+ new_label = data.get("new_label")
+ if new_label is not None:
+ return redirect(url_for_label(label=new_label), 301)
+ else:
+ flash_error("%s is not a valid label for a $p$-adic field.", label)
return redirect(url_for(".index"))
title = '$p$-adic field ' + prettyname(data)
titletag = 'p-adic field ' + prettyname(data)
@@ -414,16 +912,16 @@ def render_field_webpage(args):
Qp = r'\Q_{%d}' % p
e = data['e']
f = data['f']
+ n = data['n']
cc = data['c']
- gn = data['n']
auttype = 'aut'
- if data.get('galois_label'):
+ if data.get('galois_label') is not None:
gt = int(data['galois_label'].split('T')[1])
- the_gal = WebGaloisGroup.from_nt(gn,gt)
- isgal = ' Galois' if the_gal.order() == gn else ' not Galois'
+ the_gal = WebGaloisGroup.from_nt(n,gt)
+ isgal = ' Galois' if the_gal.order() == n else ' not Galois'
abelian = ' and abelian' if the_gal.is_abelian() else ''
galphrase = 'This field is'+isgal+abelian+r' over $\Q_{%d}.$' % p
- if the_gal.order() == gn:
+ if the_gal.order() == n:
auttype = 'gal'
info['aut_gp_knowl'] = the_gal.aut_knowl()
# we don't know the Galois group, but maybe the Aut group is obvious
@@ -438,7 +936,7 @@ def render_field_webpage(args):
('e', r'\(%s\)' % e),
('f', r'\(%s\)' % f),
('c', r'\(%s\)' % cc),
- ('Galois group', group_pretty_and_nTj(gn, gt) if data.get('galois_label') else 'not computed'),
+ ('Galois group', group_pretty_and_nTj(n, gt) if data.get('galois_label') is not None else 'not computed'),
]
# Look up the unram poly so we can link to it
unramdata = db.lf_fields.lucky({'p': p, 'n': f, 'c': 0})
@@ -446,7 +944,10 @@ def render_field_webpage(args):
logger.fatal("Cannot find unramified field!")
unramfriend = ''
else:
- unramfriend = url_for_label(unramdata['label'])
+ ulabel = unramdata.get('new_label')
+ if ulabel is None:
+ ulabel = unramdata.get('old_label')
+ unramfriend = url_for_label(ulabel)
Px = PolynomialRing(QQ, 'x')
Pt = PolynomialRing(QQ, 't')
@@ -464,24 +965,28 @@ def render_field_webpage(args):
eisenp = Ptx(str(data['eisen']).replace('y','x'))
eisenp = raw_typeset(str(eisenp), web_latex(eisenp), extra=r'$\ \in'+Qp+'(t)[x]$')
- rflabel = db.lf_fields.lucky({'p': p, 'n': {'$in': [1, 2]}, 'rf': data['rf']}, projection=0)
+ rflabel = db.lf_fields.lucky({'p': p, 'n': {'$in': [1, 2]}, 'rf': data['rf']}, projection=["new_label", "old_label"])
if rflabel is None:
logger.fatal("Cannot find discriminant root field!")
rffriend = ''
else:
+ if rflabel.get("new_label"):
+ rflabel = rflabel["new_label"]
+ else:
+ rflabel = rflabel["old_label"]
rffriend = url_for_label(rflabel)
gsm = data['gsm']
if gsm == [0]:
- gsm = 'Not computed'
+ gsm = 'not computed'
elif gsm == [-1]:
gsm = 'Does not exist'
else:
gsm = lf_formatfield(','.join(str(b) for b in gsm))
- if data.get('wild_gap') and data['wild_gap'] != [0,0]:
+ if data['wild_gap'] is not None and data['wild_gap'] != [0,0]:
wild_inertia = abstract_group_display_knowl(f"{data['wild_gap'][0]}.{data['wild_gap'][1]}")
else:
- wild_inertia = 'Not computed'
+ wild_inertia = 'not computed'
if data['f'] == 1 or data['e'] == 1:
thepolynomial = raw_typeset(polynomial)
@@ -489,54 +994,126 @@ def render_field_webpage(args):
eform = '$' + eisensteinformlatex(data['coeffs'], data['unram']) + '$'
thepolynomial = raw_typeset(polynomial, eform)
info.update({
- 'polynomial': thepolynomial,
- 'n': data['n'],
- 'p': p,
- 'c': data['c'],
- 'e': data['e'],
- 'f': data['f'],
- 'rf': lf_display_knowl(rflabel, name=printquad(data['rf'], p)),
- 'base': lf_display_knowl(str(p)+'.1.0.1', name='$%s$' % Qp),
- 'hw': data['hw'],
- 'visible': show_slopes(data['visible']),
- 'wild_inertia': wild_inertia,
- 'unram': unramp,
- 'ind_insep': show_slopes(str(data['ind_of_insep'])),
- 'eisen': eisenp,
- 'gsm': gsm,
- 'auttype': auttype,
- 'subfields': format_subfields(data['subfield'],data['subfield_mult'],p),
- 'aut': data['aut'],
- })
+ 'polynomial': thepolynomial,
+ 'n': n,
+ 'p': p,
+ 'c': cc,
+ 'e': e,
+ 'f': f,
+ 'rf': lf_display_knowl( rflabel, name=printquad(data['rf'], p)),
+ 'base': lf_display_knowl(str(p)+'.1.0.1', name='$%s$' % Qp),
+ 'hw': data['hw'],
+ 'visible': latex_content(data['visible']),
+ 'visible_swan': latex_content(artin2swan(data['visible'])),
+ 'wild_inertia': wild_inertia,
+ 'unram': unramp,
+ 'ind_insep': latex_content(str(data['ind_of_insep'])),
+ 'eisen': eisenp,
+ 'gsm': gsm,
+ 'auttype': auttype,
+ 'subfields': format_subfields(data['subfield'],data['subfield_mult'],p),
+ 'aut': data['aut'],
+ 'ppow_roots_of_unity': data.get('ppow_roots_of_unity'),
+ })
friends = []
- if data.get('slopes'):
- info.update({'slopes': show_slopes(data['slopes'])})
- if data.get('inertia'):
- info.update({'inertia': group_display_inertia(data['inertia'])})
- for k in ['gms', 't', 'u']:
- if data.get(k):
- info.update({k: data[k]})
- if data.get('ram_poly_vert'):
- info.update({'ram_polygon_plot': plot_polygon(data['ram_poly_vert'], data['residual_polynomials'], data['ind_of_insep'], p)})
- if data.get('residual_polynomials'):
- info.update({'residual_polynomials': ",".join(f"${teXify_pol(poly)}$" for poly in data['residual_polynomials'])})
- if data.get('associated_inertia'):
- info.update({'associated_inertia': ",".join(f"${ai}$" for ai in data['associated_inertia'])})
- if data.get('galois_label'):
- info.update({'gal': group_pretty_and_nTj(gn, gt, True),
+ if data.get("ppow_roots_of_unity") is not None:
+ prou = data["ppow_roots_of_unity"]
+ rou = (p**f - 1) * p**prou
+ if f > 1:
+ rou_expr = [f"({p}^{{ {f} }} - 1)"]
+ elif p > 2:
+ rou_expr = [f"({p} - 1)"]
+ else:
+ rou_expr = []
+ if prou == 1:
+ rou_expr.append(f"{p}")
+ elif prou > 1:
+ rou_expr.append(f"{p}^{{ {prou} }}")
+ rou_expr = r" \cdot ".join(rou_expr)
+ if rou_expr == "2": # only case where we don't want an = sign
+ info["roots_of_unity"] = "$2$"
+ else:
+ info["roots_of_unity"] = f"${rou} = {rou_expr}$"
+ else:
+ info["roots_of_unity"] = "not computed"
+ if data.get("family") is not None:
+ friends.append(('Absolute family', url_for(".family_page", label=data["family"])))
+ subfields = [f"{p}.1.1.0a1.1"]
+ if data["subfield"]:
+ if all(OLD_LF_RE.fullmatch(slabel) for slabel in data["subfield"]):
+ new_labels = list(db.lf_fields.search({"old_label":{"$in": data["subfield"]}}, "new_label"))
+ if all(slabel is not None for slabel in new_labels):
+ subfields.extend(new_labels)
+ else:
+ subfields.extend(data["subfield"])
+ elif all(NEW_LF_RE.fullmatch(slabel) for slabel in data["subfield"]):
+ subfields.extend(data["subfield"])
+ friends.append(('Families containing this field', url_for(".index", relative=1, search_type="Families", label_absolute=data["family"],base=",".join(subfields))))
+ rec = db.lf_families.lucky({"label":data["family"]}, ["means", "rams"])
+ info["means"] = latex_content(rec["means"]).replace("[", r"\langle").replace("]", r"\rangle")
+ info["rams"] = latex_content(rec["rams"]).replace("[", "(").replace("]", ")")
+ if n < 16 and NEW_LF_RE.fullmatch(label):
+ friends.append(('Families with this base', url_for(".index", relative=1, search_type="Families", base=label)))
+ if data.get('slopes') is not None:
+ info['slopes'] = latex_content(data['slopes'])
+ info['swanslopes'] = latex_content(artin2swan(data['slopes']))
+ if data.get('inertia') is not None:
+ info['inertia'] = group_display_inertia(data['inertia'])
+ for k in ['gms', 't', 'u', 'galois_degree']:
+ if data.get(k) is not None:
+ info[k] = data[k]
+ if data.get('ram_poly_vert') is not None:
+ info['ram_polygon_plot'] = plot_ramification_polygon(data['ram_poly_vert'], p, data['residual_polynomials'], data['ind_of_insep'])
+ if data.get('residual_polynomials') is not None:
+ info['residual_polynomials'] = ",".join(f"${teXify_pol(poly)}$" for poly in data['residual_polynomials'])
+ if data.get('associated_inertia') is not None:
+ info['associated_inertia'] = ",".join(f"${ai}$" for ai in data['associated_inertia'])
+ if data.get('galois_label') is not None:
+ info.update({'gal': group_pretty_and_nTj(n, gt, True),
'galphrase': galphrase,
'gt': gt})
- friends.append(('Galois group', "/GaloisGroup/%dT%d" % (gn, gt)))
+ friends.append(('Galois group', "/GaloisGroup/%dT%d" % (n, gt)))
+ if data.get('jump_set') is not None:
+ info['jump_set'] = data['jump_set']
+ if info['jump_set'] == []:
+ info['jump_set'] = "undefined"
+ else:
+ info['jump_set'] = f"${info['jump_set']}$"
if unramfriend != '':
friends.append(('Unramified subfield', unramfriend))
if rffriend != '':
friends.append(('Discriminant root field', rffriend))
if data['is_completion']:
+ # Need to check whether number fields are storing completions using new labels or old labels
+ zeta3loc = db.nf_fields.lookup("2.0.3.1", "local_algs")
+ if zeta3loc == ["3.2.1.2"]:
+ # Old labels
+ lstr = data["old_label"]
+ elif zeta3loc == ["3.1.2.1a1.2"]:
+ # New labels
+ lstr = data["new_label"]
+ else:
+ # Error; fall back on old label
+ print("ZETALOC", zeta3loc)
+ flash_error("Incorrect local algebra for Q(zeta3)")
+ lstr = data["old_label"]
friends.append(('Number fields with this completion',
- url_for('number_fields.number_field_render_webpage')+"?completions={}".format(label)))
+ url_for('number_fields.number_field_render_webpage')+f"?completions={lstr}"))
downloads = [('Underlying data', url_for('.lf_data', label=label))]
- bread = get_bread([(label, ' ')])
+ if data.get('new_label'):
+ _, _, _, fam, i = data['new_label'].split(".")
+ _, fama, subfam = re.split(r"(\D+)", fam)
+ bread = get_bread([(str(p), url_for('.index', p=p)),
+ (f"{f}.{e}", url_for('.index', p=p, e=e, f=f)),
+ (str(cc), url_for('.index', p=p, e=e, f=f, c=cc)),
+ (fama, url_for('.family_page', label=data['family'])),
+ (f'{subfam}.{i}', ' ')])
+ else:
+ bread = get_bread([(str(p), url_for('.index', p=p)),
+ (str(n), url_for('.index', p=p, n=n)),
+ (str(cc), url_for('.index', p=p, n=n, c=cc)),
+ (data['label'], ' ')])
return render_template(
"lf-show-field.html",
title=title,
@@ -547,13 +1124,15 @@ def render_field_webpage(args):
friends=friends,
downloads=downloads,
learnmore=learnmore_list(),
- KNOWL_ID="lf.%s" % label,
+ KNOWL_ID="lf.%s" % label, # TODO: BROKEN
)
def prettyname(ent):
if ent['n'] <= 2:
return printquad(ent['rf'], ent['p'])
- return ent['label']
+ if ent.get('new_label'):
+ return ent['new_label']
+ return ent['old_label']
@cached_function
def getu(p):
@@ -576,11 +1155,22 @@ def printquad(code, p):
@local_fields_page.route("/data/")
def lf_data(label):
- if not LF_RE.fullmatch(label):
+ if NEW_LF_RE.fullmatch(label):
+ title = f"Local field data - {label}"
+ bread = get_bread([(label, url_for_label(label)), ("Data", " ")])
+ sorts = [["p", "n", "e", "c", "ctr_family", "ctr_subfamily", "ctr"]]
+ return datapage(label, "lf_fields", title=title, bread=bread, label_cols=["new_label"], sorts=sorts)
+ elif OLD_LF_RE.fullmatch(label):
+ title = f"Local field data - {label}"
+ bread = get_bread([(label, url_for_label(label)), ("Data", " ")])
+ sorts = [["p", "n", "c", "old_label"]]
+ return datapage(label, "lf_fields", title=title, bread=bread, label_cols=["old_label"], sorts=sorts)
+ elif FAMILY_RE.fullmatch(label):
+ title = f"Local field family data - {label}"
+ bread = get_bread([(label, url_for_family(label)), ("DATA", " ")])
+ return datapage(label, "lf_families", title=title, bread=bread)
+ else:
return abort(404, f"Invalid label {label}")
- title = f"Local field data - {label}"
- bread = get_bread([(label, url_for_label(label)), ("Data", " ")])
- return datapage(label, "lf_fields", title=title, bread=bread)
@local_fields_page.route("/random")
@redirect_no_cache
@@ -605,6 +1195,19 @@ def statistics():
bread = get_bread([("Statistics", " ")])
return render_template("display_stats.html", info=LFStats(), title=title, bread=bread, learnmore=learnmore_list())
+@local_fields_page.route("/dynamic_stats")
+def dynamic_statistics():
+ info = to_dict(request.args, search_array=LFSearchArray())
+ LFStats().dynamic_setup(info)
+ title = "p-adic fields: Dynamic statistics"
+ return render_template(
+ "dynamic_stats.html",
+ info=info,
+ title=title,
+ bread=get_bread([("Dynamic Statistics", " ")]),
+ learnmore=learnmore_list(),
+ )
+
@local_fields_page.route("/Completeness")
def cande():
t = 'Completeness of $p$-adic field data'
@@ -639,150 +1242,526 @@ def reliability():
t = 'Reliability of $p$-adic field data'
ttag = 'Reliability of p-adic field data'
bread = get_bread([("Reliability", '')])
- return render_template("single.html", kid='rcs.source.lf',
+ return render_template("single.html", kid='rcs.rigor.lf',
title=t, titletag=ttag, bread=bread,
learnmore=learnmore_list_remove('Reliability'))
+@local_fields_page.route("/family/")
+def family_page(label):
+ m = FAMILY_RE.fullmatch(label)
+ if m is None:
+ flash_error("Invalid label %s", label)
+ return redirect(url_for(".index"))
+ try:
+ family = pAdicSlopeFamily(label)
+ except NotImplementedError:
+ flash_error("No famly with label %s in the database", label)
+ return redirect(url_for(".index"))
+ info = to_dict(request.args, search_array=FamilySearchArray(family), family_label=label, family=family, stats=LFStats())
+ p, n = family.p, family.n
+ if family.n0 == 1:
+ info['bread'] = get_bread([("Families", url_for(".index", search_type="Families")),
+ (str(p), url_for(".index", search_type="Families", p=p)),
+ (str(family.n), url_for(".index", search_type="Families", p=p, n=n)),
+ (label, "")])
+ else:
+ info['bread'] = get_bread([("Families", url_for(".index", search_type="Families", relative=1)),
+ (family.base, url_for(".index", search_type="Families", relative=1, base=family.base)),
+ (str(family.n), url_for(".index", search_type="Families", relative=1, base=family.base, n=n)),
+ (label, "")])
+ info['title'] = f"$p$-adic family {label}"
+ info['titletag'] = f"p-adic family {label}"
+ info['show_count'] = True
+ info['properties'] = [
+ ('Label', label),
+ ('Base', f'{family.base} '), # it would be nice to pretty print the base
+ ('Degree', rf'\({n}\)'),
+ ('e', rf'\({family.e}\)'),
+ ('f', rf'\({family.f}\)'),
+ ('c', rf'\({family.c}\)'),
+ ]
+ info['downloads'] = [('Underlying data', url_for('.lf_data', label=label))]
+ if family.n0 == 1:
+ info['friends'] = [('Relative constituents', url_for(".index", relative=1, search_type="Families", label_absolute=family.label))]
+ else:
+ info['friends'] = [('Absolute family', url_for(".family_page", label=family.label_absolute))]
+ info['latex_content'] = latex_content
+ return render_family(info)
+
+@embed_wrap(
+ table=db.lf_fields,
+ template="lf-family.html",
+ err_title="Local field family error",
+ columns=family_columns,
+ learnmore=learnmore_list,
+ postprocess=lf_postprocess,
+ # Each of the following arguments is set here so that it is overridden when constructing template_kwds,
+ # which prioritizes values found in info (which are set in family_page() before calling render_family)
+ bread=lambda:None,
+ properties=lambda:None,
+ family=lambda:None,
+ friends=lambda:None,
+ downloads=lambda:None,
+)
+def render_family(info, query):
+ family = info["family"]
+ query["family"] = family.label_absolute
+ if family.n0 > 1:
+ query["subfield"] = {"$contains": family.oldbase}
+ #query["p"] = family.p
+ #query["visible"] = str(family.artin_slopes)
+ #query["f"] = 1 # TODO: Update to allow for tame extensions
+ #query["e"] = family.n
-class LFSearchArray(SearchArray):
- noun = "field"
- sorts = [("", "prime", ['p', 'n', 'c', 'num']),
- ("n", "degree", ['n', 'p', 'c', 'num']),
- ("c", "discriminant exponent", ['c', 'p', 'n', 'num']),
- ("e", "ramification index", ['e', 'n', 'p', 'c', 'num']),
- ("f", "residue degree", ['f', 'n', 'p', 'c', 'num']),
- ("gal", "Galois group", ['n', 'galT', 'p', 'c', 'num']),
- ("u", "Galois unramified degree", ['u', 'n', 'p', 'c', 'num']),
- ("t", "Galois tame degree", ['t', 'n', 'p', 'c', 'num']),
- ("s", "top slope", ['top_slope', 'p', 'n', 'c', 'num'])]
- jump_example = "2.4.6.7"
- jump_egspan = "e.g. 2.4.6.7"
- jump_knowl = "lf.search_input"
- jump_prompt = "Label"
+ parse_galgrp(info,query,'gal',qfield=('galois_label','n'))
+ parse_rats(info,query,'topslope',qfield='top_slope',name='Top Artin slope', process=ratproc)
+ parse_newton_polygon(info,query,"slopes", qfield="slopes_tmp", mode=info.get('slopes_quantifier'))
+ parse_newton_polygon(info,query,"ind_of_insep", qfield="ind_of_insep_tmp", mode=info.get('insep_quantifier'), reversed=True)
+ parse_bracketed_posints(info,query,"associated_inertia")
+ parse_bracketed_posints(info,query,"jump_set")
+ if 'one_per' in info and info['one_per'] == 'packet':
+ query["__one_per__"] = "packet"
+ parse_noop(info,query,"hidden")
- def __init__(self):
+def common_family_parse(info, query):
+ parse_ints(info,query,'p',name='Prime p')
+ parse_ints(info,query,'n',name='Degree')
+ parse_ints(info,query,'n0',name='Base degree')
+ parse_ints(info,query,'n_absolute',name='Absolute degree')
+ parse_ints(info,query,'e',name='Ramification index')
+ parse_ints(info,query,'e0',name='Base ramification index')
+ parse_ints(info,query,'e_absolute',name='Absolute ramification index')
+ parse_ints(info,query,'f',name='Residue field degree')
+ parse_ints(info,query,'f0',name='Base residue field degree')
+ parse_ints(info,query,'f_absolute',name='Absolute residue field degree')
+ parse_ints(info,query,'c',name='Discriminant exponent c')
+ parse_ints(info,query,'c0',name='Base discriminant exponent c')
+ parse_ints(info,query,'c_absolute',name='Absolute discriminant exponent c')
+ parse_ints(info,query,'w',name='Wild ramification exponent')
+ parse_regex_restricted(info,query,'base',regex=NEW_LF_RE,errknowl='lf.field.label',errtitle='label')
+ parse_noop(info,query,'label_absolute',name='Absolute label') # TODO: Add a regex here
+ parse_floats(info,query,'mass_relative',name='Mass', qfield='mass_relative')
+ parse_floats(info,query,'mass_absolute',name='Mass', qfield='mass_absolute')
+ parse_floats(info,query,'mass_found',name='Mass found')
+ parse_ints(info,query,'ambiguity',name='Ambiguity')
+ parse_ints(info,query,'field_count',name='Field count')
+ parse_ints(info,query,'wild_segments',name='Wild segments')
+ #parse_newton_polygon(info,query,"visible", qfield="visible_tmp", mode=info.get('visible_quantifier'))
+ parse_herbrand(info,query)
+
+@search_wrap(
+ table=db.lf_families,
+ columns=families_columns,
+ title='Absolute $p$-adic families search results',
+ titletag=lambda:'p-adic families search results',
+ err_title='p-adic families search input error',
+ learnmore=learnmore_list,
+ bread=lambda:get_bread([("Families", "")]),
+ postprocess=families_postprocess,
+ url_for_label=url_for_family,
+)
+def families_search(info, query):
+ if "relative" in info:
+ query["__title__"] = "Relative $p$-adic families search results"
+ else:
+ query["n0"] = 1
+ common_family_parse(info, query)
+
+def common_boxes():
+ degree = TextBox(
+ name='n',
+ label='Degree',
+ short_label='Degree $n$',
+ knowl='lf.degree',
+ example='6',
+ example_span='6, or a range like 3..5')
+ qp = TextBox(
+ name='p',
+ label=r'Residue field characteristic',
+ short_label='Residue characteristic $p$',
+ knowl='lf.residue_field',
+ example='3',
+ example_span='3, or a range like 3..7')
+ c = TextBox(
+ name='c',
+ label='Discriminant exponent',
+ short_label='Discriminant exponent $c$',
+ knowl='lf.discriminant_exponent',
+ example='8',
+ example_span='8, or a range like 2..6')
+ e = TextBox(
+ name='e',
+ label='Ramification index',
+ short_label='Ramification index $e$',
+ knowl='lf.ramification_index',
+ example='3',
+ example_span='3, or a range like 2..6')
+ f = TextBox(
+ name='f',
+ label='Residue field degree',
+ short_label='Residue field degree $f$',
+ knowl='lf.residue_field_degree',
+ example='3',
+ example_span='3, or a range like 2..6')
+ topslope = TextBox(
+ name='topslope',
+ label='Top Artin slope',
+ knowl='lf.top_slope',
+ example='4/3',
+ example_span='4/3, or a range like 3..5')
+ slopes_quantifier = SubsetBox(
+ name="slopes_quantifier",
+ )
+ slopes = TextBoxWithSelect(
+ name='slopes',
+ label='Galois Artin slopes',
+ short_label='Galois Artin',
+ knowl='lf.hidden_slopes',
+ select_box=slopes_quantifier,
+ example='[2,2,3]',
+ example_span='[2,2,3] or [3,7/2,4]')
+ visible_quantifier = SubsetBox(
+ name="visible_quantifier",
+ )
+ visible = TextBoxWithSelect(
+ name='visible',
+ label='Visible Artin slopes',
+ short_label='Visible Artin',
+ knowl='lf.slopes',
+ select_box=visible_quantifier,
+ example='[2,2,3]',
+ example_span='[2,2,3] or [2,3,17/4]')
+ insep_quantifier = SubsetBox(
+ name="insep_quantifier",
+ )
+ ind_insep = TextBoxWithSelect(
+ name='ind_of_insep',
+ label='Indices of insep.',
+ short_label='Indices',
+ knowl='lf.indices_of_inseparability',
+ select_box=insep_quantifier,
+ example='[1,1,0]',
+ example_span='[1,1,0] or [18,10,4,0]')
+ associated_inertia = TextBox(
+ name='associated_inertia',
+ label='Assoc. Inertia',
+ knowl='lf.associated_inertia',
+ example='[1,2,1]',
+ example_span='[1,2,1] or [1,1,1,1]')
+ jump_set = TextBox(
+ name='jump_set',
+ label='Jump Set',
+ knowl='lf.jump_set',
+ example='[1,2]',
+ example_span='[1,2] or [1,3,8]')
+ gal = TextBoxNoEg(
+ name='gal',
+ label='Galois group',
+ short_label='Galois group',
+ knowl='nf.galois_search',
+ example='5T3',
+ example_span='e.g. 8.3, C5 or 7T2')
+ aut = TextBox(
+ name='aut',
+ label='Num. automorphisms',
+ knowl='lf.automorphism_group',
+ example='1',
+ example_span='2, or a range like 2..3'
+ )
+ u = TextBox(
+ name='u',
+ label='Galois unramified degree',
+ short_label='Galois unram. degree $u$',
+ knowl='lf.unramified_degree',
+ example='3',
+ example_span='3, or a range like 1..4'
+ )
+ t = TextBox(
+ name='t',
+ label='Galois tame degree',
+ short_label='Galois tame degree $t$',
+ knowl='lf.tame_degree',
+ example='2',
+ example_span='2, or a range like 2..3'
+ )
+ inertia = TextBox(
+ name='inertia_gap',
+ label='Inertia subgroup',
+ knowl='lf.inertia_group_search',
+ example='3.1',
+ example_span='8.3, C5 or 7T2',
+ )
+ wild = TextBox(
+ name='wild_gap',
+ label='Wild inertia subgroup',
+ knowl='lf.wild_inertia_group_search',
+ example='4.1',
+ example_span='8.3, C5 or 7T2',
+ )
+ family = SneakyTextBox(
+ name='family',
+ label='Family',
+ knowl='lf.family',
+ )
+ packet = SneakyTextBox(
+ name='packet',
+ label='Packet',
+ knowl='lf.packet',
+ )
+ hidden = SneakyTextBox(
+ name="hidden",
+ label="Hidden content",
+ knowl="lf.slopes")
+ return degree, qp, c, e, f, topslope, slopes, visible, ind_insep, associated_inertia, jump_set, gal, aut, u, t, inertia, wild, family, packet, hidden
+
+class FamilySearchArray(EmbeddedSearchArray):
+ sorts = [
+ ("", "Label", ['ctr_subfamily', 'ctr']),
+ ("gal", "Galois group", ['galT', 'ctr_subfamily', 'ctr']),
+ ("s", "top slope", ['top_slope', 'ctr_subfamily', 'ctr']),
+ ("ind_of_insep", "Index of insep", ['ind_of_insep', 'ctr_subfamily', 'ctr']),
+ ]
+ def __init__(self, fam):
+ degree, qp, c, e, f, topslope, slopes, visible, ind_insep, associated_inertia, jump_set, gal, aut, u, t, inertia, wild, family, packet, hidden = common_boxes()
+ if fam.packet_count is None:
+ self.refine_array = [[gal, slopes, ind_insep, hidden], [associated_inertia, jump_set]]
+ else:
+ one_per = SelectBox(
+ name="one_per",
+ label="Fields per packet",
+ knowl="lf.packet",
+ options=[("", "all"),
+ ("packet", "one")])
+ self.refine_array = [[gal, slopes, ind_insep, hidden], [associated_inertia, jump_set, one_per]]
+
+class FamiliesSearchArray(SearchArray):
+ def __init__(self, relative=False):
+ #degree, qp, c, e, f, topslope, slopes, visible, ind_insep, associated_inertia, jump_set, gal, aut, u, t, inertia, wild, family, packet = common_boxes()
degree = TextBox(
name='n',
- label='Degree',
+ label='Rel. degree $n$' if relative else 'Degree $n$',
knowl='lf.degree',
example='6',
example_span='6, or a range like 3..5')
qp = TextBox(
name='p',
- label=r'Residue field characteristic',
- short_label='Residue characteristic',
+ label=r'Residue characteristic $p$',
knowl='lf.residue_field',
example='3',
example_span='3, or a range like 3..7')
c = TextBox(
name='c',
- label='Discriminant exponent',
+ label='Rel. disc. exponent $c$' if relative else 'Discriminant exponent $c$',
knowl='lf.discriminant_exponent',
example='8',
example_span='8, or a range like 2..6')
e = TextBox(
name='e',
- label='Ramification index',
+ label='Rel. ram. index $e$' if relative else 'Ramification index $e$',
knowl='lf.ramification_index',
example='3',
example_span='3, or a range like 2..6')
f = TextBox(
name='f',
- label='Residue field degree',
+ label='Rel. res. field degree $f$' if relative else 'Residue field degree $f$',
knowl='lf.residue_field_degree',
example='3',
example_span='3, or a range like 2..6')
- topslope = TextBox(
- name='topslope',
- label='Top slope',
- knowl='lf.top_slope',
- example='4/3',
- example_span='4/3, or a range like 3..5')
- slopes_quantifier = SubsetBox(
- name="slopes_quantifier",
- )
- slopes = TextBoxWithSelect(
- name='slopes',
- label='Wild slopes',
- short_label='Wild',
- knowl='lf.wild_slopes',
- select_box=slopes_quantifier,
- example='[2,2,3]',
- example_span='[2,2,3] or [3,7/2,4]')
- visible_quantifier = SubsetBox(
- name="visible_quantifier",
- )
- visible = TextBoxWithSelect(
- name='visible',
- label='Visible slopes',
- short_label='Visible',
- knowl='lf.visible_slopes',
- select_box=visible_quantifier,
- example='[2,2,3]',
- example_span='[2,2,3] or [2,3,17/4]')
- insep_quantifier = SubsetBox(
- name="insep_quantifier",
- )
- ind_insep = TextBoxWithSelect(
- name='ind_of_insep',
- label='Ind. of insep.',
- short_label='Indices',
- knowl='lf.indices_of_inseparability',
- select_box=insep_quantifier,
- example='[1,1,0]',
- example_span='[1,1,0] or [18,10,4,0]')
- associated_inertia = TextBox(
- name='associated_inertia',
- label='Assoc. Inertia',
- knowl='lf.associated_inertia',
- example='[1,2,1]',
- example_span='[1,2,1] or [1,1,1,1]')
- gal = TextBoxNoEg(
- name='gal',
- label='Galois group',
- short_label='Galois group',
- knowl='nf.galois_search',
- example='5T3',
- example_span='e.g. [8,3], 8.3, C5 or 7T2')
- u = TextBox(
- name='u',
- label='Galois unramified degree',
- knowl='lf.unramified_degree',
+ n0 = TextBox(
+ name='n0',
+ label='Base degree $n_0$',
+ knowl='lf.degree',
+ example='6',
+ example_span='6, or a range like 3..5')
+ c0 = TextBox(
+ name='c0',
+ label='Base disc. exponent $c_0$',
+ knowl='lf.discriminant_exponent',
+ example='8',
+ example_span='8, or a range like 2..6')
+ e0 = TextBox(
+ name='e0',
+ label='Base ram. index $e_0$',
+ knowl='lf.ramification_index',
example='3',
- example_span='3, or a range like 1..4'
- )
- t = TextBox(
- name='t',
- label='Galois tame degree',
- knowl='lf.tame_degree',
- example='2',
- example_span='2, or a range like 2..3'
- )
- inertia = TextBox(
- name='inertia_gap',
- label='Inertia subgroup',
- knowl='lf.inertia_group_search',
- example='[3,1]',
- example_span='e.g. [8,3], 8.3, C5 or 7T2',
- )
- wild = TextBox(
- name='wild_gap',
- label='Wild inertia subgroup',
- knowl='lf.wild_inertia_group_search',
- example='[4,1]',
- example_span='e.g. [8,3], 8.3, C5 or 7T2',
- )
+ example_span='3, or a range like 2..6')
+ f0 = TextBox(
+ name='f0',
+ label='Base res. field degree $f_0$',
+ knowl='lf.residue_field_degree',
+ example='3',
+ example_span='3, or a range like 2..6')
+ n_absolute = TextBox(
+ name='n_absolute',
+ label=r'Abs. degree $n_{\mathrm{abs}}$',
+ knowl='lf.degree',
+ example='6',
+ example_span='6, or a range like 3..5')
+ c_absolute = TextBox(
+ name='c_absolute',
+ label=r'Abs. disc. exponent $c_{\mathrm{abs}}$',
+ knowl='lf.discriminant_exponent',
+ example='8',
+ example_span='8, or a range like 2..6')
+ e_absolute = TextBox(
+ name='e_absolute',
+ label=r'Abs. ram. index $e_{\mathrm{abs}}$',
+ knowl='lf.ramification_index',
+ example='3',
+ example_span='3, or a range like 2..6')
+ f_absolute = TextBox(
+ name='f_absolute',
+ label=r'Abs. res. field degree $f_{\mathrm{abs}}$',
+ knowl='lf.residue_field_degree',
+ example='3',
+ example_span='3, or a range like 2..6')
+ #w = TextBox(
+ # name='w',
+ # label='Wild ramification exponent $w$',
+ # knowl='lf.ramification_index',
+ # example='3',
+ # example_span='3, or a range like 2..6')
+ base = TextBox(
+ name='base',
+ label='Base',
+ knowl='lf.family_base',
+ example='2.2.1.0a1.1')
+ mass_relative = TextBox(
+ name='mass_relative',
+ label='Mass',
+ knowl='lf.family_mass',
+ example='255',
+ example_span='9/2, or a range like 1-10')
+ mass_absolute = TextBox(
+ name='mass_absolute',
+ label='Absolute mass',
+ knowl='lf.family_mass',
+ example='255/8',
+ example_span='9/2, or a range like 1-10')
+ #mass_found = TextBox(
+ # name='mass_found',
+ # label='Mass found',
+ # knowl='lf.family_mass',
+ # example='0.5-',
+ # example_span='0, or a range like 0.1-0.4')
+ ambiguity = TextBox(
+ name='ambiguity',
+ label='Ambiguity',
+ knowl='lf.family_ambiguity',
+ example='1',
+ example_span='1, or a range like 2-8')
+ field_count = TextBox(
+ name='field_count',
+ label='Field count',
+ knowl='lf.family_field_count',
+ example='1',
+ example_span='2, or a range like 2-8')
+ wild_segments = TextBox(
+ name='wild_segments',
+ label='Wild segments',
+ knowl='lf.wild_segments',
+ example='1',
+ example_span='2, or a range like 2-4')
+ label_absolute = SneakyTextBox(
+ name='label_absolute',
+ label='Absolute label',
+ knowl='lf.family_label',
+ example='2.1.4.6a')
+ herbrand = TextBox(
+ name='herbrand',
+ label='Herbrand invariant',
+ knowl='lf.herbrand_input',
+ example='[1/3,1/3]')
+ if relative:
+ relbox = HiddenBox("relative", "")
+ self.refine_array = [[qp, degree, e, f, c],
+ [base, n0, e0, f0, c0],
+ [herbrand, n_absolute, e_absolute, f_absolute, c_absolute],
+ #[visible, slopes, rams, means, slope_multiplicities],
+ [mass_relative, mass_absolute, ambiguity, field_count, wild_segments, relbox],
+ [label_absolute]]
+ self.sorts = [
+ ("", "base", ['p', 'n0', 'e0', 'c0', 'ctr0_family', 'ctr0_subfamily', 'ctr0', 'n', 'e', 'c', 'ctr']),
+ ("c", "discriminant exponent", ['c', 'p', 'n', 'e', 'n0', 'e0', 'c0', 'ctr0_family', 'ctr0_subfamily', 'ctr0', 'ctr']),
+ ("top_slope", "top slope", ['top_slope', 'slopes', 'visible', 'p', 'n0', 'e0', 'c0', 'ctr0_family', 'ctr0_subfamily', 'ctr0', 'n', 'e', 'c', 'ctr']),
+ ("ambiguity", "ambiguity", ['ambiguity', 'p', 'n0', 'e0', 'c0', 'ctr0_family', 'ctr0_subfamily', 'ctr0', 'n', 'e', 'c', 'ctr']),
+ ("field_count", "num fields", ['field_count', 'p', 'n0', 'e0', 'c0', 'ctr0_family', 'ctr0_subfamily', 'ctr0', 'n', 'e', 'c', 'ctr']),
+ ("mass", "mass", ['mass_relative', 'p', 'n0', 'e0', 'c0', 'ctr0_family', 'ctr0_subfamily', 'ctr0', 'n', 'e', 'c', 'ctr']),
+ #("mass_found", "mass found", ['mass_found', 'mass_relative', 'p', 'n0', 'e0', 'c0', 'n', 'e', 'c', 'ctr']),
+ ]
+ else:
+ self.refine_array = [[qp, degree, e, f, c],
+ [mass_relative, ambiguity, field_count, wild_segments, herbrand]]
+ self.sorts = [
+ ("", "label", ['p', 'n', 'e', 'c', 'ctr']),
+ ("c", "discriminant exponent", ['c', 'p', 'n', 'e', 'ctr']),
+ ("top_slope", "top slope", ['top_slope', 'slopes', 'visible', 'p', 'n', 'e', 'c', 'ctr']),
+ ("ambiguity", "ambiguity", ['p', 'n', 'ambiguity', 'e', 'c', 'ctr']),
+ ("field_count", "num fields", ['p', 'n', 'field_count', 'e', 'c', 'ctr']),
+ ("mass", "mass", ['mass_relative', 'p', 'n', 'e', 'c', 'ctr']),
+ #("mass_found", "mass found", ['mass_found', 'mass_relative', 'p', 'n', 'e', 'c', 'ctr']),
+ ]
+
+
+ def search_types(self, info):
+ return self._search_again(info, [
+ ('Families', 'List of families'),
+ ('FamilyCounts', 'Counts table'),
+ ('RandomFamily', 'Random family')])
+
+ def _buttons(self, info):
+ if self._st(info) == "FamilyCounts":
+ return []
+ return super()._buttons(info)
+
+class LFSearchArray(SearchArray):
+ noun = "field"
+
+ sorts = [("", "prime", ['p', 'n', 'e', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("n", "degree", ['n', 'e', 'p', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("c", "discriminant exponent", ['c', 'p', 'n', 'e', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("e", "ramification index", ['n', 'e', 'p', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("f", "residue degree", ['f', 'n', 'p', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("gal", "Galois group", ['n', 'galT', 'p', 'e', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("u", "Galois unramified degree", ['u', 'f', 'n', 'p', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("t", "Galois tame degree", ['t', 'e', 'n', 'p', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("s", "top slope", ['top_slope', 'p', 'n', 'e', 'c', 'ctr_family', 'ctr_subfamily', 'ctr']),
+ ("jump", "jump set", ['jump_set', 'p', 'n', 'e', 'c', 'ctr_family', 'ctr_subfamily', 'ctr'])]
+ jump_example = "2.1.4.6a2.1"
+ jump_egspan = "e.g. 2.1.4.6a2.1"
+ jump_knowl = "lf.search_input"
+ jump_prompt = "Label"
+ null_column_explanations = {
+ 'packet': False, # If a packet is stored, it's complete since we don't record packets unless all hidden info known for that subfamily
+ }
+
+ def __init__(self):
+ degree, qp, c, e, f, topslope, slopes, visible, ind_insep, associated_inertia, jump_set, gal, aut, u, t, inertia, wild, family, packet, hidden = common_boxes()
results = CountBox()
self.browse_array = [[degree, qp], [e, f], [c, topslope], [u, t],
- [slopes, visible], [ind_insep, associated_inertia], [gal, inertia], [wild], [results]]
- self.refine_array = [[degree, qp, gal, u, associated_inertia],
- [e, c, inertia, t, ind_insep],
- [f, topslope, slopes, visible, wild]]
+ [slopes, visible], [ind_insep, associated_inertia],
+ [jump_set, aut], [gal, inertia], [wild], [results]]
+ self.refine_array = [[degree, qp, c, gal],
+ [e, f, t, u],
+ [aut, inertia, ind_insep, associated_inertia, jump_set],
+ [topslope, slopes, visible, wild],
+ [family, packet, hidden]]
+
+ def search_types(self, info):
+ return self._search_again(info, [
+ ('List', 'List of fields'),
+ ('Counts', 'Counts table'),
+ ('Random', 'Random field')])
+
+ def _buttons(self, info):
+ if self._st(info) == "Counts":
+ return []
+ return super()._buttons(info)
def ramdisp(p):
return {'cols': ['n', 'e'],
- 'constraint': {'p': p, 'n': {'$lte': 15}},
+ 'constraint': {'p': p, 'n': {'$lte': 23}},
'top_title':[('degree', 'lf.degree'),
('and', None),
('ramification index', 'lf.ramification_index'),
@@ -792,7 +1771,7 @@ def ramdisp(p):
def discdisp(p):
return {'cols': ['n', 'c'],
- 'constraint': {'p': p, 'n': {'$lte': 15}},
+ 'constraint': {'p': p, 'n': {'$lte': 23}},
'top_title':[('degree', 'lf.degree'),
('and', None),
('discriminant exponent', 'lf.discriminant_exponent'),
@@ -814,20 +1793,23 @@ def galcache():
return knowl_cache(db.lf_fields.distinct("galois_label"))
def galformatter(gal):
n, t = galdata(gal)
- return group_pretty_and_nTj(n, t, True, cache=galcache())
+ return '' + group_pretty_and_nTj(n, t, True, cache=galcache()).replace("(as", ' (as') + " "
class LFStats(StatsDisplay):
table = db.lf_fields
baseurl_func = ".index"
short_display = {'galois_label': 'Galois group',
'n': 'degree',
'e': 'ramification index',
- 'c': 'discriminant exponent'}
+ 'c': 'discriminant exponent',
+ 'hidden': 'hidden slopes'}
sort_keys = {'galois_label': galdata}
formatters = {
- 'galois_label': galformatter
+ 'galois_label': galformatter,
+ 'hidden': latex_content,
}
query_formatters = {
- 'galois_label': (lambda gal: r'gal=%s' % (galunformatter(gal)))
+ 'galois_label': (lambda gal: r'gal=%s' % (galunformatter(gal))),
+ 'hidden': (lambda hid: r'hidden=%s' % (content_unformatter(hid))),
}
stat_list = [
@@ -841,26 +1823,56 @@ class LFStats(StatsDisplay):
galdisp(2, 10),
galdisp(2, 12),
galdisp(2, 14),
+ galdisp(2, 16),
+ galdisp(2, 18),
+ galdisp(2, 20),
+ galdisp(2, 22),
galdisp(3, 6),
galdisp(3, 9),
galdisp(3, 12),
galdisp(3, 15),
+ galdisp(3, 18),
+ galdisp(3, 21),
galdisp(5, 10),
galdisp(5, 15),
- galdisp(7, 14)
+ galdisp(5, 20),
+ galdisp(7, 14),
+ galdisp(7, 21),
+ galdisp(11, 22),
]
def __init__(self):
self.numfields = db.lf_fields.count()
+ self.num_abs_families = db.lf_families.count({"n0":1})
+ self.num_rel_families = db.lf_families.count({"n0":{"$gt": 1}})
+
+ @staticmethod
+ def dynamic_parse(info, query):
+ from .main import common_parse
+ common_parse(info, query)
+
+ dynamic_parent_page = "padic-refine-search.html"
+ dynamic_cols = ["galois_label", "slopes"]
@property
def short_summary(self):
- return self.summary + ' Here are some further statistics .' % (url_for(".statistics"))
+ return 'The database currently contains %s %s, %s absolute %s, and %s relative families. Here are some further statistics .' % (
+ comma(self.numfields),
+ display_knowl("lf.padic_field", r"$p$-adic fields"),
+ comma(self.num_abs_families),
+ display_knowl("lf.family_polynomial", "families"),
+ comma(self.num_rel_families),
+ url_for(".statistics"),
+ )
@property
def summary(self):
- return r'The database currently contains %s %s, including all with $p < 200$ and %s $n < 24$.' % (
+ return r'The database currently contains %s %s, including all with $p < 200$ and %s $n < 24$. It also contains all %s absolute %s with $p < 200$ and degree $n < 48$, as well as all %s relative families with $p < 200$, base degree $n_0 < 16$ and absolute degree $n_{\mathrm{absolute}} < 48$.' % (
comma(self.numfields),
display_knowl("lf.padic_field", r"$p$-adic fields"),
- display_knowl("lf.degree", "degree")
+ display_knowl("lf.degree", "degree"),
+ comma(self.num_abs_families),
+ display_knowl("lf.family_polynomial", "families"),
+ comma(self.num_rel_families),
)
+
diff --git a/lmfdb/local_fields/templates/lf-count-results.html b/lmfdb/local_fields/templates/lf-count-results.html
new file mode 100644
index 0000000000..97aca13ee9
--- /dev/null
+++ b/lmfdb/local_fields/templates/lf-count-results.html
@@ -0,0 +1,13 @@
+{% extends 'padic-refine-search.html' %}
+
+{% block top_matter %}
+
+ Fields with $n>16$ and $p \mid n$ have not yet been added, except $n=p$ and $(p,n) = (2,18)$. Not all Galois groups and hidden slope information has been computed for $(p,n) = (2,16)$.
+
+{% endblock %}
+
+{% block show_results %}
+
+{% include 'count_results.html' %}
+
+{% endblock %}
diff --git a/lmfdb/local_fields/templates/lf-families.html b/lmfdb/local_fields/templates/lf-families.html
new file mode 100644
index 0000000000..e825a86fcd
--- /dev/null
+++ b/lmfdb/local_fields/templates/lf-families.html
@@ -0,0 +1,32 @@
+{% extends "homepage.html" %}
+
+{% block content %}
+
+
+
+ Label
+ $e$
+ $f$
+ $c$
+ Visible Slopes
+ Heights
+ Rams
+ Num. Poly
+ Num. Fields
+
+ {% for family in families %}
+
+ {{family.link|safe}}
+ ${{family.e}}$
+ ${{family.f}}$
+ ${{family.c}}$
+ ${{family.artin_slopes}}$
+ ${{family.heights}}$
+ ${{family.rams}}$
+ ${{family.poly_count}}$
+ ${{family.field_count}}$
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/lmfdb/local_fields/templates/lf-family.html b/lmfdb/local_fields/templates/lf-family.html
new file mode 100644
index 0000000000..9728bf1911
--- /dev/null
+++ b/lmfdb/local_fields/templates/lf-family.html
@@ -0,0 +1,141 @@
+{% extends 'embedded_results.html' %}
+
+{% block embedding_content %}
+
+{% if family.e > 1 %}
+ {{ KNOWL('lf.family_polynomial', 'Defining polynomial') }}{% if family.f > 1 %} over unramified subextension{% endif %}
+
+
+ ${{ family.polynomial._latex_() }}$
+
+{% endif %}
+
+ {{ KNOWL('lf.family_invariants', 'Invariants') }}
+
+ {{ KNOWL('lf.residue_field', 'Residue field characteristic') }}: ${{family.p}}$
+ {{ KNOWL('lf.degree', 'Degree') }}: ${{family.n}}$
+ {{ KNOWL('lf.family_base', 'Base field') }}: {{family.base_link | safe}}
+ {{ KNOWL('lf.ramification_index', 'Ramification index') }} $e$: ${{family.e}}$
+ {{ KNOWL('lf.residue_field_degree', 'Residue field degree') }} $f$: ${{family.f}}$
+ {{ KNOWL('lf.discriminant_exponent', 'Discriminant exponent') }} $c$: ${{family.c}}$
+ {{ KNOWL('lf.slopes', 'Absolute Artin slopes' if family.n0 > 1 else 'Artin slopes') }}: {{info.latex_content(family.artin_slopes)}}
+ {{ KNOWL('lf.slopes', 'Swan slopes') }}: {{info.latex_content(family.slopes)}}
+ {{ KNOWL('lf.means', 'Means') }}: {{family.means_display}}
+ {{ KNOWL('lf.rams', 'Rams') }}: {{family.rams_display}}
+ {{ KNOWL('lf.family_field_count', 'Field count') }}: ${{family.field_count}}$ {% if family.all_stored %} (complete) {% else %} (incomplete) {% endif %}
+ {{ KNOWL('lf.family_ambiguity', 'Ambiguity') }}: ${{family.ambiguity}}$
+ {{ KNOWL('lf.family_mass', 'Mass') }}: ${{family.mass_relative_display}}$
+ {{ KNOWL('lf.family_mass', 'Absolute Mass') }}: ${{family.mass_absolute_display}}$ {% if not family.all_stored %}(${{family.mass_stored}}$ currently in the LMFDB){% endif %}
+
+
+{% if family.w > 0 %}
+ {{ KNOWL('lf.family_diagrams', 'Diagrams') }}
+
+
+ Eisenstein diagram
+ Ramification polygon
+ Herbrand function
+
+
+
+
+
+
+
+
+
+
+{% endif %}
+
+{% if family.field_count > 0 %}
+
+ {{ KNOWL('lf.family_varying', 'Varying') }}
+{% if not family.all_stored %}
+The following invariants arise for fields within the LMFDB; since not all fields in this family are stored, it may be incomplete.
+{% endif %}
+{% if family.n0 > 1 %}
+These invariants are all associated to absolute extensions of $\Q_{ {{family.p}} }$ within this relative family, not the relative extension.
+{% endif %}
+
+ {% if family.n0 > 1 and family.some_hidden_data_available %}
+ {{ KNOWL('nf.galois_group', 'Galois group') }}: {{ family.galois_groups | safe }}
+ {{ KNOWL('lf.hidden_slopes', 'Hidden Artin slopes') }}: {{ family.hidden_slopes | safe }}
+ {% endif %}
+ {{ KNOWL('lf.indices_of_inseparability', 'Indices of inseparability') }}: {{ family.indices_of_insep | safe }}
+ {{ KNOWL('lf.associated_inertia', 'Associated inertia') }}: {{ family.associated_inertia | safe }}
+ {{ KNOWL('lf.jump_set', 'Jump Set') }}: {{ family.jump_set | safe }}
+
+
+{% if family.n0 == 1 and family.some_hidden_data_available %}
+ {{ KNOWL('lf.packet', 'Galois groups and Hidden Artin slopes') }}
+
+{% if (family.gal_slope_tables|length) > 1 %}
+ Select desired size of Galois group.{% if not family.all_hidden_data_available %} Note that the following data has not all been computed for fields in this family, so the tables below are incomplete.{% endif %}
+
+ {% for N, d in family.gal_slope_tables %}
+ {{N}}
+ {% endfor %}
+ all
+
+{% elif not family.all_hidden_data_available %}
+ Note that the following data has not all been computed for fields in this family, so the table below is incomplete.
+{% endif %}
+
+{% for N, d in family.gal_slope_tables %}
+
+ {% include 'stat_2d.html' %}
+
+{% endfor %}
+
+{% endif %} {# family.n0 == 1 #}
+
+ {{ KNOWL('lf.family_polynomial', 'Fields') }}
+
+{% else %} {# family.field_count = 0 #}
+
+
+ The LMFDB does not contain any fields from this family.
+
+
+{% endif %}
+
+
+
+{% endblock %}
+
+{% block post_results_content %}
+
+{#
+{% if family.poly_count < 100 %}
+ Polynomials
+
+
+ {% for poly in family %}
+ ${{ poly._latex_() }}$
+ {% endfor %}
+
+
+{% endif %}
+#}
+
+{% endblock %}
diff --git a/lmfdb/local_fields/templates/lf-index.html b/lmfdb/local_fields/templates/lf-index.html
index 99a8eb83fb..4c3ddbcd92 100644
--- a/lmfdb/local_fields/templates/lf-index.html
+++ b/lmfdb/local_fields/templates/lf-index.html
@@ -1,3 +1,4 @@
+
{% extends "homepage.html" %}
{% block content %}
@@ -6,85 +7,58 @@
{{ info.stats.short_summary | safe }}
-Browse
+Browse fields
-
-The table gives for each $p$ and $n$ shown, the number of degree $n$ extension
-fields
-of $\Q_p$ up to isomorphism.
+
+This table gives, for each $p$ and $n$ shown, the number of degree $n$ extension
+fields of $\Q_p$ up to isomorphism. Here are more counts for larger $p$ and $n$.
-
-
+{# We want the columns in the field and family tables to align, which requires some circumlocutions (we end up making them one table, but have to mess with the layout to insert a header and paragraph in between) #}
+
+
-
-$p$ \ $n$
-2 3 4 5 6
-7 8 9 10 11
-12 13 14 15
-
+
+ $p$ \ $n$
+ {% for n in range(2,24) %}
+ {{n}}
+ {% endfor %}
+
-
-2 7 2 59 2 47 2 1823 3 158 2 5493
-2
-590
-4
-
-
-3
-3 10 5 2 75 2 8 795 6 2 785 2
-6
-1172
-
-
-5
-3 2 7 26 7 2 11 3 258 2 17 2
-6
-1012
-
-
-7
-3 4 5 2 12 50 8 7 6 2 20 2
-654
-8
-
-
-11
-3 2 5 6 7 2 8 3 18 122 13 2
-6
-12
-
-
-13
-3 4 7 2 12 2 11 7 6 2 28 170
-9
-8
-
-
-17
-3 2 7 2 7 2 15 3 6 2 17 2
-6
-4
-
-
-19
-3 4 5 2 12 2 8 13 8 2 20 2
-6
-8
-
-
-23
-3 2 5 2 7 2 8 3 6 12 13 2
-6
-4
-
-
+ {% for p in [2,3,5,7] %}
+
+ {{p}}
+ {% for n in range(2,24) %}
+ {{info.field_count[n,p]}}
+ {% endfor %}
+
+ {% endfor %}
+
+
+ Browse families
+ This table gives the number of degree $n$ {{KNOWL('lf.family_polynomial', 'families')}} over $\Q_p$. Here are more counts for larger $p$ and $n$, and search pages for absolute families and relative families .
+
+
+
+ $p$ \ $n$
+ {% for n in range(2,24) %}
+ {{n}}
+ {% endfor %}
+
+ {% for p in [2,3,5,7] %}
+
+ {{p}}
+ {% for n in range(2,24) %}
+ {{info.family_count[n,p]}}
+ {% endfor %}
+
+ {% endfor %}
+
-
-Some interesting $p$-adic fields or a random $p$-adic field
+Some interesting $p$-adic fields , a random $p$-adic field , a random absolute family , a random relative family
{{KNOWL('intro.search', 'Search')}}
diff --git a/lmfdb/local_fields/templates/lf-show-field.html b/lmfdb/local_fields/templates/lf-show-field.html
index 062be05c83..b7d34ff596 100644
--- a/lmfdb/local_fields/templates/lf-show-field.html
+++ b/lmfdb/local_fields/templates/lf-show-field.html
@@ -12,7 +12,7 @@ {{ KNOWL('lf.invariants', title='Invariants') }}
Base field: {{info.base|safe}}
{{ KNOWL('lf.degree', title='Degree') }} $d$: ${{info.n}}$
- {{ KNOWL('lf.ramification_index', title='Ramification exponent') }} $e$: ${{info.e}}$
+ {{ KNOWL('lf.ramification_index', title='Ramification index') }} $e$: ${{info.e}}$
{{ KNOWL('lf.residue_field_degree', title='Residue field degree') }} $f$: ${{info.f}}$
{{ KNOWL('lf.discriminant_exponent', title='Discriminant exponent') }} $c$: ${{info.c}}$
{{ KNOWL('lf.discriminant_root_field', title='Discriminant root field') }}: {{info.rf|safe}}
@@ -37,7 +37,12 @@ {{ KNOWL('lf.invariants', title='Invariants') }}
{% endif %}
{{info.galphrase}}
- {{ KNOWL('lf.visible_slopes', title='Visible slopes')}}: {{info.visible}}
+ {{ KNOWL('lf.slopes', title='Visible Artin slopes')}}: {{info.visible}}
+ {{ KNOWL('lf.slopes', title='Visible Swan slopes')}}: {{info.visible_swan}}
+ {{ KNOWL('lf.means', title='Means')}}: {{info.means}}
+ {{ KNOWL('lf.rams', title='Rams')}}: {{info.rams}}
+ {{ KNOWL('lf.jump_set', title='Jump set')}}: {{info.jump_set}}
+ {{ KNOWL('lf.roots_of_unity', title='Roots of unity') }}: {{info.roots_of_unity}}
{{ KNOWL('lf.intermediate_fields', title='Intermediate fields') }}
@@ -60,7 +65,7 @@ {{ KNOWL('lf.intermediate_fields', title='Intermediate fields') }}
{% endif %}
-{{ KNOWL('lf.unramified_totally_ramified_tower', title='Unramified/totally ramified tower') }}
+{{ KNOWL('lf.unramified_totally_ramified_tower', title='Canonical tower') }}
{{ KNOWL('lf.unramified_subfield', title='Unramified subfield')}}: {{info.unram|safe}}
Relative {{ KNOWL('lf.eisenstein_polynomial', 'Eisenstein polynomial')}}: {{info.eisen|safe}}
@@ -71,7 +76,7 @@ {{ KNOWL('lf.ramification_polygon_display', title='Ramification polygon') }}
{% if info.e > 1 %}
- {% if info.ram_poly_polt %}
+ {% if info.ram_polygon_plot %}
@@ -80,7 +85,7 @@ {{ KNOWL('lf.ramification_polygon_display', title='Ramification polygon') }}
{{ KNOWL('lf.indices_of_inseparability', 'Indices of inseparability')}}: {{info.ind_insep|safe}}
{% else %}
- Not computed
+ not computed
{% endif %}
@@ -90,55 +95,68 @@ {{ KNOWL('lf.ramification_polygon_display', title='Ramification polygon') }}
{% endif %}
-{{ KNOWL('lf.galois_invariants', title='Invariants of the Galois closure') }}
+{{ KNOWL('lf.galois_closure_invariants', title='Invariants of the Galois closure') }}
+ {{ KNOWL('nf.galois_group', title='Galois degree')}}:
+ {% if info.galois_degree %}
+ ${{info.galois_degree}}$
+ {% else %}
+ not computed
+ {% endif %}
+
{{ KNOWL('nf.galois_group', title='Galois group')}}:
{% if info.gal %}
- {{info.gal|safe}}
+ {{info.gal|safe}}
{% else %}
- Not computed
+ not computed
{% endif %}
{{ KNOWL('lf.inertia_group', title='Inertia group')}}:
{% if info.inertia %}
{{info.inertia|safe}}
{% else %}
- Not computed
+ not computed
{% endif %}
{{ KNOWL('lf.wild_inertia_group', title='Wild inertia group')}}:
{% if info.wild_inertia %}
{{info.wild_inertia|safe}}
{% else %}
- Not computed
+ not computed
{% endif %}
- {{ KNOWL('lf.unramified_degree', title='Unramified degree')}}:
+ {{ KNOWL('lf.unramified_degree', title='Galois unramified degree')}}:
{% if info.u %}
${{info.u}}$
{% else %}
- Not computed
+ not computed
{% endif %}
- {{ KNOWL('lf.tame_degree', title='Tame degree')}}:
+ {{ KNOWL('lf.tame_degree', title='Galois tame degree')}}:
{% if info.t %}
${{info.t}}$
{% else %}
- Not computed
+ not computed
{% endif %}
- {{ KNOWL('lf.wild_slopes', title='Wild slopes')}}:
+ {{ KNOWL('lf.slopes', title='Galois Artin slopes')}}:
{% if info.slopes %}
{{info.slopes}}
{% else %}
- Not computed
+ not computed
+ {% endif %}
+ {{ KNOWL('lf.slopes', title='Galois Swan slopes')}}:
+ {% if info.slopes %}
+ {{info.swanslopes}}
+ {% else %}
+ not computed
{% endif %}
{{ KNOWL('lf.galois_mean_slope', title='Galois mean slope')}}:
{% if info.gms %}
${{info.gms}}$
{% else %}
- Not computed
+ not computed
{% endif %}
{{KNOWL('lf.galois_splitting_model', title='Galois splitting model')}}: {{info.gsm|safe}}
diff --git a/lmfdb/local_fields/templates/padic-refine-search.html b/lmfdb/local_fields/templates/padic-refine-search.html
new file mode 100644
index 0000000000..0d95f997d9
--- /dev/null
+++ b/lmfdb/local_fields/templates/padic-refine-search.html
@@ -0,0 +1,18 @@
+{% extends 'homepage.html' %}
+
+{% block content %}
+
+{% if info.stats %}
+
+{{info.stats|safe}}
+
+{% endif %}
+
+{% block top_matter %}{% endblock %}
+
+{% include 'refine_search_form.html' %}
+
+{% block show_results %}{% endblock %}
+{% include 'debug_info.html' %}
+
+{% endblock %}
diff --git a/lmfdb/local_fields/test_localfields.py b/lmfdb/local_fields/test_localfields.py
index f7650545c9..7653e79734 100644
--- a/lmfdb/local_fields/test_localfields.py
+++ b/lmfdb/local_fields/test_localfields.py
@@ -12,50 +12,50 @@ def test_search_ramif_cl_deg(self):
def test_search_f(self):
L = self.tc.get('/padicField/?n=6&p=2&f=3')
dat = L.get_data(as_text=True)
- assert '2.6.4.1' not in dat
- assert '2.6.6.2' in dat
+ assert '2.2.3.4a1.1' not in dat
+ assert '2.3.2.6a1.2' in dat
def test_search_top_slope(self):
L = self.tc.get('/padicField/?p=2&topslope=3.5')
- assert '2.4.9.1' in L.get_data(as_text=True) # number of matches
+ assert '2.1.4.9a1.1' in L.get_data(as_text=True) # number of matches
L = self.tc.get('/padicField/?p=2&topslope=3.4..3.55')
- assert '2.4.9.1' in L.get_data(as_text=True) # number of matches
+ assert '2.1.4.9a1.1' in L.get_data(as_text=True) # number of matches
L = self.tc.get('/padicField/?p=2&topslope=7/2')
- assert '2.4.9.1' in L.get_data(as_text=True) # number of matches
+ assert '2.1.4.9a1.1' in L.get_data(as_text=True) # number of matches
def test_field_page(self):
- L = self.tc.get('/padicField/11.6.4.2')
- assert '11.6.4.2' in L.get_data(as_text=True)
- #assert 'x^{2} - x + 7' in L.get_data(as_text=True) # bad (not robust) test, but it's the best i was able to find...
- #assert 'x^{3} - 11 t' in L.get_data(as_text=True) # bad (not robust) test, but it's the best i was able to find...
+ L = self.tc.get('/padicField/11.6.4.2', follow_redirects=True)
+ assert '11.2.3.4a1.1' in L.get_data(as_text=True)
+ assert 'x^{2} + 7 x + 2' in L.get_data(as_text=True) # bad (not robust) test, but it's the best i was able to find...
+ assert 'x^{3} + 44 t + 99' in L.get_data(as_text=True) # bad (not robust) test, but it's the best i was able to find...
def test_global_splitting_models(self):
# The first one will have to change if we compute a GSM for it
- L = self.tc.get('/padicField/163.8.7.2')
- assert 'Not computed' in L.get_data(as_text=True)
- L = self.tc.get('/padicField/2.8.0.1')
+ L = self.tc.get('/padicField/163.1.8.7a1.2')
+ assert 'not computed' in L.get_data(as_text=True)
+ L = self.tc.get('/padicField/2.8.1.0a1.1')
assert 'Does not exist' in L.get_data(as_text=True)
def test_underlying_data(self):
- page = self.tc.get('/padicField/11.6.4.2').get_data(as_text=True)
- assert 'Underlying data' in page and 'data/11.6.4.2' in page
+ page = self.tc.get('/padicField/11.2.3.4a1.2').get_data(as_text=True)
+ assert 'Underlying data' in page and 'data/11.2.3.4a1.2' in page
def test_search_download(self):
page = self.tc.get('/padicField/?Submit=gp&download=1&query=%7B%27p%27%3A+2%2C+%27n%27%3A+2%7D&n=2&p=2').get_data(as_text=True)
- assert '''columns = ["label", "coeffs", "p", "e", "f", "c", "gal", "slopes"];
+ assert '''columns = ["label", "coeffs", "p", "f", "e", "c", "gal", "slopes"];
data = {[
-["2.2.0.1", [1, 1, 1], 2, 1, 2, 0, [2, 1], [[], 1, 2]],
-["2.2.2.1", [2, 2, 1], 2, 2, 1, 2, [2, 1], [[2], 1, 1]],
-["2.2.2.2", [6, 2, 1], 2, 2, 1, 2, [2, 1], [[2], 1, 1]],
-["2.2.3.1", [2, 4, 1], 2, 2, 1, 3, [2, 1], [[3], 1, 1]],
-["2.2.3.2", [10, 4, 1], 2, 2, 1, 3, [2, 1], [[3], 1, 1]],
-["2.2.3.3", [2, 0, 1], 2, 2, 1, 3, [2, 1], [[3], 1, 1]],
-["2.2.3.4", [10, 0, 1], 2, 2, 1, 3, [2, 1], [[3], 1, 1]]
+["2.2.1.0a1.1", [1, 1, 1], 2, 2, 1, 0, [2, 1], [[], 1, 2]],
+["2.1.2.2a1.1", [2, 2, 1], 2, 1, 2, 2, [2, 1], [[2], 1, 1]],
+["2.1.2.2a1.2", [6, 2, 1], 2, 1, 2, 2, [2, 1], [[2], 1, 1]],
+["2.1.2.3a1.1", [2, 0, 1], 2, 1, 2, 3, [2, 1], [[3], 1, 1]],
+["2.1.2.3a1.2", [10, 0, 1], 2, 1, 2, 3, [2, 1], [[3], 1, 1]],
+["2.1.2.3a1.3", [2, 4, 1], 2, 1, 2, 3, [2, 1], [[3], 1, 1]],
+["2.1.2.3a1.4", [10, 4, 1], 2, 1, 2, 3, [2, 1], [[3], 1, 1]]
]};
create_record(row) =
{
- out = Map(["label",row[1];"coeffs",row[2];"p",row[3];"e",row[4];"f",row[5];"c",row[6];"gal",row[7];"slopes",row[8]]);
+ out = Map(["label",row[1];"coeffs",row[2];"p",row[3];"f",row[4];"e",row[5];"c",row[6];"gal",row[7];"slopes",row[8]]);
field = Polrev(mapget(out, "coeffs"));
mapput(~out, "field", field);
return(out);''' in page
diff --git a/lmfdb/number_fields/templates/nf-show-field.html b/lmfdb/number_fields/templates/nf-show-field.html
index d1787ab36d..cb4ca30a9b 100644
--- a/lmfdb/number_fields/templates/nf-show-field.html
+++ b/lmfdb/number_fields/templates/nf-show-field.html
@@ -278,7 +278,7 @@ {{ KNOWL('nf.sibling', title='Sibling') }} fields
{{KNOWL("lf.residue_field_degree",title="$f$")}}
{{KNOWL("lf.discriminant_exponent",title="$c$")}}
{{KNOWL("nf.galois_group",title="Galois group")}}
- {{KNOWL("lf.slope_content",title="Slope content")}}
+ {{KNOWL("lf.slopes",title="Slope content")}}
{{ info.loc_alg | safe }}
diff --git a/lmfdb/static/groups.js b/lmfdb/static/groups.js
index ecf99ddbf2..a16d8a9310 100644
--- a/lmfdb/static/groups.js
+++ b/lmfdb/static/groups.js
@@ -159,18 +159,6 @@ function getpositions() {
}
var styles=['subgroup_diagram', 'subgroup_profile', 'subgroup_autdiagram', 'subgroup_autprofile', 'normal_diagram', 'normal_profile', 'normal_autdiagram', 'normal_autprofile'];
-function button_on(who) {
- $('button.'+who).css('background', '{{color.lf_an_button_bkg}}');
- $('button.'+who).css('border', '2px solid {{color.lf_an_button_brd}}');
-}
-function other_buttons_off(keep) {
- for (var i = 0; i < styles.length; i++) {
- if(styles[i] != keep) {
- $('button.'+styles[i]).css('background', '{{color.lf_ar_button_bkg}}');
- $('button.'+styles[i]).css('border', '1px solid {{color.lf_ar_button_brd}}');
- }
- }
-}
var mode_pairs = [['subgroup', 'normal'], ['', 'aut'], ['diagram', 'profile']];
function select_subgroup_mode(mode) {
var cls, thismode, opposite_mode, piece;
diff --git a/lmfdb/static/lmfdb.js b/lmfdb/static/lmfdb.js
index 68eabf5489..79429351ca 100644
--- a/lmfdb/static/lmfdb.js
+++ b/lmfdb/static/lmfdb.js
@@ -358,7 +358,11 @@ function get_count_of_results() {
$("span.download-msg").html("Computing number of results...");
if (address.slice(-1) === "#")
address = address.slice(0,-1);
- address += "&result_count=1";
+ if (address.includes("?")) {
+ address += "&result_count=1";
+ } else {
+ address += "?result_count=1";
+ }
$.ajax({url: address, success: get_count_callback});
};
diff --git a/lmfdb/templates/count_results.html b/lmfdb/templates/count_results.html
index 8b3c3d2ce9..049b5dbb20 100644
--- a/lmfdb/templates/count_results.html
+++ b/lmfdb/templates/count_results.html
@@ -26,8 +26,12 @@
n/a
{% else %}
{% set url = info.url_func(row, col) %}
+ {% if url is none %}
+ {{size}}
+ {% else %}
{{size}}
- {% endif %}
+ {% endif %} {# url none #}
+ {% endif %} {# size none #}
{% endfor %}
diff --git a/lmfdb/templates/embedded_results.html b/lmfdb/templates/embedded_results.html
index a628c9953a..66be3e4a81 100644
--- a/lmfdb/templates/embedded_results.html
+++ b/lmfdb/templates/embedded_results.html
@@ -5,6 +5,7 @@
{% endblock %}
+{% if info.number > 0 %}
{{ info.columns.above_results | safe }}
{% if info.columns.languages %}
{% set languages = info.columns.languages %}
@@ -27,10 +28,17 @@
{% endif %}
- {% if info.number > info.count or info.start != 0 -%}
+ {% if info.number > info.count or info.start != 0 or info.show_count -%}
- Showing {{ info.start + 1 }}-{{ upper_count }} of {{ info.number }}
-
+ Showing {% if info.number <= info.count and info.start == 0 -%}
+ all {{ info.number }}
+ {% elif info.exact_count -%}
+ {{ info.start + 1 }}-{{ upper_count }} of {{ info.number }}
+ {% else -%}
+ {{ info.start + 1 }}-{{ upper_count }} of
+
at least {{ info.number }}
+ {% endif %}
+
{% endif %}
{% include 'forward_back.html' %}
{% include 'download_search_results.html' %}
@@ -45,7 +53,13 @@
{% for col in C.columns_shown(info, rank) %}
- {{ col.display_knowl() | safe }}
+ {{ col.display_knowl(info) | safe }}
{% endfor %}
{% endfor %}
diff --git a/lmfdb/templates/stat_2d.html b/lmfdb/templates/stat_2d.html
index 374cb47b10..9533fc32a0 100644
--- a/lmfdb/templates/stat_2d.html
+++ b/lmfdb/templates/stat_2d.html
@@ -1,5 +1,5 @@
- {{info.stats._short_display[d.attribute.cols[1]]}}
+ {{d.attribute.col_title | safe}}
@@ -12,26 +12,26 @@
{% if loop.index == 1 %}
- {{info.stats._short_display[d.attribute.cols[0]] | safe}}
+ {{d.attribute.row_title | safe}}
{% endif %}
- {{ rhead | safe}}
+ {{ rhead | safe}}
{% for c in row %}
{% if c.count %}
{% if c.query %}
- {{c.count}}
+
{% else %}
- {{c.count}}
+
{% endif %}
{% else %}
-
+
{% endif %}
{% endfor %}
{% for c in row %}
- {{c.proportion}}
+ {{c.proportion}}
{% endfor %}
{% endfor %} {# d.grid #}
diff --git a/lmfdb/templates/style.css b/lmfdb/templates/style.css
index 0adf995ec6..09325378e7 100644
--- a/lmfdb/templates/style.css
+++ b/lmfdb/templates/style.css
@@ -1128,6 +1128,16 @@ table.statgrid th.chead {
border: 1px solid #ccc;
border-bottom: 2px solid {{color.table_ntdata_border}};
}
+table.statgrid .totalrow {
+ border-top: 2px solid {{color.table_ntdata_border}};
+}
+table.statgrid .totalcol {
+ border-left: 2px solid {{color.table_ntdata_border}};
+}
+table.statgrid .totalcorner {
+ border-top: 2px solid {{color.table_ntdata_border}};
+ border-left: 2px solid {{color.table_ntdata_border}};
+}
table.statgrid td.prop {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
@@ -1950,7 +1960,7 @@ div.table-scroll-wrapper table.onesticky th:first-child::after, div.table-scroll
table.nowrap td {
white-space:nowrap;
}
-td.nowrap {
+td.nowrap, th.nowrap, span.nowrap {
white-space:nowrap;
}
table.ntdata td.nowrap a {
@@ -2141,13 +2151,13 @@ div.sub_divider {
margin-right: 20px;
}
-button.sub_active {
+button.sub_active, button.dia_active, button.gs_active {
background: {{color.lf_an_button_bkg}};
border: 2px solid {{color.lf_an_button_brd}};
cursor: pointer;
}
-button.sub_inactive {
+button.sub_inactive, button.dia_inactive, button.gs_inactive {
background: {{color.lf_ar_button_bkg}};
border: 1px solid {{color.lf_ar_button_brd}};
}
diff --git a/lmfdb/utils/__init__.py b/lmfdb/utils/__init__.py
index f0227b5cde..957e8c3f20 100644
--- a/lmfdb/utils/__init__.py
+++ b/lmfdb/utils/__init__.py
@@ -31,8 +31,9 @@
'parse_galgrp', 'parse_nf_string', 'parse_subfield', 'parse_nf_elt', 'parse_nf_jinv',
'parse_container', 'parse_hmf_weight', 'parse_count', 'parse_newton_polygon',
'parse_start', 'parse_ints_to_list_flash', 'integer_options',
+ 'unparse_range',
'nf_string_to_label', 'clean_input', 'prep_ranges',
- 'search_wrap', 'count_wrap', 'embed_wrap',
+ 'search_wrap', 'count_wrap', 'embed_wrap', 'yield_wrap',
'SearchArray', 'EmbeddedSearchArray', 'TextBox', 'TextBoxNoEg', 'TextBoxWithSelect', 'BasicSpacer',
'SkipBox', 'CheckBox', 'CheckboxSpacer', 'DoubleSelectBox', 'HiddenBox',
'SearchButton', 'SearchButtonWithSelect', 'RowSpacer',
@@ -147,9 +148,10 @@
parse_nf_elt, parse_nf_jinv, parse_container, parse_hmf_weight, parse_count, parse_start,
parse_ints_to_list_flash, integer_options, nf_string_to_label,
parse_subfield, parse_interval,
+ unparse_range,
clean_input, prep_ranges, input_string_to_poly)
-from .search_wrapper import search_wrap, count_wrap, embed_wrap
+from .search_wrapper import search_wrap, count_wrap, embed_wrap, yield_wrap
from .search_boxes import (
SearchArray, EmbeddedSearchArray, TextBox, TextBoxNoEg, TextBoxWithSelect, BasicSpacer,
SkipBox, CheckBox, CheckboxSpacer, DoubleSelectBox, HiddenBox,
diff --git a/lmfdb/utils/display_stats.py b/lmfdb/utils/display_stats.py
index 2e6dd3f047..50e053099b 100644
--- a/lmfdb/utils/display_stats.py
+++ b/lmfdb/utils/display_stats.py
@@ -291,7 +291,7 @@ def __call__(self, grid, row_headers, col_headers, stats):
# Make the sums available for the column proportions
stats._total_grid[i].append({'count':overall})
proportion = _format_percentage(total, overall) if col_proportions else ''
- D = {'count':total, 'query':query, 'proportion':proportion}
+ D = {'count':total, 'query':query, 'proportion':proportion, 'extraclass':'totalcol', 'propclass':'totalcol'}
row.append(D)
if col_counts:
row_headers.append(col_total_label)
@@ -300,8 +300,12 @@ def __call__(self, grid, row_headers, col_headers, stats):
row = []
for i, col in enumerate(zip(*grid)):
# We've already totaled rows, so have to skip if we don't want the corner
- if not corner_count and i == num_cols:
- break
+ if i == num_cols:
+ if not corner_count:
+ break
+ extraclasses = {'extraclass': 'totalcorner', 'propclass': 'totalcol'}
+ else:
+ extraclasses = {'extraclass': 'totalrow'}
total = sum(elt['count'] for elt in col)
if total == 0:
query = None
@@ -313,6 +317,7 @@ def __call__(self, grid, row_headers, col_headers, stats):
overall = sum(D['count'] for D in total_grid_cols[i])
proportion = _format_percentage(total, overall) if (col_proportions and i != num_cols or corner_prop and i == num_cols) else ''
D = {'count':total, 'query':query, 'proportion':proportion}
+ D.update(extraclasses)
row.append(D)
grid.append(row)
#if corner_count and row_counts and not col_counts:
@@ -629,8 +634,9 @@ def prep(self, attr):
data = self.display_data(**attr)
attr['intro'] = attr.get('intro',[])
data['attribute'] = attr
- if len(cols) == 1:
+ if 'row_title' not in attr:
attr['row_title'] = self._short_display[cols[0]]
+ if len(cols) == 1:
max_rows = attr.get('max_rows',6)
counts = data['counts']
rows = [counts[i:i+10] for i in range(0, len(counts), 10)]
@@ -641,8 +647,8 @@ def prep(self, attr):
else:
data['divs'] = [(rows, "short_table", "none")]
elif len(cols) == 2:
- attr['row_title'] = self._short_display[cols[0]]
- attr['col_title'] = self._short_display[cols[1]]
+ if 'col_title' not in attr:
+ attr['col_title'] = self._short_display[cols[1]]
return data
@lazy_attribute
diff --git a/lmfdb/utils/downloader.py b/lmfdb/utils/downloader.py
index f0286d1b3b..00517f66b5 100644
--- a/lmfdb/utils/downloader.py
+++ b/lmfdb/utils/downloader.py
@@ -745,7 +745,7 @@ def __call__(self, info):
seen.add(name)
cols = [cols[i] for i in include]
column_names = [column_names[i] for i in include]
- data_format = [col.title for col in cols]
+ data_format = [(col.title if isinstance(col.title, str) else col.title(info)) for col in cols]
first50 = [[col.download(rec) for col in cols] for rec in first50]
if num_results > 10000:
# Estimate the size of the download file. This won't necessarily be a great estimate
@@ -820,10 +820,14 @@ def knowl_subber(match):
else:
knowl = col.download_desc
if knowl:
- if name.lower() == col.title.lower():
- yield lang.comment(f" {col.title} --\n")
+ if isinstance(col.title, str):
+ title = col.title
else:
- yield lang.comment(f"{col.title} ({name}) --\n")
+ title = col.title(info)
+ if name.lower() == title.lower():
+ yield lang.comment(f" {title} --\n")
+ else:
+ yield lang.comment(f"{title} ({name}) --\n")
for line in knowl.split("\n"):
if line.strip():
yield lang.comment(" " + line.rstrip() + "\n")
diff --git a/lmfdb/utils/search_boxes.py b/lmfdb/utils/search_boxes.py
index b0df4c6f06..fd53d0c3ba 100644
--- a/lmfdb/utils/search_boxes.py
+++ b/lmfdb/utils/search_boxes.py
@@ -537,7 +537,11 @@ def _input(self, info):
for col in C.columns_shown(info, use_rank):
if col.short_title is None: # probably a spacer column:
continue
- title = col.short_title.replace("$", "").replace(r"\(", "").replace(r"\)", "").replace("\\", "")
+ if isinstance(col.short_title, str):
+ short_title = col.short_title
+ else:
+ short_title = col.short_title(info)
+ title = short_title.replace("$", "").replace(r"\(", "").replace(r"\)", "").replace("\\", "")
if col.default(info):
disp = "✓ " + title # The space is a unicode space the size of an emdash
else:
@@ -677,8 +681,11 @@ def sort_order(self, info):
def _search_again(self, info, search_types):
if info is None:
return search_types
- st = self._st(info)
- return [(st, "Search again")] + [(v, d) for v, d in search_types if v != st]
+ mst = st = self._st(info)
+ # Sometimes need to treat empty string as equal to "List"
+ if not st and any(v == "List" for (v,d) in search_types):
+ mst = "List"
+ return [(st, "Search again")] + [(v, d) for v, d in search_types if v != mst]
def search_types(self, info):
# Override this method to change the displayed search buttons
@@ -745,7 +752,7 @@ def _st(self, info):
if info is not None:
search_type = info.get("search_type", info.get("hst", ""))
if search_type == "List":
- # Backward compatibility
+ # Want to avoid including search_type in URL when possible
search_type = ""
return search_type
diff --git a/lmfdb/utils/search_columns.py b/lmfdb/utils/search_columns.py
index bb79312be5..75bae85c1b 100644
--- a/lmfdb/utils/search_columns.py
+++ b/lmfdb/utils/search_columns.py
@@ -18,6 +18,7 @@
(in the case of column groups).
"""
+import re
from .web_display import display_knowl
from lmfdb.utils import coeff_to_poly
from sage.all import Rational, latex
@@ -58,7 +59,7 @@ class SearchCol:
and the default key used when extracting data from a database record.
- ``knowl`` -- a knowl identifier, for displaying the column header as a knowl
- ``title`` -- the string shown for the column header, also included when describing the column
- in a download file.
+ in a download file. Alternatively, you can provide a function of info that produces such a string.
- ``default`` -- either a boolean or a function taking an info dictionary as input and returning
a boolean. In either case, this determines whether the column is displayed initially. See
the ``get_default_func`` above.
@@ -98,7 +99,12 @@ def __init__(self, name, knowl, title, default=True, align="left",
self.knowl = knowl
self.title = title
if short_title is None:
- short_title = None if title is None else title.lower()
+ if title is None:
+ short_title = None
+ elif isinstance(title, str):
+ short_title = title.lower()
+ else:
+ short_title = lambda info: title(info).lower()
self.short_title = short_title
self.default = get_default_func(default, name)
self.mathmode = mathmode
@@ -178,13 +184,17 @@ def display(self, rec):
s = f"${s}$"
return s
- def display_knowl(self):
+ def display_knowl(self, info):
"""
Displays the column header contents.
"""
+ if isinstance(self.title, str):
+ title = self.title
+ else:
+ title = self.title(info)
if self.knowl:
- return display_knowl(self.knowl, self.title)
- return self.title
+ return display_knowl(self.knowl, title)
+ return title
def show(self, info, rank=None):
"""
@@ -229,7 +239,7 @@ def __init__(self, name, **kwds):
def display(self, rec):
return ""
- def display_knowl(self):
+ def display_knowl(self, info):
return ""
def show(self, info, rank=None):
@@ -578,8 +588,44 @@ def split(x):
class ListCol(ProcessedCol):
"""
+ Used for lists that may be empty.
+
+ The list may be stored in a postgres array or a postgres string
+ """
+ def __init__(self, *args, **kwds):
+ if "delim" in kwds:
+ self.delim = kwds.pop("delim")
+ assert len(self.delim) == 2
+ else:
+ self.delim = None
+ super().__init__(*args, **kwds)
+
+ def display(self, rec):
+ s = str(self.func(self.get(rec)))
+ if s == "[]":
+ s = "[ ]"
+ if self.delim:
+ s = s.replace("[", self.delim[0]).replace("]", self.delim[1])
+ if s and self.mathmode:
+ s = f"${s}$"
+ return s
+
+class RationalListCol(ListCol):
+ """
+ For lists of rational numbers.
+
Uses the ``eval_rational_list`` function to process the column for downloading.
"""
+ def __init__(self, name, knowl, title, func=None, apply_download=False, mathmode=True, use_frac=True, **kwds):
+ self.use_frac = use_frac
+ super().__init__(name, knowl, title, func=func, apply_download=apply_download, mathmode=mathmode, **kwds)
+
+ def display(self, rec):
+ s = super().display(rec)
+ if self.use_frac:
+ s = re.sub(r"(\d+)/(\d+)", r"\\frac{\1}{\2}", s)
+ return s.replace("'", "").replace('"', '')
+
def download(self, rec):
s = super().download(rec)
return eval_rational_list(s)
diff --git a/lmfdb/utils/search_parsing.py b/lmfdb/utils/search_parsing.py
index 020e818349..295fb7af3d 100644
--- a/lmfdb/utils/search_parsing.py
+++ b/lmfdb/utils/search_parsing.py
@@ -37,7 +37,6 @@
FLOAT_RE = re.compile("^" + FLOAT_STR + "$")
BRACKETING_RE = re.compile(r"(\[[^\]]*\])") # won't work for iterated brackets [[a,b],[c,d]]
PREC_RE = re.compile(r"^-?((?:\d+(?:[.]\d*)?)|(?:[.]\d+))(?:e([-+]?\d+))?$")
-LF_LABEL_RE = re.compile(r"^\d+\.\d+\.\d+\.\d+$")
MULTISET_RE = re.compile(r"^(\d+)(\^(\d+))?(,(\d+)(\^(\d+))?)*$")
class PowMulNodeVisitor(ast.NodeTransformer):
@@ -70,6 +69,7 @@ def __init__(
default_qfield,
error_is_safe,
clean_spaces,
+ angle_to_curly,
):
self.f = f
self.clean_info = clean_info
@@ -81,6 +81,7 @@ def __init__(
self.default_qfield = default_qfield
self.error_is_safe = error_is_safe # Indicates that the message in raised exception contains no user input, so it is not escaped
self.clean_spaces = clean_spaces
+ self.angle_to_curly = angle_to_curly
def __call__(self, info, query, field=None, name=None, qfield=None, *args, **kwds):
try:
@@ -97,7 +98,7 @@ def __call__(self, info, query, field=None, name=None, qfield=None, *args, **kwd
inp = str(inp)
if SPACES_RE.search(inp):
raise SearchParsingError("You have entered spaces in between digits. Please add a comma or delete the spaces.")
- inp = clean_input(inp, self.clean_spaces)
+ inp = clean_input(inp, self.clean_spaces, self.angle_to_curly)
if qfield is None:
if field is None:
qfield = self.default_qfield
@@ -147,6 +148,7 @@ def search_parser(
default_qfield=None,
error_is_safe=False,
clean_spaces=True,
+ angle_to_curly=False,
):
return SearchParser(
f,
@@ -159,15 +161,19 @@ def search_parser(
default_qfield,
error_is_safe,
clean_spaces,
+ angle_to_curly,
)
# Remove whitespace for simpler parsing
# Remove brackets to avoid tricks (so we can echo it back safely)
-def clean_input(inp, clean_spaces=True):
+def clean_input(inp, clean_spaces=True, angle_to_curly=False):
if inp is None:
return None
if clean_spaces:
- return re.sub(r"[\s<>]", "", str(inp))
+ inp = re.sub(r"\s", "", str(inp))
+ if angle_to_curly:
+ inp = re.sub("<", "{", inp)
+ return re.sub(">", "}", inp)
else:
return re.sub(r"[<>]", "", str(inp))
@@ -355,6 +361,21 @@ def parse_range2(arg, key, parse_singleton=int, parse_endpoint=None, split_minus
else:
return [key, parse_singleton(arg)]
+def unparse_range(query_part, col_name=None):
+ """
+ Given the output of parse_ints or other parser based on parse_range2,
+ return a lower and upper bound for the result. Either being None indicates no limit.
+ $or is not supported
+ """
+ if isinstance(query_part, dict):
+ if "$or" in query_part:
+ msg = "Multiple ranges not supported"
+ if col_name:
+ msg += f" for {col_name}"
+ raise ValueError(msg)
+ return query_part.get("$gte"), query_part.get("$lte")
+ return query_part, query_part
+
# Like parse_range2, but to deal with strings which could be rational numbers
# process is a function to apply to arguments after they have been parsed
def parse_range2rat(arg, key, process):
@@ -1155,12 +1176,16 @@ def parse_inertia(inp, query, qfield, err_msg=None):
nt = aliases[inp2][0]
query[iner_gap] = nt2abstract(nt[0], nt[1])
else:
- # Check for Gap code
- rematch = re.match(r"^\[(\d+),(\d+)\]$", inp)
+ # Check for Gap code using [a,b] or a.b notation
+ rematch = re.fullmatch(r"\[(\d+),(\d+)\]", inp)
if rematch:
query[iner_gap] = [int(rematch.group(1)), int(rematch.group(2))]
else:
- raise NameError
+ rematch = re.fullmatch(r"(\d+)\.(\d+)", inp)
+ if rematch:
+ query[iner_gap] = [int(rematch.group(1)), int(rematch.group(2))]
+ else:
+ raise NameError
except NameError:
if re.match(r"^[ACDFMQS]\d+$", inp):
@@ -1168,18 +1193,20 @@ def parse_inertia(inp, query, qfield, err_msg=None):
if err_msg:
raise SearchParsingError(err_msg)
else:
- raise SearchParsingError("It needs to be a GAP id, such as [4,1] or [12,5], ia transitive group in nTj notation, such as 5T1, or a group label ")
+ raise SearchParsingError("It needs to be a small group id, such as [4,1] or 12.5, ia transitive group in nTj notation, such as 5T1, or a group label ")
# see SearchParser.__call__ for actual arguments when calling
@search_parser(clean_info=True, error_is_safe=True)
def parse_padicfields(inp, query, qfield, flag_unramified=False):
+ from lmfdb.local_fields.main import NEW_LF_RE, OLD_LF_RE
labellist = inp.split(",")
doflash = False
for label in labellist:
- if not LF_LABEL_RE.match(label):
+ if not NEW_LF_RE.fullmatch(label) and not OLD_LF_RE.fullmatch(label):
raise SearchParsingError('It needs to be a $p$-adic field label or a list of local field labels')
splitlab = label.split('.')
- if splitlab[2] == '0':
+ if (OLD_LF_RE.fullmatch(label) and splitlab[2] == '0' or
+ NEW_LF_RE.fullmatch(label) and splitlab[3][0] == '0'):
doflash = True
if flag_unramified and doflash:
flash_info("Search results may be incomplete. Given $p$-adic completions contain an unramified field and completions are only searched for ramified primes .")
diff --git a/lmfdb/utils/search_wrapper.py b/lmfdb/utils/search_wrapper.py
index 8b3b49bf09..760c7365be 100644
--- a/lmfdb/utils/search_wrapper.py
+++ b/lmfdb/utils/search_wrapper.py
@@ -333,12 +333,18 @@ def __call__(self, info):
if not isinstance(data, tuple):
return data # error page
query, sort, table, title, err_title, template, one_per = data
+ groupby = query.pop("__groupby__", self.groupby)
template_kwds = {key: info.get(key, val()) for key, val in self.kwds.items()}
try:
if query:
- res = table.count(query, groupby=self.groupby)
+ res = table.count(query, groupby=groupby)
else:
- res = table.stats.column_counts(self.groupby)
+ # We want to use column_counts since it caches results, but it also sorts the input columns and doesn't adjust the results
+ res = table.stats.column_counts(groupby)
+ sgroupby = sorted(groupby)
+ if sgroupby != groupby:
+ perm = [sgroupby.index(col) for col in groupby]
+ res = {tuple(key[i] for i in perm): val for (key, val) in res.items()}
except QueryCanceledError as err:
return self.query_cancelled_error(
info, query, err, err_title, template, template_kwds
@@ -355,7 +361,7 @@ def __call__(self, info):
res[row, col] = 0
else:
res[row, col] = None
- info['count'] = 50 # put count back in so that it doesn't show up as none in url
+ info['count'] = 50 # put count back in so that it doesn't show up as none in url
except ValueError as err:
# Errors raised in postprocessing
@@ -404,6 +410,12 @@ def __call__(self, info):
proj = query.pop("__projection__", self.projection)
if isinstance(proj, list):
proj = [col for col in proj if col in table.search_cols]
+ if "result_count" in info:
+ if one_per:
+ nres = table.count_distinct(one_per, query)
+ else:
+ nres = table.count(query)
+ return jsonify({"nres": str(nres)})
count = parse_count(info, self.per_page)
start = parse_start(info)
try:
@@ -414,6 +426,7 @@ def __call__(self, info):
offset=start,
sort=sort,
info=info,
+ one_per=one_per
)
except QueryCanceledError as err:
return self.query_cancelled_error(info, query, err, err_title, template, template_kwds)
@@ -424,9 +437,95 @@ def __call__(self, info):
# This is caused when a user inputs a number that's too large for a column search type
return self.oob_error(info, query, err, err_title, template, template_kwds)
else:
+ try:
+ if self.postprocess is not None:
+ res = self.postprocess(res, info, query)
+ except ValueError as err:
+ raise
+ flash_error(str(err))
+ info["err"] = str(err)
+ return render_template(template, info=info, title=err_title, **template_kwds)
info["results"] = res
return render_template(template, info=info, title=title, **template_kwds)
+class YieldWrapper(Wrapper):
+ """
+ A variant on search wrapper that is intended to replace the database table with a Python function
+ that yields rows.
+
+ The Python function should also accept a boolean random keyword (though it's allowed to raise an error)
+ """
+ def __init__(
+ self,
+ f, # still a function that parses info into a query dictionary
+ template="search_results.html",
+ yielder=None,
+ title=None,
+ err_title=None,
+ per_page=50,
+ columns=None,
+ url_for_label=None,
+ **kwds
+ ):
+ Wrapper.__init__(
+ self, f, template, yielder, title, err_title, postprocess=None, **kwds
+ )
+ self.per_page = per_page
+ self.columns = columns
+ self.url_for_label = url_for_label
+
+ def __call__(self, info):
+ info = to_dict(info)
+ # if search_type starts with 'Random' returns a random label
+ search_type = info.get("search_type", info.get("hst", ""))
+ info["search_type"] = search_type
+ info["columns"] = self.columns
+ random = info["search_type"].startswith("Random")
+ template_kwds = {key: info.get(key, val()) for key, val in self.kwds.items()}
+ data = self.make_query(info, random)
+ if not isinstance(data, tuple):
+ return data
+ query, sort, yielder, title, err_title, template, one_per = data
+ if "result_count" in info:
+ if one_per:
+ nres = yielder(query, one_per=one_per, count=True)
+ else:
+ nres = yielder(query, count=True)
+ return jsonify({"nres": str(nres)})
+ count = parse_count(info, self.per_page)
+ start = parse_start(info)
+ try:
+ if random:
+ label = yielder(query, random=True)
+ if label is None:
+ res = []
+ # ugh; we have to set these manually
+ info["query"] = dict(query)
+ info["number"] = 0
+ info["count"] = count
+ info["start"] = start
+ info["exact_count"] = True
+ else:
+ return redirect(self.url_for_label(label), 307)
+ else:
+ res = yielder(
+ query,
+ limit=count,
+ offset=start,
+ sort=sort,
+ info=info,
+ one_per=one_per,
+ )
+ except ValueError as err:
+ flash_error(str(err))
+ info["err"] = str(err)
+ title = err_title
+ raise
+ else:
+ info["results"] = res
+ return render_template(template, info=info, title=title, **template_kwds)
+
+
@decorator_keywords
def search_wrap(f, **kwds):
return SearchWrapper(f, **kwds)
@@ -438,3 +537,7 @@ def count_wrap(f, **kwds):
@decorator_keywords
def embed_wrap(f, **kwds):
return EmbedWrapper(f, **kwds)
+
+@decorator_keywords
+def yield_wrap(f, **kwds):
+ return YieldWrapper(f, **kwds)
diff --git a/lmfdb/utils/web_display.py b/lmfdb/utils/web_display.py
index 0a2b60b79e..d4a0be49d9 100644
--- a/lmfdb/utils/web_display.py
+++ b/lmfdb/utils/web_display.py
@@ -708,10 +708,16 @@ def frac_string(frac):
# copied here from hilbert_modular_forms.hilbert_modular_form as it
# started to cause circular imports:
-def teXify_pol(pol_str): # TeXify a polynomial (or other string containing polynomials)
+def teXify_pol(pol_str, greek_vars=False, subscript_vars=False): # TeXify a polynomial (or other string containing polynomials)
if not isinstance(pol_str, str):
pol_str = str(pol_str)
- o_str = pol_str.replace('*', '')
+ if greek_vars:
+ greek_re = re.compile(r"\b(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega)\b")
+ pol_str = greek_re.sub(r"\\\g<1>", pol_str)
+ if subscript_vars:
+ subscript_re = re.compile(r"([A-Za-z]+)(\d+)")
+ pol_str = subscript_re.sub(r"\g<1>_{\g<2>}", pol_str)
+ o_str = pol_str.replace('*', ' ')
ind_mid = o_str.find('/')
while ind_mid != -1:
ind_start = ind_mid - 1