diff --git a/.gitignore b/.gitignore index 24f0be2209..064e961329 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ config.ini *.o *.c *.swp +*.aux +*.log +*.dvi *~ @@ -34,3 +37,10 @@ LMFDBinventory.log LMFDBtransactions_inv.log scripts/groups/Make-images/images/*.png +scripts/groups/Make-images/images/eqguts.tex +scripts/groups/Make-images/prettyindex +scripts/groups/Make-images/imagereloader + +smf_lmfdb +__pycache__ +create_input_data.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..88cf8ed709 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "smf_lmfdb"] + path = smf_lmfdb + url = https://github.com/assaferan/smf_lmfdb.git diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..3b66410730 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.ignoreLimitWarning": true +} \ No newline at end of file diff --git a/CONTRIBUTORS.yaml b/CONTRIBUTORS.yaml index 27209fff8c..70eed7780a 100644 --- a/CONTRIBUTORS.yaml +++ b/CONTRIBUTORS.yaml @@ -34,6 +34,10 @@ name: Jennifer Balakrishnan url: https://math.bu.edu/people/jbala/ affil: Boston University --- +name: Barinder S. Banwait +url: https://barinderbanwait.github.io/ +affil: Boston University +--- name: Alex Bartel url: https://www.gla.ac.uk/schools/mathematicsstatistics/staff/alexbartel/ affil: University of Glasgow @@ -210,6 +214,10 @@ name: John Jones url: https://hobbes.la.asu.edu/ affil: Arizona State University --- +name: Eray Karabiyik +url: https://math.cornell.edu/emir-eray-karabiyik +affil: Cornell University +--- name: Jon Keating affil: University of Bristol url: https://www.maths.bris.ac.uk/people/profile/majpk diff --git a/Postgres_FAQ.md b/Postgres_FAQ.md index 682fe3b954..53c875955c 100644 --- a/Postgres_FAQ.md +++ b/Postgres_FAQ.md @@ -160,7 +160,7 @@ Database Interface ```python sage: from psycodict import db, SQL - sage: cur = db._execute(SQL("SELECT label, dim, cm_discs, rm_discs from mf_newforms WHERE projective_image = %s AND cm_discs @> %s LIMIT 2"), ['D2', [-19]]) + sage: cur = db._execute(SQL("SELECT label, dim, cm_discs, rm_discs from mf_newforms_eis WHERE projective_image = %s AND cm_discs @> %s LIMIT 2"), ['D2', [-19]]) sage: cur.rowcount 2 sage: list(cur) @@ -759,7 +759,7 @@ Data Validation version of the database with verification enabled (it is not enabled by default since the verification checks are vulnerable to SQL injection and should not be available when the website is - running). Then use, for example, `db.mf_newforms.verify()`. + running). Then use, for example, `db.mf_newforms_eis.verify()`. You can specify a particular speedtype to run (e.g. `speedtype="slow"` or `speedtype="overall"`), a specific diff --git a/lmfdb/abvar/fq/__pycache__/download.cpython-310.pyc.139950799420368 b/lmfdb/abvar/fq/__pycache__/download.cpython-310.pyc.139950799420368 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lmfdb/abvar/fq/isog_class.py b/lmfdb/abvar/fq/isog_class.py index 188ecb1438..87e3f62747 100644 --- a/lmfdb/abvar/fq/isog_class.py +++ b/lmfdb/abvar/fq/isog_class.py @@ -9,7 +9,7 @@ """ from flask import url_for -from collections import Counter +from collections import defaultdict, Counter from lmfdb.utils import encode_plot, display_float from lmfdb.logger import make_logger @@ -17,13 +17,15 @@ from lmfdb import db from lmfdb.app import app +from sage.all import Factorization, FreeAlgebra from sage.rings.all import Integer, QQ, RR, ZZ from sage.plot.all import line, points, circle, polygon, Graphics from sage.misc.latex import latex from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute -from lmfdb.utils import list_to_factored_poly_otherorder, coeff_to_poly, web_latex, integer_divisors, teXify_pol +from lmfdb.groups.abstract.web_groups import abstract_group_display_knowl, abelian_group_display_knowl +from lmfdb.utils import list_to_factored_poly_otherorder, coeff_to_poly, web_latex, integer_divisors, teXify_pol, display_knowl from lmfdb.number_fields.web_number_field import nf_display_knowl, field_pretty from lmfdb.galois_groups.transitive_group import transitive_group_display_knowl from lmfdb.abvar.fq.web_abvar import av_display_knowl, av_data # , av_knowl_guts @@ -73,22 +75,62 @@ def validate_label(label): raise ValueError("the final part must be of the form c1_c2_..._cg, with each ci consisting of lower case letters") + +def show_singular_support(n): + if n == 0: + return r"$\varnothing$" + elif not n: + return "not computed" + return r"$\{" + ",".join(fr"\mathfrak{{P}}_{{ {i} }}" for (i, b) in enumerate(ZZ(n).bits(), 1) if b) + r"\}$" + + +def diagram_js(layers, display_opts): + ll = [ + [ + node.label, # grp.subgroup + node.label, # grp.short_label + node.tex, # grp.subgroup_tex + 1, # grp.count (never want conjugacy class counts) + node.rank, # grp.subgroup_order + node.img, + node.x, # grp.diagramx[0] if aut else (grp.diagramx[2] if grp.normal else grp.diagramx[1]) + [node.x, node.x, node.x, node.x], # grp.diagram_aut_x if aut else grp.diagram_x + ] + for node in layers[0] + ] + if len(ll) == 0: + display_opts["w"] = display_opts["h"] = 0 + return [], [], 0 + ranks = [node[4] for node in ll] + rank_ctr = Counter(ranks) + ranks = sorted(rank_ctr) + # We would normally make rank_lookup a dictionary, but we're passing it to the horrible language known as javascript + # The format is for compatibility with subgroup lattices + rank_lookup = [[r, r, 0] for r in ranks] + max_width = max(rank_ctr.values()) + display_opts["w"] = min(100 * max_width, 20000) + display_opts["h"] = 160 * len(ranks) + + return [ll, layers[1]], rank_lookup, len(ranks) + + class AbvarFq_isoclass(): """ Class for an isogeny class of abelian varieties over a finite field """ + name = "isogeny" + + select_line = rf""" + Click on an {display_knowl('ag.endomorphism_ring', 'endomorphism ring')}, + with the {display_knowl('av.fq.endomorphism_ring_notation', 'notation') } + $[\mathrm{{index}}]_{{i}}^{{\# \mathrm{{weak}} \cdot \# \mathrm{{Pic}}}}$, + in the diagram to see information about it. +""" + def __init__(self, dbdata): - if "size" not in dbdata: - dbdata["size"] = None - if "hyp_count" not in dbdata: - dbdata["hyp_count"] = None - if "jacobian_count" not in dbdata: - dbdata["jacobian_count"] = None - # New invariants: cyclicity and noncyclic primes - if "is_cyclic" not in dbdata: - dbdata["is_cyclic"] = None - if "noncyclic_primes" not in dbdata: - dbdata["noncyclic_primes"] = [] + for col in ["size", "zfv_is_bass", "zfv_is_maximal", "zfv_index", "zfv_index_factorization", "zfv_plus_index", "zfv_plus_index_factorization", "zfv_plus_norm", "hyp_count", "jacobian_count", "all_polarized_product", "cohen_macaulay_max", "endomorphism_ring_count", "weak_equivalence_count", "zfv_singular_count", "group_structure_count", "zfv_pic_size", "principal_polarization_count", "singular_primes", "is_cyclic", "noncyclic_primes"]: + if col not in dbdata: + dbdata[col] = None self.__dict__.update(dbdata) @classmethod @@ -125,6 +167,14 @@ def expanded_polynomial(self): else: return latex(QQ[['x']](self.polynomial)) + @lazy_attribute + def zfv_index_factorization_latex(self): + return latex(Factorization(self.zfv_index_factorization)) + + @lazy_attribute + def zfv_plus_index_factorization_latex(self): + return latex(Factorization(self.zfv_plus_index_factorization)) + @property def p(self): q = Integer(self.q) @@ -210,6 +260,265 @@ def circle_plot(self): P.set_aspect_ratio(1) return encode_plot(P, pad=0, pad_inches=None, transparent=True, axes_pad=0.04) + @lazy_attribute + def weak_equivalence_classes(self): + return list(db.av_fq_weak_equivalences.search({"isog_label": self.label})) + + def filtered_mrings(self, query): + query["is_invertible"] = True + query["isog_label"] = self.label + if len(query) == 2: + return set(self.endring_data) + return set(db.av_fq_weak_equivalences.search(query, "multiplicator_ring")) + + @lazy_attribute + def endring_data(self): + inv = {} + ninv = defaultdict(list) + for rec in self.weak_equivalence_classes: + mring = rec["multiplicator_ring"] + if rec["is_invertible"]: + inv[mring] = rec + else: + ninv[mring].append(rec) + return {mring: [inv[mring]] + ninv[mring] for mring in inv} + + @cached_method(key=lambda self, query: str(query)) + def endring_poset(self, query={}): + # The poset of endomorphism rings for abelian varieties in this isogeny class + # For ordinary isogeny classes, these are precisely the sub-orders of the maximal order that contain the Frobenius order + included_in_query = self.filtered_mrings(query) + class LatNode: + def __init__(self, label): + self.label = label + self.index = ZZ(label.split(".")[0]) + if label in included_in_query: + self.tex = tex[label] + else: + self.tex = r"\cdot" + if self.tex in texlabels: + self.img = texlabels[self.tex] + else: + self.img = texlabels["?"] + self.rank = sum(e for (p,e) in self.index.factor()) + self.x = xcoord[label] + parents = {} + pic_size = {} + num_wes = Counter() + num_ind = Counter() + xcoord = {} + for R, wes in self.endring_data.items(): + N = ZZ(R.split(".")[0]) + num_wes[R] = len(wes) + num_ind[N] += 1 + parents[R] = wes[0]['minimal_overorders'] + pic_size[R] = wes[0]['pic_size'] + xcoord[R] = wes[0]['diagramx'] + if not pic_size: + return [], [] # no weak equivalence class data for this isogeny class + tex = {} + texlabels = set(["?", r"\cdot"]) + for R, npic in pic_size.items(): + N, i = R.split(".") + N = ZZ(N) + if N == 1: + factored_index = "1" + else: + factored_index = r"\cdot".join((f"{p}^{{{e}}}" if e > 1 else f"{p}") for (p, e) in N.factor()) + istr = f"_{{{i}}}" if num_ind[N] > 1 else "" + we_pic = rf"{num_wes[R]}\cdot{pic_size[R]}" if num_wes[R] > 1 else f"{pic_size[R]}" + tex[R] = "[%s]^{%s}%s" % (factored_index, we_pic, istr) + texlabels.add(tex[R]) + texlabels = {rec["label"]: rec["image"] for rec in db.av_fq_teximages.search({"label": {"$in": list(texlabels)}})} + nodes = [LatNode(lab) for lab in pic_size] + edges = [] + if nodes: + maxrank = max(node.rank for node in nodes) + for node in nodes: + node.rank = maxrank - node.rank + edges.extend([[node.label, lab] for lab in parents[node.label]]) + return nodes, edges + + + def diagram_js(self, query): + display_opts = {} + graph, rank_lookup, num_layers = diagram_js(self.endring_poset(query), display_opts) + return { + 'string': f'var [sdiagram,graph] = make_sdiagram("subdiagram", "{self.label}", {graph}, {rank_lookup}, {num_layers});', + 'display_opts': display_opts + } + + + + @lazy_attribute + def endring_select_line(self): + return self.select_line + + @cached_method + def endring_disp(self, endring): + we = self.endring_data[endring] + max_index = max(L[0]["index"] for L in self.endring_data.values()) + disp = {} + rec = we[0] + + R = FreeAlgebra(ZZ, ["F", "V"]) + F, V = R.gens() + pows = [V**i for i in reversed(range(0, self.g))] + [F**i for i in range(1, self.g + 1)] + def to_R(num): + assert len(num) == len(pows) + return sum(c*p for c, p in zip(num, pows)) + + # Conductor display + if rec["conductor"]: + # conductor + M, d, num = rec["conductor"] + num = to_R(num) + if num != 0: + conductor = latex(num) + if d != 1: + conductor = r"\frac{1}{%s}(%s)" % (d, conductor) + conductor = fr"\langle {M},{conductor}\rangle_{{\mathcal{{O}}_{{\mathbb{{Q}}(F)}}}}" + else: + conductor = r"\mathcal{O}_{\mathbb{Q}[F]}" + if M != 1: + conductor = f"{M} {conductor}" + else: + conductor = "not computed" + + # Ring display + short_names = [] + long_names = [] + if rec["index"] == 1: + short_names.append(conductor) + long_names.append(conductor) + elif rec.get("is_Zconductor_sum") and rec["index"] != max_index: # Don't write for Z[F,V] itself + short_names.append(r"\mathbb{Z} + \mathfrak{f}_R") + if rec["conductor"]: + long_names.append(r"\mathbb{Z} + " + conductor) + elif rec.get("is_ZFVconductor_sum") and rec["index"] != max_index: # Don't write for Z[F,V] itself + short_names.append(r"\mathbb{Z}[F, V] + \mathfrak{f}_R") + if rec["conductor"]: + long_names.append(r"\mathbb{Z}[F, V] + " + conductor) + gen = rec.get("generator_over_ZFV") + if gen: + d, num = gen + num = to_R(num) + gens = ["F", "V"] + if num != 0: + s = latex(num) + if d != 1: + s = r"\frac{1}{%s}(%s)" % (d, s) + gens.append(s) + gen_name = fr"\mathbb{{Z}}[{','.join(gens)}]" + short_names.append(gen_name) + long_names.append(gen_name) + + if rec["conductor"]: + conductor = f"${conductor}$" + + pic_size = rec["pic_size"] + if rec["pic_invs"] is not None: + pic_disp = abelian_group_display_knowl(rec['pic_invs']) + elif rec["pic_size"] is not None: + pic_disp = f"Abelian group of order {pic_size}" + else: + pic_disp = "not computed" + + av_structure = defaultdict(Counter) + for w in we: + av_structure[1][tuple(w["rational_invariants"])] += pic_size + for n in range(2, 11): + av_structure[n][tuple(w["higher_invariants"][n-2])] += pic_size + for n in range(1, 11): + if len(av_structure[n]) == 1: + disp[f"av_structure{n}"] = abelian_group_display_knowl(next(iter(av_structure[n]))) + else: + gps = sorted((-cnt, invs) for (invs, cnt) in av_structure[n].items()) + disp[f"av_structure{n}"] = ",".join("%s ($%s$)" % (abelian_group_display_knowl(invs), -m) for (m, invs) in gps) + + dimensions = Counter() + for w in we: + dimensions[tuple(w["dimensions"])] += pic_size + if len(dimensions) == 1: + disp["dimensions"] = f"${list(next(iter(dimensions)))}$" + else: + dimensions = sorted((-cnt, dims) for (dims, cnt) in dimensions.items()) + disp["dimensions"] = ",".join("$[%s]$ ($%s$)" % (",".join(str(c) for c in dims), -m) for (m, dims) in dimensions) + + disp["short_names"] = short_names + disp["long_names"] = long_names + disp["index"] = int(endring.split(".")[0]) + disp["conductor"] = conductor + disp["pic"] = pic_disp + disp["av_count"] = len(we) * pic_size + return disp + + @lazy_attribute + def _group_structures(self): + av_structure = defaultdict(Counter) + for we in self.endring_data.values(): + pic_size = we[0]["pic_size"] + for w in we: + av_structure[1][tuple(w["rational_invariants"])] += pic_size + for n in range(2, 6): + av_structure[n][tuple(w["higher_invariants"][n-2])] += pic_size + return av_structure + + @lazy_attribute + def group_structures(self): + N = self.most_group_structures + av_structure = self._group_structures + disp = [[] for _ in range(N)] + for n in range(1, 6): + if len(av_structure[n]) == 1: + disp[0].append(abelian_group_display_knowl(next(iter(av_structure[n])))) + nextj = 1 + else: + gps = sorted((-cnt, invs) for (invs, cnt) in av_structure[n].items()) + for j, (m, invs) in enumerate(gps): + disp[j].append("%s ($%s$)" % (abelian_group_display_knowl(invs), -m)) + nextj = len(gps) + for j in range(nextj, N): + disp[j].append("") + return disp + + @lazy_attribute + def most_group_structures(self): + return max(len(opts) for opts in self._group_structures.values()) + + def endringinfo(self, endring): + rec = self.endring_data[endring][0] + disp = self.endring_disp(endring) + + num_we = len(self.endring_data[endring]) + names = "=".join(["R"] + disp["short_names"]) + + if rec["cohen_macaulay_type"] is None: + cm_type = "not computed" + else: + cm_type = "$%s$" % rec["cohen_macaulay_type"] + + ans = [ + f'Information on the {display_knowl("ag.endomorphism_ring", "endomorphism ring")} ${names}$
', + "", + f"", + fr"", + fr"", + f"", + f"", + f"", + f"", + fr"", + "
{display_knowl('av.fq.lmfdb_label', 'Label')}:{'.'.join(rec['label'].split('.')[3:])}
{display_knowl('av.fq.index_of_order', 'Index')} $[\mathcal{{O}}_{{\mathbb{{Q}}[F]}}:R]$:${disp['index']}$
{display_knowl('av.endomorphism_ring_conductor', 'Conductor')} $\mathfrak{{f}}_R$:{disp['conductor']}
{display_knowl('ag.cohen_macaulay_type', 'Cohen-Macaulay type')}:{cm_type}
{display_knowl('av.fq.singular_primes', 'Singular support')}:${show_singular_support(rec['singular_support'])}$
{display_knowl('ag.fq.point_counts', 'Group structure')}:{disp['av_structure1']}
{display_knowl('av.fq.picard_of_order', 'Picard group')}:{disp['pic']}
$\# \{{${display_knowl('av.fq.weak_equivalence_class', 'weak equivalence classes')}$\}}$:${num_we}$
" + ] + # Might also want to add rational point structure for varieties in this class, link to search page for polarized abvars... + return "\n".join(ans) + + @lazy_attribute + def singular_primes_disp(self): + disp = [",".join(teXify_pol(f) for f in P.split(",")) for P in self.singular_primes] + return [fr"\langle {P} \rangle" for P in disp] + def _make_jacpol_property(self): ans = [] if self.has_principal_polarization == 1: @@ -489,6 +798,59 @@ def show_curve(cv): return "" + header_polarized_varieties = [ + ('label', 'av.fq.lmfdb_label', 'Label'), + ('degree', 'av.polarization', 'Degree'), + ('aut_group', 'ag.aut_group', 'Automorphism Group'), + #('geom_aut_group', 'av.geom_aut_group', 'Geometric automorphism group'), + ('endomorphism_ring', 'ag.endomorphism_ring', 'Endomorphism ring'), + ('kernel', 'av.polarization', 'Kernel'), + ] + + @lazy_attribute + def polarized_abelian_varieties(self): + cols = [elt for elt, _ ,_ in self.header_polarized_varieties] + return list(db.av_fq_pol.search({"isog_label": self.label}, cols, sort=['degree'])) + + def display_header_polarizations(self): + ths = "\n".join( + f'{display_knowl(kwl, title=title)}' for _, kwl, title in self.header_polarized_varieties) + + return f'\n{ths}\n' + + + def display_rows_polarizations(self, query=None): + polarized_columns_display = { + 'aut_group': lambda x : abstract_group_display_knowl(x['aut_group']), + 'degree': lambda x : web_latex(x['degree']), + 'endomorphism_ring': lambda x : x['endomorphism_ring'], + # 'geom_aut_group' : lambda x: abstract_group_display_knowl(x['geom_aut_group']), + 'isom_label' : lambda x : x['isom_label'], + 'kernel' : lambda x : abelian_group_display_knowl(x['kernel']), + 'label' : lambda x : x['label'], + } + res = "" + #FIXME filter by query + for i,elt in enumerate(self.polarized_abelian_varieties): + shade = 'dark' if i%2 == 0 else 'light' + res += f'\n' + "\n".join([f"{polarized_columns_display[col](elt)}" for col, _, _ in self.header_polarized_varieties]) + "\n" + return f'\n{res}' + + def display_polarizations(self, query=None): + return rf""" + + {self.display_header_polarizations()} + {self.display_rows_polarizations()} +
+""" + @lazy_attribute + def number_principal_polarizations(self): + if self.polarized_abelian_varieties: + return sum(1 for elt in self.polarized_abelian_varieties if elt['degree'] == 1) + else: + return None + + @app.context_processor def ctx_decomposition(): return {"av_data": av_data} diff --git a/lmfdb/abvar/fq/main.py b/lmfdb/abvar/fq/main.py index 981d910d85..0d8a45e5da 100644 --- a/lmfdb/abvar/fq/main.py +++ b/lmfdb/abvar/fq/main.py @@ -3,6 +3,7 @@ from flask import abort, render_template, url_for, request, redirect from sage.rings.all import ZZ +from sage.combinat.subset import Subsets from sage.databases.cremona import cremona_letter_code from lmfdb import db @@ -11,16 +12,16 @@ to_dict, flash_error, integer_options, display_knowl, coeff_to_poly, SearchArray, TextBox, TextBoxWithSelect, SkipBox, CheckBox, CheckboxSpacer, YesNoBox, parse_ints, parse_string_start, parse_subset, parse_newton_polygon, parse_submultiset, parse_bool, parse_bool_unknown, - search_wrap, count_wrap, YesNoMaybeBox, CountBox, SubsetBox, SelectBox + search_wrap, count_wrap, embed_wrap, YesNoMaybeBox, CountBox, SubsetBox, SelectBox ) from lmfdb.utils.interesting import interesting_knowls from lmfdb.api import datapage from . import abvarfq_page from .search_parsing import parse_nf_string, parse_galgrp -from .isog_class import validate_label, AbvarFq_isoclass +from .isog_class import validate_label, AbvarFq_isoclass, show_singular_support from .stats import AbvarFqStats from lmfdb.number_fields.web_number_field import nf_display_knowl, field_pretty -from lmfdb.utils import redirect_no_cache +from lmfdb.utils import redirect_no_cache, SearchButton from lmfdb.utils.search_columns import SearchColumns, SearchCol, MathCol, LinkCol, ProcessedCol, CheckCol, CheckMaybeCol from lmfdb.abvar.fq.download import AbvarFq_download from lmfdb.utils.search_parsing import parse_primes @@ -104,6 +105,144 @@ def abelian_varieties_by_gq(g, q): ) return abelian_variety_search(D) +def endring_postprocess(res, info, query): + # In addition to the normal postprocessing, we can put code here for determining induced inclusion relations on the poset + # Need to add index to schema + # Need to set name, av_count, av_structure*, conductor_disp, dimensions_disp, pic_disp + cl = info["cl"] + # We can access all weak equivalence classes from cl; res contains only the endomorphism rings being displayed + for rec in res: + disp = cl.endring_disp(rec["multiplicator_ring"]) + rec["name"] = "
".join(f"${name}$" for name in disp["long_names"]) + for col in ["av_count"] + [f"av_structure{n}" for n in range(1, 11)]: + rec[col] = disp[col] + for col in ["conductor", "dimensions", "pic"]: + rec[col+"_disp"] = disp[col] + return res + +def support_opts(num_primes): + for w in range(num_primes, 0, -1): + for S in Subsets(range(num_primes), w): + S = sorted(S) + if w > 1: + N = sum(2**c for c in S) + if w == num_primes: + key = "" + else: + key = ",".join(str(c) for c in range(N+1) if (c & N == c)) + yield (key, "any of " + ", ".join(f"P{i+1}" for i in S)) + yield ("0," + ",".join(str(2**c) for c in S), ("one of " if w > 1 else "") + ", ".join(f"P{i+1}" for i in S)) + +endring_columns = SearchColumns([ + ProcessedCol("label", "av.fq.lmfdb_label", "Label", lambda label: ".".join(label.split(".")[3:]), default=False), + MathCol("av_count", "av.fq.isogeny_class_size", "Num. iso"), + ProcessedCol("singular_support", "av.fq.singular_primes", "Singular support", show_singular_support, default=lambda info: "singular_support" in info), + MathCol("number_of_we", "av.fq.weak_equivalence_class", "Num. weak equivalence classes", default=False), + MathCol("pic_size", "av.fq.picard_group", "Picard size", default=False), + SearchCol("av_structure1", "av.fq.group_structure", r"$A(\mathbb{F}_q)$-structure", short_title="q-structure"), + SearchCol("av_structure2", "av.fq.group_structure", r"$A(\mathbb{F}_{q^2})$-structure", short_title="q^2-structure", default=False), + SearchCol("av_structure3", "av.fq.group_structure", r"$A(\mathbb{F}_{q^3})$-structure", short_title="q^3-structure", default=False), + SearchCol("av_structure4", "av.fq.group_structure", r"$A(\mathbb{F}_{q^4})$-structure", short_title="q^4-structure", default=False), + SearchCol("av_structure5", "av.fq.group_structure", r"$A(\mathbb{F}_{q^5})$-structure", short_title="q^5-structure", default=False), + SearchCol("av_structure6", "av.fq.group_structure", r"$A(\mathbb{F}_{q^6})$-structure", short_title="q^6-structure", default=False), + SearchCol("av_structure7", "av.fq.group_structure", r"$A(\mathbb{F}_{q^7})$-structure", short_title="q^7-structure", default=False), + SearchCol("av_structure8", "av.fq.group_structure", r"$A(\mathbb{F}_{q^8})$-structure", short_title="q^8-structure", default=False), + SearchCol("av_structure9", "av.fq.group_structure", r"$A(\mathbb{F}_{q^9})$-structure", short_title="q^9-structure", default=False), + SearchCol("av_structure10", "av.fq.group_structure", r"$A(\mathbb{F}_{q^{10}})$-structure", short_title="q^10-structure", default=False), + CheckCol("is_product", "av.fq.is_product", "Product", default=False), + SearchCol("name", "ag.endomorphism_ring", "Endomorphism ring"), + MathCol("index", "av.fq.index_of_order", "Index"), + CheckCol("is_conjugate_stable", "av.fq.conjugate_stable", "Conjugate stable", default=False), + CheckCol("is_Zconductor_sum", "av.fq.is_zconductor_sum", r"$\mathbb{Z}$-conductor sum", short_title="Z-conductor sum", default=False), + CheckCol("is_ZFVconductor_sum", "av.fq.is_zfvconductor_sum", r"$\mathbb{Z}[F,V]$-conductor sum", short_title="Z[F,V]-conductor sum", default=False), + MathCol("conductor_disp", "av.endomorphism_ring_conductor", "Conductor"), + CheckCol("conductor_is_Oprime", "av.endomorphism_ring_conductor", r"Conductor $\mathcal{O}$-prime", short_title="conductor O-prime", default=False), + CheckCol("conductor_is_Sprime", "av.endomorphism_ring_conductor", "Conductor $S$-prime", short_title="conductor S-prime", default=False), + MathCol("cohen_macaulay_type", "ag.cohen_macaulay_type", "Cohen-Macaulay type"), + SearchCol("dimensions_disp", "av.fq.singular_dimensions", "Singular dimensions", default=False), + SearchCol("pic_disp", "av.fq.picard_group", "Picard group")], + db_cols=["cohen_macaulay_type", "conductor", "conductor_Oindex", "conductor_Sindex", "conductor_is_Oprime", "conductor_is_Sprime", "dimensions", "generator_over_ZFV", "higher_invariants", "index", "is_ZFVconductor_sum", "is_Zconductor_sum", "is_conjugate_stable", "is_product", "label", "multiplicator_ring", "pic_invs", "pic_size", "rational_invariants", "number_of_we", "singular_support"]) + +class EndringSearchArray(SearchArray): + sorts = [("", "index", ["isog_label", "index", "multiplicator_ring"]), + ("cohen_macaulay", "cohen_macaulay", ["isog_label", "cohen_macaulay_type", "index", "multiplicator_ring"])] + def __init__(self, cl): + self.cl = cl + cohen_macaulay = TextBox( + "cohen_macaulay", + label="Cohen-Macaulay type", + knowl="ag.cohen_macaulay_type", + example="3" + ) + pic_size = TextBox( + "pic_size", + label="Picard group order", + knowl="av.fq.picard_group", + example="1" + ) + number_of_we = TextBox( + "number_of_we", + label="Num. weak equiv. classes", + knowl="av.fq.weak_equivalence_class", + example="2" + ) + cond_index = TextBox( + "cond_index", + label=r"Conductor $\mathcal{O}$-index", + knowl="av.endomorphism_ring_conductor", + example="100-200" + ) + Zcond = YesNoBox( + "Zcond", + label=r"$\mathbb{Z}$-conductor sum", + knowl="av.fq.is_Zconductor_sum", + ) + ZFVcond = YesNoBox( + "ZFVcond", + label=r"$\mathbb{Z}[F,V]$-conductor sum", + knowl="av.fq.is_zfvconductor_sum", + ) + product = YesNoBox( + "product", + label="Product", + knowl="av.is_product", + ) + conj_stable = YesNoBox( + "conj_stable", + label="Conjugate stable", + knowl="av.fq.conjugate_stable", + ) + Oprime = YesNoBox( + "OPrime", + label=r"Conductor $\mathcal{O}$-prime", + knowl="av.endomorphism_ring_conductor", + ) + Sprime = YesNoBox( + "SPrime", + label="Conductor $S$-prime", + knowl="av.endomorphism_ring_conductor", + ) + if hasattr(cl, "zfv_singular_primes"): + singular_opts = list(support_opts(len(cl.zfv_singular_primes))) + else: + singular_opts = [] + singular_support = SelectBox( + "singular_support", + label="Singular support", + knowl="av.fq.singular_primes", + options=singular_opts, + ) + self.refine_array = [[pic_size, cohen_macaulay, product, Zcond, Oprime], + [number_of_we, cond_index, conj_stable, ZFVcond, Sprime]] + if hasattr(cl, "zfv_singular_primes") and len(cl.zfv_singular_primes) > 1: + self.refine_array.append([singular_support]) + + def search_types(self, info): + if len(self.cl.endring_data) == 1: + return [("", "Search again")] + else: + return [(info.get("search_type", "lattice"), "Search again"), SearchButton("", "Lattice mode", type="", name="switch", onclick=" onclick='switch_endring_mode(); return false;'", cls="")] + @abvarfq_page.route("///") def abelian_varieties_by_gqi(g, q, iso): label = abvar_label(g, q, iso) @@ -116,6 +255,7 @@ def abelian_varieties_by_gqi(g, q, iso): cl = AbvarFq_isoclass.by_label(label) except ValueError: return abort(404, "Isogeny class %s is not in the database." % label) + info = to_dict(request.args, search_array=EndringSearchArray(cl), cl=cl) bread = get_bread( (str(g), url_for(".abelian_varieties_by_g", g=g)), (str(q), url_for(".abelian_varieties_by_gq", g=g, q=q)), @@ -129,18 +269,57 @@ def abelian_varieties_by_gqi(g, q, iso): if hasattr(cl, "curves") and cl.curves: downloads.append(('Curves to text', url_for('.download_curves', label=label))) downloads.append(("Underlying data", url_for('.AV_data', label=label))) + info['title'] = 'Abelian variety isogeny class %s over $%s$' % (label, cl.field()) + info['bread'] = bread + info['properties'] = cl.properties() + info['friends'] = cl.friends() + info['downloads'] = downloads + info['KNOWL_ID'] = 'av.fq.%s' % label + return render_abvar(info) + +def endring_parse(info, query): + cl = info["cl"] + query["is_invertible"] = True + query["isog_label"] = cl.label + parse_ints(info, query, "cohen_macaulay", qfield="cohen_macaulay_type") + parse_ints(info, query, "pic_size") + parse_ints(info, query, "number_of_we") + parse_ints(info, query, "cond_index", qfield="conductor_Oindex") + parse_ints(info, query, "singular_support") + parse_bool(info, query, "Zcond", qfield="is_Zconductor_sum") + parse_bool(info, query, "ZFVcond", qfield="is_ZFVconductor_sum") + parse_bool(info, query, "product", qfield="is_product") + parse_bool(info, query, "conj_stable", qfield="is_conjugate_stable") + parse_bool(info, query, "OPrime", qfield="conductor_is_Oprime") + parse_bool(info, query, "SPrime", qfield="conductor_is_Sprime") + +@embed_wrap( + table=db.av_fq_weak_equivalences, + template="show-abvarfq.html", + err_title="Endomorphism ring search error", + columns=endring_columns, + learnmore=learnmore_list, + postprocess=endring_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, + cl=lambda:None, + friends=lambda:None, + downloads=lambda:None, + KNOWL_ID=lambda:None, +) +def render_abvar(info, query): + endring_parse(info, query) - return render_template( - "show-abvarfq.html", - properties=cl.properties(), - friends=cl.friends(), - downloads=downloads, - title='Abelian variety isogeny class %s over $%s$' % (label, cl.field()), - bread=bread, - cl=cl, - learnmore=learnmore_list(), - KNOWL_ID='av.fq.%s' % label - ) +isogeny_class_label_regex = re.compile(r"(\d+)\.(\d+)\.([a-z_]+)") +mring_regex = re.compile(r"(\d+)\.(\d+)") + +def split_label(lab): + return isogeny_class_label_regex.match(lab).groups() + +def abvar_label(g, q, iso): + return "%s.%s.%s" % (g, q, iso) def url_for_label(label): label = label.replace(" ", "") @@ -154,7 +333,7 @@ def url_for_label(label): @abvarfq_page.route("/data/