diff --git a/lmfdb/bianchi_modular_forms/bianchi_modular_form.py b/lmfdb/bianchi_modular_forms/bianchi_modular_form.py index 557e0a30b3..25ca069a84 100644 --- a/lmfdb/bianchi_modular_forms/bianchi_modular_form.py +++ b/lmfdb/bianchi_modular_forms/bianchi_modular_form.py @@ -791,7 +791,7 @@ class BianchiStats(StatsDisplay): stat_list = [ {'cols': ['field_label', 'level_norm'], 'top_title': '%s by %s and %s' % ( - display_knowl("mf.bianchi.bianchimodularforms", + display_knowl("mf.bianchi", "Bianchi modular forms"), display_knowl('nf', 'base field'), display_knowl('mf.bianchi.level', 'level norm')), @@ -852,7 +852,7 @@ def __init__(self): def summary(self): return r"The database currently contains %s %s of weight 2 over %s imaginary quadratic fields, and %s %s over %s imaginary quadratic fields, including all with class number one." % ( comma(self.nforms), - display_knowl("mf.bianchi.bianchimodularforms", + display_knowl("mf.bianchi", "Bianchi modular forms"), self.nformfields, comma(self.ndims), @@ -862,4 +862,4 @@ def summary(self): @property def short_summary(self): - return r'The database currently contains %s %s of weight 2 over %s imaginary quadratic fields. Here are some further statistics.' % (comma(self.nforms), display_knowl("mf.bianchi.bianchimodularforms", "Bianchi modular forms"), self.nformfields, url_for(".statistics")) + return r'The database currently contains %s %s of weight 2 over %s imaginary quadratic fields. Here are some further statistics.' % (comma(self.nforms), display_knowl("mf.bianchi", "Bianchi modular forms"), self.nformfields, url_for(".statistics")) diff --git a/lmfdb/bianchi_modular_forms/templates/bmf-field_dim_table.html b/lmfdb/bianchi_modular_forms/templates/bmf-field_dim_table.html index ae8ed9669f..aba341c294 100644 --- a/lmfdb/bianchi_modular_forms/templates/bmf-field_dim_table.html +++ b/lmfdb/bianchi_modular_forms/templates/bmf-field_dim_table.html @@ -8,7 +8,7 @@

Table of the dimensions of the spaces of {{ -KNOWL('mf.bianchi.bianchimodularforms', title='Bianchi cusp forms') }} +KNOWL('mf.bianchi', title='Bianchi cusp forms') }} for \(\Gamma_0(\mathfrak{n})\subseteq {{info.bgroup}}\) for {{ KNOWL('mf.bianchi.level', title='levels') }} \(\mathfrak{n}\) ordered by norm, over \(K=\) {{info.field_pretty}}. diff --git a/lmfdb/groups/abstract/code.yaml b/lmfdb/groups/abstract/code.yaml new file mode 100644 index 0000000000..419415d455 --- /dev/null +++ b/lmfdb/groups/abstract/code.yaml @@ -0,0 +1,67 @@ +prompt: + magma: 'magma' + gap: 'gap' + +logo: + magma: + gap: + +comment: + magma: | + // + gap: | + # + +not-implemented: + magma: | + // (not yet implemented) + gap: | + # (not yet implemented) + + + +presentation: + comment: Define the group with the given generators and relations + magma: G := PCGroup({pccodelist}); {gens} := Explode({used_gens}); AssignNames(~G, {magma_assign}); + gap: G := PcGroupCode({pccode},{ordgp}); {gap_assign} + + +permutation: + comment: Define the group as a permutation group + magma: G := PermutationGroup< {deg} | {perms} >; + gap: G := Group( {perms} ); + + +GLZ: + comment: Define the group as a matrix group with coefficients in Z + magma: G := MatrixGroup< {nZ}, Integers() | {LZ} >; + gap: G := Group({LZsplit}); + + +GLFp: + comment: Define the group as a matrix group with coefficients in GLFp + magma: G := MatrixGroup< {nFp}, GF({Fp}) | {LFp} >; + gap: G := not yet implemented +# gap: G := Group({LFpsplit}); + +GLZN: + comment: Define the group as a matrix group with coefficients in GLZN + magma: G := MatrixGroup< {nZN}, Integers({N}) | {LZN} >; + gap: G := Group({LZNsplit}); + +GLZq: + comment: Define the group as a matrix group with coefficients in GLZq + magma: G := MatrixGroup< {nZq}, Integers({Zq}) | {LZq} >; + gap: G := Group({LZqsplit}); + + +#GLFq: +# comment: Define the group as a matrix group with coefficients in GLFq +# magma: G := MatrixGroup< {nFq}, GL({Fq}) | {LFq} >; +# gap: G := Group({LFqsplit}); + +transitive: + comment: Define the group from the transitive group database + magma: G := TransitiveGroup(d,n); + gap: G := TransitiveGroup(d,n); + diff --git a/lmfdb/groups/abstract/main.py b/lmfdb/groups/abstract/main.py index c63d0ab550..6b83f65f3e 100644 --- a/lmfdb/groups/abstract/main.py +++ b/lmfdb/groups/abstract/main.py @@ -1540,7 +1540,6 @@ def diagram_js_string(gp, only=None): # Writes individual pages def render_abstract_group(label, data=None): - info = {} if data is None: label = clean_input(label) @@ -1571,12 +1570,15 @@ def render_abstract_group(label, data=None): title = f"Abstract group ${gp.tex_name}$" + # disable until we can fix downloads downloads = [ - ("Group to Gap", url_for(".download_group", label=label, download_type="gap")), - ("Group to Magma", url_for(".download_group", label=label, download_type="magma")), - ("Group to Oscar", url_for(".download_group", label=label, download_type="oscar")), - ("Underlying data", url_for(".gp_data", label=label)), + ("Underlying data", url_for(".gp_data", label=label)), ] +# ("Group to Gap", url_for(".download_group", label=label, download_type="gap")), +# ("Group to Magma", url_for(".download_group", label=label, download_type="magma")), +# ("Group to Oscar", url_for(".download_group", label=label, download_type="oscar")), + # ("Underlying data", url_for(".gp_data", label=label)), +# ] # "internal" friends sbgp_of_url = ( @@ -1651,6 +1653,7 @@ def render_abstract_group(label, data=None): bread=bread, info=info, gp=gp, + code=gp.code_snippets(), properties=gp.properties(), friends=friends, learnmore=learnmore_list_add(*learnmore_gp_picture), @@ -2742,11 +2745,8 @@ def cc_data(gp, label, typ="complex", representative=None): ans += "
Representative: id" else: gp_value = WebAbstractGroup(gp) - if gp_value.representations.get("Lie") and gp_value.representations["Lie"][0]["family"][0] == "P" and gp_value.order < 2000: #Problem with projective lie groups of order <2000 - pass - else: - repn = gp_value.decode(wacc.representative, as_str=True) - ans += "
Representative: {}".format("$" + repn + "$") + repn = gp_value.decode(wacc.representative, as_str=True) + ans += "
Representative: {}".format("$" + repn + "$") return Markup(ans) @@ -3082,3 +3082,14 @@ def order_stats_list_to_string(o_list): if o_list.index(pair) != len(o_list) - 1: s += "," return s + + +sorted_code_names = ['presentation', 'permutation', 'matrix', 'transitive'] + +code_names = {'presentation': 'Define the group using generators and relations', + 'permutation': 'Define the group as a permutation group', + 'matrix': 'Define the group as a matrix group', + 'transitive': 'Define the group from the transitive group database'} + +Fullname = {'magma': 'Magma', 'gap': 'Gap'} +Comment = {'magma': '//', 'gap': '#'} diff --git a/lmfdb/groups/abstract/templates/abstract-show-group.html b/lmfdb/groups/abstract/templates/abstract-show-group.html index 1f77c16119..326925028d 100644 --- a/lmfdb/groups/abstract/templates/abstract-show-group.html +++ b/lmfdb/groups/abstract/templates/abstract-show-group.html @@ -1,8 +1,8 @@ {% extends "homepage.html" %} - {% block content %} + + {% if gp.live() %}

This group is not stored in the database. However, basic information about the group, computed on the fly, is listed below. @@ -191,6 +192,16 @@

Minimal Presentations

{% if not (gp.live() and gp.abelian) %}

Constructions

+ {% if code %}
+ Show commands: + {% set slash = joiner("/ ") %} + {% for lang in code['show']|sort %} + {# override show names for standard languages to ensure consistency #} + {% set show = 'PariGP' if lang == 'pari' else 'SageMath' if lang == 'sage' else 'Magma' if lang == 'magma' else 'Oscar' if lang == 'oscar' else "Gap" if lang == 'gap' else code['show']['lang'] %} + {{slash()}}{{show}} + {% endfor %} +


+ {% endif %} {{ gp.stored_representations | safe }} diff --git a/lmfdb/groups/abstract/web_groups.py b/lmfdb/groups/abstract/web_groups.py index 18a117185d..9be86487bd 100644 --- a/lmfdb/groups/abstract/web_groups.py +++ b/lmfdb/groups/abstract/web_groups.py @@ -1,6 +1,7 @@ import re # import timeout_decorator - +import os +import yaml from lmfdb import db from flask import url_for from urllib.parse import quote_plus @@ -96,6 +97,48 @@ def group_pretty_image(label): return str(img) # we should not get here +def create_gens_list(genslist): + # For Magma + gens_list = [f"G.{i}" for i in genslist] + return str(gens_list).replace("'", "") + +def create_gap_assignment(genslist): + # For GAP + return " ".join(f"{var_name(j)} := G.{i};" for j, i in enumerate(genslist)) + +def create_magma_assignment(G): + used = [u - 1 for u in sorted(G.gens_used)] + rel_ords = [ZZ(p) for p in G.PCG.FamilyPcgs().RelativeOrders()] + ngens = len(used) + names = [] + for j, i in enumerate(used): + if j == ngens - 1: + icap = len(rel_ords) + else: + icap = used[j+1] + power = 1 + v = var_name(j) + for i0 in range(i, icap): + if power == 1: + names.append(v) + else: + names.append(f"{v}{power}") + power *= rel_ords[i0] + return str(names).replace("'", '"') + +def split_matrix_list(longList,d): + # for code snippets, turns d^2 list into d lists of length d for Gap matrices + return [longList[i:i+d] for i in range(0,d**2,d)] + +def split_matrix_list_ZN(longList,d, Znfld): + longList = [f"ZmodnZObj({x},{Znfld})" for x in longList] + return str([longList[i:i+d] for i in range(0,d**2,d)]).replace("'", "") + +#not currently in use. Should work if elements of Fp and Fq are given as +#power of mult. generator +def split_matrix_list_Fq(longList,d, Fqfld): + longList = [f"0*Z({Fqfld})" if x == 0 else "Z({Fqfld})^{x-1}" for x in longList] + return str([longList[i:i+d] for i in range(0,d**2,d)]).replace("'", "") # Functions below are for conjugacy class searches def gp_label_to_cc_data(gp): @@ -1867,7 +1910,8 @@ def _matrix_coefficient_data(self, rep_type, as_str=False): rep_type = "GLFp" return R, N, k, d, rep_type - def decode_as_matrix(self, code, rep_type, as_str=False, LieType=False): + def decode_as_matrix(self, code, rep_type, as_str=False, LieType=False, ListForm=False): + # ListForm is for code snippet if rep_type == "GLZ" and not isinstance(code, int): # decimal here represents an integer encoding b a, b = str(code).split(".") code = int(a) @@ -1888,6 +1932,8 @@ def pad(X, m): elif rep_type == "GLZ": shift = (N - 1) // 2 L = [c - shift for c in L] + if ListForm: + return L x = matrix(R, d, d, L) if as_str: # for projective families, we add "[ ]" @@ -2004,8 +2050,9 @@ def print_elt(vec): relators = ", ".join(rel_powers + relators) return r"\langle %s \mid %s \rangle" % (show_gens, relators) - def presentation_raw(self): + def presentation_raw(self, as_str=True): # We use knowledge of the form of the presentation to construct it manually. + # Need as_str = False for code snippet gens = list(self.PCG.GeneratorsOfGroup()) pcgs = self.PCG.FamilyPcgs() used = [u - 1 for u in sorted(self.gens_used)] # gens_used is 1-indexed @@ -2056,16 +2103,22 @@ def print_elt(vec): for j in range(i + 1, ngens): b = used[j] if all(x == 0 for x in pcgs.ExponentsOfCommutator(b + 1, a + 1)): # back to 1-indexed - if not self.abelian: + if not as_str: # print commutator out for code snippets + comm.append("%s^-1*%s^-1*%s*%s" % (var_name(i), var_name(j), var_name(i), var_name(j))) + elif not self.abelian: comm.append("[%s,%s]" % (var_name(i), var_name(j))) else: v = pcgs.ExponentsOfConjugate(b + 1, a + 1) # back to 1-indexed relators.append("%s*%s^-1*%s^-1*%s" % (print_elt(v), var_name(i), var_name(j), var_name(i))) - show_gens = ", ".join(var_name(i) for i in range(len(used))) if pure_powers or comm: rel_powers = [",".join(pure_powers + comm)] + rel_powers relators = ", ".join(rel_powers + relators) - return r"< %s | %s >" % (show_gens, relators) + if as_str: + show_gens = ", ".join(var_name(i) for i in range(len(used))) + return r"< %s | %s >" % (show_gens, relators) + else: + show_gens = ",".join(var_name(i) for i in range(len(used))) # no space for code snipptes + return show_gens @lazy_attribute def representations(self): @@ -2114,6 +2167,9 @@ def representation_line(self, rep_type, skip_head=False): if not skip_head: #add copy button in certain cases pres_raw=self.presentation_raw() pres = raw_typeset(pres_raw,compress_pres(pres)) + if self.live(): # skip code snippet on live group for now + return f'' + code_cmd = self.create_snippet('presentation') else: pres = " $" + pres + "$" if self.abelian and not self.cyclic: @@ -2123,23 +2179,31 @@ def representation_line(self, rep_type, skip_head=False): pres = "Abelian group " + pres if skip_head: return f'{pres} .' # for repr_strg - return f'' + return f'{code_cmd}' elif rep_type == "Perm": gens = ", ".join(self.decode_as_perm(g, as_str=True) for g in rdata["gens"]) gens=raw_typeset(gens,compress_perm(gens)) d = rdata["d"] + if self.live(): # skip code snippet on live group for now + code_cmd = "" + else: + code_cmd = self.create_snippet('permutation') if d >= 10: gens=f"Degree ${d}$" + gens - return f'' + return f'{code_cmd}' else: # Matrix group R, N, k, d, _ = self._matrix_coefficient_data(rep_type, as_str=True) gens = ", ".join(self.decode_as_matrix(g, rep_type, as_str=True) for g in rdata["gens"]) gens = fr"$\left\langle {gens} \right\rangle \subseteq \GL_{{{d}}}({R})$" + if rep_type == "GLFq": + code_cmd = "" + else: + code_cmd = self.create_snippet(rep_type) if skip_head: - return f'' + return f'{code_cmd}' else: - return f'' + return f'{code_cmd}' @lazy_attribute def transitive_friends(self): @@ -2562,6 +2626,100 @@ def image(self): circles = "" return f'\n{circles}' + def create_snippet(self,item): + # mimics jinja macro place_code to be included in Constructions section + # this is specific for embedding in a table. eg. we need to replace "<" with "<" + code = self.code_snippets() + snippet_str = "" # initiate new string + if code[item]: + for L in code[item]: + if isinstance(code[item][L],str): + lines = code[item][L].split('\n')[:-1] if '\n' in code[item][L] else [code[item][L]] + lines = [line.replace("<", "<").replace(">", ">") for line in lines] + else: # not currrently used in groups + lines = code[item][L] + prompt = code['prompt'][L] if 'prompt' in code and L in code['prompt'] else L + class_str = " ".join([L,'nodisplay','code','codebox']) + col_span_val = '"6"' + for line in lines: + snippet_str = snippet_str + f'' + return snippet_str + + @cached_method + def code_snippets(self): + if self.live(): + return + _curdir = os.path.dirname(os.path.abspath(__file__)) + code = yaml.load(open(os.path.join(_curdir, "code.yaml")), Loader=yaml.FullLoader) + code['show'] = { lang:'' for lang in code['prompt'] } + if "PC" in self.representations: + gens = self.presentation_raw(as_str=False) + pccodelist = self.representations["PC"]["pres"] + pccode = self.representations["PC"]["code"] + ordgp = self.order + used_gens = create_gens_list(self.representations["PC"]["gens"]) + gap_assign = create_gap_assignment(self.representations["PC"]["gens"]) + magma_assign = create_magma_assignment(self) + else: + gens, pccodelist, pccode, ordgp, used_gens, gap_assign, magma_assign = None, None, None, None, None, None, None + if "Perm" in self.representations: + rdata = self.representations["Perm"] + perms = ", ".join(self.decode_as_perm(g, as_str=True) for g in rdata["gens"]) + deg = rdata["d"] + else: + perms, deg = None, None + + if "GLZ" in self.representations: + nZ = self.representations["GLZ"]["d"] + LZ = [self.decode_as_matrix(g, "GLZ", ListForm=True) for g in self.representations["GLZ"]["gens"]] + LZsplit = [split_matrix_list(self.decode_as_matrix(g, "GLZ", ListForm=True),nZ) for g in self.representations["GLZ"]["gens"]] + else: + nZ, LZ, LZsplit = None, None, None + if "GLFp" in self.representations: + nFp = self.representations["GLFp"]["d"] + Fp = self.representations["GLFp"]["p"] + LFp = [self.decode_as_matrix(g, "GLFp", ListForm=True) for g in self.representations["GLFp"]["gens"]] +# LFpsplit = "[" + ",".join([split_matrix_list_Fq(self.decode_as_matrix(g, "GLFp", ListForm=True), nFp, Fp) for g in self.representations["GLFp"]["gens"]]) +"]" + else: + nFp, Fp, LFp = None, None, None + #nFp, Fp, LFp, LFpsplit = None, None, None, None + if "GLZN" in self.representations: + nZN = self.representations["GLZN"]["d"] + N = self.representations["GLZN"]["p"] + LZN = [self.decode_as_matrix(g, "GLZN", ListForm=True) for g in self.representations["GLZN"]["gens"]] + LZNsplit ="[" + ",".join([split_matrix_list_ZN(self.decode_as_matrix(g, "GLZN", ListForm=True) , nZN, N) for g in self.representations["GLZN"]["gens"]]) +"]" + else: + nZN, N, LZN, LZNsplit = None, None, None, None + if "GLZq" in self.representations: + nZq = self.representations["GLZq"]["d"] + Zq = self.representations["GLZq"]["q"] + LZq = [self.decode_as_matrix(g, "GLZq", ListForm=True) for g in self.representations["GLZq"]["gens"]] + LZqsplit ="[" + ",".join([split_matrix_list_ZN(self.decode_as_matrix(g, "GLZq", ListForm=True) , nZq, Zq) for g in self.representations["GLZq"]["gens"]]) +"]" + else: + nZq, Zq, LZq, LZqsplit = None, None, None, None +# add below for GLFq implementation +# if "GLFq" in self.representations: +# nFq = self.representations["GLFq"]["d"] +# Fq = self.representations["GLFq"]["q"] +# LFq = [self.decode_as_matrix(g, "GLFq", ListForm=True) for g in self.representations["GLFq"]["gens"]] +# LFqsplit = "[" + ",".join([split_matrix_list_Fq(self.decode_as_matrix(g, "GLFq", ListForm=True), nFq, Fq) for g in self.representations["GLFq"]["gens"]]) +"]" +# else: +# nFq, Fq, LFq, LFqsplit = None, None, None, None + + data = {'gens' : gens, 'pccodelist': pccodelist, 'pccode': pccode, + 'ordgp': ordgp, 'used_gens': used_gens, 'gap_assign': gap_assign, + 'magma_assign': magma_assign, 'deg': deg, 'perms' : perms, + 'nZ': nZ, 'nFp': nFp, 'nZN': nZN, 'nZq': nZq, #'nFq': nFq, + 'Fp': Fp, 'N': N, 'Zq': Zq, #'Fq': Fq, + 'LZ': LZ, 'LFp': LFp, 'LZN': LZN, 'LZq': LZq, #'LFq': LFq, + 'LZsplit': LZsplit, 'LZNsplit': LZNsplit, 'LZqsplit': LZqsplit, + # 'LFpsplit': LFpsplit, 'LFqsplit': LFqsplit, # add for GLFq GAP + } + for prop in code: + for lang in code['prompt']: + code[prop][lang] = code[prop][lang].format(**data) + return code + # The following attributes are used in create_boolean_string @property def nonabelian(self): @@ -2721,6 +2879,7 @@ def element_orders(self): for T in cartesian_product_iterator( [Zmod(m) for m in self.snf])) + class WebAbstractSubgroup(WebObj): table = db.gps_subgroups diff --git a/lmfdb/templates/homepage.html b/lmfdb/templates/homepage.html index f033b98276..21c5681f04 100644 --- a/lmfdb/templates/homepage.html +++ b/lmfdb/templates/homepage.html @@ -231,7 +231,7 @@

Properties

{% set slash = joiner("/ ") %} {% for lang in code['show']|sort %} {# override show names for standard languages to ensure consistency #} - {% set show = 'PariGP' if lang == 'pari' else 'SageMath' if lang == 'sage' else 'Magma' if lang == 'magma' else 'Oscar' if lang == 'oscar' else code['show']['lang'] %} + {% set show = 'PariGP' if lang == 'pari' else 'SageMath' if lang == 'sage' else 'Magma' if lang == 'magma' else 'Oscar' if lang == 'oscar' else "Gap" if lang == 'gap' else code['show']['lang'] %} {{slash()}}{{show}} {% endfor %} diff --git a/lmfdb/templates/style.css b/lmfdb/templates/style.css index fb7853dc24..6fc1772d9f 100644 --- a/lmfdb/templates/style.css +++ b/lmfdb/templates/style.css @@ -1955,6 +1955,8 @@ div.codebox { white-space: pre; } + + /* Styling of Maass form plot */ div.maassformplot {
{display_knowl("group.presentation", "Presentation")}:{pres}
{display_knowl("group.presentation", "Presentation")}:{pres}
{display_knowl("group.presentation", "Presentation")}:{pres}
{display_knowl("group.permutation_gens", "Permutation group")}:{gens}
{display_knowl("group.permutation_gens", "Permutation group")}:{gens}
{gens}
{gens}
{display_knowl("group.matrix_group", "Matrix group")}:{gens}
{display_knowl("group.matrix_group", "Matrix group")}:{gens}
{prompt}: {line}