By {{ KNOWL('character.dirichlet.modulus', title="modulus") }}:
-
{% for rnge in info.modulus_list %} {{rnge}} {% endfor %} $\cdots$
+
{% for rnge in info.modulus_list %} {{rnge}} {% endfor %} $\cdots$
By {{ KNOWL('character.dirichlet.conductor', title="conductor") }}:
{% for rnge in info.conductor_list %} {{rnge}} {% endfor %} $\cdots$
diff --git a/lmfdb/characters/test_characters.py b/lmfdb/characters/test_characters.py
index 792c8178eb..485f9434e2 100644
--- a/lmfdb/characters/test_characters.py
+++ b/lmfdb/characters/test_characters.py
@@ -31,8 +31,8 @@ def test_even_odd(self):
assert '>%sfurther statistics.' % (latex_comma(self.ngroups), url_for(".statistics"))
+ return r'The database currently contains %s groups, including all transitive subgroups of $S_n$ (up to conjugacy) for $n \le 47$ and $n \ne 32$. Here are some further statistics.' % (comma(self.ngroups), url_for(".statistics"))
diff --git a/lmfdb/groups/abstract/main.py b/lmfdb/groups/abstract/main.py
index a8d01f1cd4..73090a966f 100644
--- a/lmfdb/groups/abstract/main.py
+++ b/lmfdb/groups/abstract/main.py
@@ -48,6 +48,7 @@
pos_int_and_factor,
sparse_cyclotomic_to_mathml,
integer_to_mathml,
+ redirect_no_cache,
)
from lmfdb.utils.search_parsing import (parse_multiset, search_parser, collapse_ors)
from lmfdb.utils.interesting import interesting_knowls
@@ -841,11 +842,10 @@ def dynamic_statistics():
@abstract_page.route("/random")
+@redirect_no_cache
def random_abstract_group():
label = db.gps_groups.random(projection="label")
- response = make_response(redirect(url_for(".by_label", label=label), 307))
- response.headers["Cache-Control"] = "no-cache, no-store"
- return response
+ return url_for(".by_label", label=label)
@abstract_page.route("/interesting")
diff --git a/lmfdb/groups/abstract/web_groups.py b/lmfdb/groups/abstract/web_groups.py
index 94ebb57197..4056a923c8 100644
--- a/lmfdb/groups/abstract/web_groups.py
+++ b/lmfdb/groups/abstract/web_groups.py
@@ -2335,7 +2335,8 @@ def representation_line(self, rep_type, skip_head=False):
if rep_type == "Lie":
desc = "Groups of " + display_knowl("group.lie_type", "Lie type")
reps = ", ".join(fr"$\{rep['family']}({rep['d']},{rep['q']})$" for rep in rdata)
- return f'
{desc}:
{reps}
'
+ code_cmd = " ".join([self.create_lie_type_snippet(rep['family']) for rep in rdata])
+ return f'
{desc}:
{reps}
{code_cmd}
'
elif rep_type == "PC":
pres = self.presentation()
if not skip_head: #add copy button in certain cases
@@ -2841,6 +2842,32 @@ def create_snippet(self,item):
post="")
return snippet.place_code()
+ # Used for creating code snipets for Lie type representations
+ def create_lie_type_snippet(self,item):
+ snippet = CodeSnippet(self.code_snippets(), item)
+ return snippet.place_code()
+
+ @lazy_attribute
+ def lie_representations(self):
+ # Ideally we should get the Lie-type representations from the "gps_special_names" table:
+ # lie_reps = list(db.gps_special_names.search({'label':self.label}, projection={'family','gens','parameters'}))
+
+ # TODO: We should update groups data to use new family names
+ # For now, we'll implement a "old to new" family dictionary (can delete once groups data is updated)
+ old_to_new_family_name = {"GO":"Orth", "GOPlus":"OrthPlus", "GOMinus":"OrthMinus", "GU":"Unitary", "PGO":"PO",
+ "PGOPlus":"POPlus", "PGOMinus":"POMinus", "PGU":"PU", "CSp":"GSp", "CSO":"GSO", "CSOPlus":"GSOPlus",
+ "CSOMinus":"GSOMinus", "CSU":"GSU", "CO":"GOrth", "COPlus":"GOrthPlus", "COMinus":"GOrthMinus",
+ "CU":"GUnitary"}
+
+ # Temporary solution: Get Lie type representations from gps_groups table (the order here is important!)
+ if "Lie" in self.representations:
+ lie_reps = self.representations["Lie"]
+ for i in range(len(lie_reps)):
+ if lie_reps[i]['family'] in old_to_new_family_name:
+ lie_reps[i]['family'] = old_to_new_family_name[lie_reps[i]['family']]
+ return lie_reps
+ return None
+
@cached_method
def code_snippets(self):
if self.live():
@@ -2899,7 +2926,7 @@ def code_snippets(self):
LZqsage = "["+", ".join(["MS("+str(split_matrix_list(self.decode_as_matrix(g, "GLZq", ListForm=True), nZq))+")" for g in self.representations["GLZq"]["gens"]])+"]"
else:
nZq, Zq, LZq, LZqsplit, LZqsage = None, None, None, None, None
-# add below for GLFq implementation
+ # add below for GLFq implementation
if "GLFq" in self.representations:
nFq = self.representations["GLFq"]["d"]
Fq = self.representations["GLFq"]["q"]
@@ -2922,6 +2949,72 @@ def code_snippets(self):
'LZsage': LZsage, 'LFpsage': LFpsage, 'LZNsage': LZNsage, 'LZqsage': LZqsage, 'LFqsage': LFqsage,
}
+ # The following code implements code snippets for the Lie-type matrix representations
+ magma_top_lie, gap_top_lie, sage_top_lie = None, None, None # Keep track of Lie-type snippet for use as a top code snippet
+ gap_used_lie_gens, sage_used_lie_gens = False, False # Track whether we need to use generators
+ if self.lie_representations is not None:
+ # Get Magma commands for all the Lie type families
+ gps_families_data = list(db.gps_families.search(projection={'family','magma_cmd'}))
+ magma_commands = {d['family']: d['magma_cmd'] for d in gps_families_data}
+ # Hardcoded list of Lie Type families available in GAP and Sage (NB: Must ensure their implementation agrees with our definition!)
+ gap_families = ['GL','SL','PSL','PGL','Sp','SO','SU','PSp','PSO','PSU','Orth','Unitary','Omega','PO','PU','POmega','PGammaL','PSigmaL']
+ sage_families = ['GL','SL','PSL','PGL','PSp','PSU','Orth','Unitary','PU']
+
+ # Prioritise displaying the first Lie type representation which is implemented in the language
+ for lie_rep in self.lie_representations:
+ code[lie_rep['family']] = dict()
+ nLie, qLie = ZZ(lie_rep['d']), ZZ(lie_rep['q'])
+
+ code[lie_rep['family']]['magma'] = "G := "+magma_commands[lie_rep['family']].replace("n,q", str(nLie)+","+str(qLie))+";"
+ if magma_top_lie is None:
+ magma_top_lie = code[lie_rep['family']]['magma']
+
+ if lie_rep['family'] in gap_families:
+ code[lie_rep['family']]['gap'] = code[lie_rep['family']]['magma']
+ if (gap_top_lie is None) or gap_used_lie_gens:
+ gap_top_lie = code[lie_rep['family']]['gap']
+ gap_used_lie_gens = False
+ elif "gens" in lie_rep:
+ lie_mats = [self.decode_as_matrix(g, "Lie", ListForm=True) for g in lie_rep["gens"]]
+ if qLie.is_prime():
+ e = libgap.One(GF(qLie))
+ lie_gap_mats = [split_matrix_list_Fp(mat, nLie, e) for mat in lie_mats]
+ gap_lie_code_snippet = code['GLFp']['gap'].format(**{'LFpsplit':lie_gap_mats})
+ else:
+ lie_gap_mats = "[" + ",".join(split_matrix_list_Fq(mat, nLie, qLie) for mat in lie_mats) + "]"
+ gap_lie_code_snippet = code['GLFq']['gap'].format(**{'LFqsplit':lie_gap_mats})
+ if gap_top_lie is None:
+ gap_top_lie, gap_used_lie_gens = gap_lie_code_snippet, True
+
+ if lie_rep['family'] in sage_families:
+ code[lie_rep['family']]['sage'] = "G = "+magma_commands[lie_rep['family']].replace("n,q", str(nLie)+","+str(qLie))
+ if (sage_top_lie is None) or sage_used_lie_gens:
+ sage_top_lie = code[lie_rep['family']]['sage']
+ sage_used_lie_gens = False
+ elif "gens" in lie_rep:
+ lie_mats = [self.decode_as_matrix(g, "Lie", ListForm=True) for g in lie_rep["gens"]]
+ if qLie.is_prime():
+ lie_sage_mats = "["+", ".join(["MS("+str(split_matrix_list(self.decode_as_matrix(g, "Lie", ListForm=True),nLie))+")" for g in lie_rep["gens"]])+"]"
+ sage_lie_code_snippet = code['GLFp']['sage'].format(**{'LFpsage':lie_sage_mats, 'nFp':nLie, 'Fp':qLie})
+ else:
+ lie_sage_mats = "["+", ".join(["MS("+str(split_matrix_Fq_add_al(mat, nLie))+")" for mat in lie_mats])+"]"
+ sage_lie_code_snippet = code['GLFq']['sage'].format(**{'LFqsage':lie_sage_mats, 'nFq':nLie, 'Fq':qLie})
+ if sage_top_lie is None:
+ sage_top_lie, sage_used_lie_gens = sage_lie_code_snippet, True
+
+ # In the case no Lie type representation is implemented natively in Sage or GAP, we display the first one with gens as a matrix group
+ # as a code snippet in the "Constructions" header (and as a possible candidate for the top code snippet)
+ if (gap_top_lie is not None) and gap_used_lie_gens:
+ for lie_rep in self.lie_representations:
+ if "gens" in lie_rep:
+ code[lie_rep['family']]['gap'] = gap_top_lie
+ break
+ if (sage_top_lie is not None) and sage_used_lie_gens:
+ for lie_rep in self.lie_representations:
+ if "gens" in lie_rep:
+ code[lie_rep['family']]['sage'] = sage_top_lie
+ break
+
# Here, we add the (perhaps subjectively?) "best" implementation of this group as a code snippet in Magma/GAP/SageMath,
# to display at the top of each group page. This is computed and stored in code['code_description'].
# If the group is a member of a special family (i.e. Cyclic,Symmetric,Dihedral,Alternating,Dicyclic,LieType,Chevalley),
@@ -2959,27 +3052,13 @@ def code_snippets(self):
code['code_description']['gap'] = "G := DicyclicGroup("+str(self.order)+");" # GAP Dic(n) has order n
code['code_description']['sage'] = "G = DiCyclicGroup("+str(self.order/4)+")" # Sage Dic(n) has order 4n
else:
- # List of Lie Type families available in Magma (NB: Must ensure the Magma implementation agrees with our definition!)
- for f in ['GL', 'SL', 'PSL', 'PGL', 'Sp', 'SO', 'SU', 'PSp', 'PSO', 'PSU', 'SOPlus', 'SOMinus']:
- if f in [t['family'] for t in self_families]:
- fam_index = [t['family'] for t in self_families].index(f)
- lie_params = str(self_families[fam_index]['parameters']['n'])+", "+str(self_families[fam_index]['parameters']['q'])
- code['code_description']['magma'] = "G := "+f+"("+lie_params+");"
- break
- # List of Lie Type families available in GAP (NB: Must ensure the GAP implementation agrees with our definition!)
- for f in ['GL', 'SL', 'PSL', 'PGL', 'Sp', 'SO', 'SU', 'PSp', 'PSO', 'PSU']:
- if f in [t['family'] for t in self_families]:
- fam_index = [t['family'] for t in self_families].index(f)
- lie_params = str(self_families[fam_index]['parameters']['n'])+", "+str(self_families[fam_index]['parameters']['q'])
- code['code_description']['gap'] = "G := "+f+"("+lie_params+");"
- break
- # List of Lie Type families available in Sage (NB: Must ensure the Sage implementation agrees with our definition!)
- for f in ['GL', 'SL', 'PSL', 'PGL']:
- if f in [t['family'] for t in self_families]:
- fam_index = [t['family'] for t in self_families].index(f)
- lie_params = str(self_families[fam_index]['parameters']['n'])+", "+str(self_families[fam_index]['parameters']['q'])
- code['code_description']['sage'] = "G = "+f+"("+lie_params+")"
- break
+ # Use a Lie Type matrix construction (if it exists and the Lie type generators were not used)
+ if magma_top_lie is not None:
+ code['code_description']['magma'] = magma_top_lie
+ if (gap_top_lie is not None) and (not gap_used_lie_gens):
+ code['code_description']['gap'] = gap_top_lie
+ if (sage_top_lie is not None) and (not sage_used_lie_gens):
+ code['code_description']['sage'] = sage_top_lie
# Checking if group is in the Chevalley or Twisted Chevalley family
if ('Chev' in [t['family'] for t in self_families]) and ('magma' not in code['code_description']):
chev_index = [t['family'] for t in self_families].index("Chev")
@@ -3018,6 +3097,11 @@ def code_snippets(self):
for lang in code[code_rep]:
if lang not in code['code_description']:
code['code_description'][lang] = code[code_rep][lang]
+ # Finally, try using Lie constructions which required use of the generators
+ if (gap_top_lie is not None) and ('gap' not in code['code_description']):
+ code['code_description']['gap'] = gap_top_lie
+ if (sage_top_lie is not None) and ('sage' not in code['code_description']):
+ code['code_description']['sage'] = sage_top_lie
# Otherwise, if absolutely all else fails, we display no code snippet at the top :(
# If no Sage top code snippet, then we resort to implementing the group G using the GAP interface in Sage
diff --git a/lmfdb/groups/glnC/main.py b/lmfdb/groups/glnC/main.py
index 1e12089595..bfc9e79174 100644
--- a/lmfdb/groups/glnC/main.py
+++ b/lmfdb/groups/glnC/main.py
@@ -9,7 +9,7 @@
flash_error, SearchArray, TextBox, CountBox, YesNoBox,
parse_ints, parse_bool, clean_input, to_dict, sparse_cyclotomic_to_latex,
# parse_gap_id, parse_bracketed_posints,
- search_wrap)
+ search_wrap, redirect_no_cache)
from lmfdb.utils.search_columns import SearchColumns, LinkCol, MathCol
from lmfdb.groups.abstract.web_groups import group_names_pretty
from lmfdb.groups.abstract.main import abstract_group_display_knowl, abstract_subgroup_label_regex
@@ -58,9 +58,10 @@ def index():
@glnC_page.route("/random")
+@redirect_no_cache
def random_glnC_group():
label = db.gps_crep.random(projection='label')
- return redirect(url_for(".by_label", label=label))
+ return url_for(".by_label", label=label)
@glnC_page.route("/