Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

master -> dev #5987

Merged
merged 14 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lmfdb/backend/searchtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def _parse_values(self, D):
[]
sage: db.lfunc_lfunctions._parse_values({'bad_lfactors':[1,2]})[1][0]
'[1, 2]'
sage: db.char_orbits._parse_values({'modulus':3})
sage: db.char_dirichlet._parse_values({'modulus':3})
[3]
"""

Expand Down
36 changes: 0 additions & 36 deletions lmfdb/characters/ListCharacters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# ListCharacters.py
import re
from sage.all import lcm, factor, Integers
from sage.databases.cremona import cremona_letter_code
from lmfdb.characters.web_character import WebDirichlet, parity_string
from lmfdb.characters.TinyConrey import ConreyCharacter
from lmfdb.utils import flash_error, integer_divisors

Expand Down Expand Up @@ -101,37 +99,3 @@ def out(chi):
entries2[k] = l
cols = headers
return headers, entries2, rows, cols


def info_from_db_orbit(orbit):
mod = orbit['modulus']
conductor = orbit['conductor']
orbit_index = orbit['orbit_index']
orbit_letter = cremona_letter_code(orbit_index - 1)
orbit_label = f"{mod}.{orbit_letter}"
order = orbit['order']
is_odd = parity_string(orbit['parity'])
is_prim = _is_primitive(orbit['is_primitive'])
return [(mod,
num,
conductor,
orbit_label,
order,
is_odd,
is_prim,
WebDirichlet.char2tex(mod, num))
for num in orbit['galois_orbit']]


def _is_primitive(db_primitive):
"""
Translate db's primitive entry to boolean.
"""
return str(db_primitive) == "True"


def _is_odd(db_parity):
"""
Translate db's parity entry to boolean.
"""
return int(db_parity) == -1
3 changes: 2 additions & 1 deletion lmfdb/characters/TinyConrey.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ class ConreyCharacter():
"""

def __init__(self, modulus, number):
assert gcd(modulus, number)==1
if gcd(modulus, number) != 1:
raise ValueError(f"Conrey number ({number}) must be coprime to the modulus ({modulus})")
self.modulus = Integer(modulus)
self.number = Integer(number)
self.conrey = Mod(number,modulus)
Expand Down
61 changes: 30 additions & 31 deletions lmfdb/characters/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from lmfdb.characters import characters_page
from lmfdb import db

ORBIT_MAX_MOD = 100000
ORBIT_MAX_MOD = 1000000

# make url_character available from templates

Expand Down Expand Up @@ -75,9 +75,9 @@ def render_characterNavigation():

class DirichSearchArray(SearchArray):
noun = "character"
sorts = [("", "modulus", ["modulus", "orbit_index"]),
("conductor", "conductor", ["conductor", "modulus", "orbit_index"]),
("order", "order", ["order", "modulus", "orbit_index"])]
sorts = [("", "modulus", ["modulus", "orbit"]),
("conductor", "conductor", ["conductor", "modulus", "orbit"]),
("order", "order", ["order", "modulus", "orbit"])]
jump_example = "13.2"
jump_egspan = r"e.g. 13.2 for the Dirichlet character \(\displaystyle\chi_{13}(2,·)\),or 13.f for its Galois orbit."
jump_knowl = "character.dirichlet.search_input"
Expand Down Expand Up @@ -158,9 +158,9 @@ def common_parse(info, query):
if 'parity' in info:
parity = info['parity']
if parity == 'even':
query['parity'] = 1
query['is_even'] = True
elif parity == 'odd':
query['parity'] = -1
query['is_even'] = False
parse_bool(info, query, "is_primitive", name="is_primitive")
parse_bool(info, query, "is_real", name="is_real")
parse_bool(info, query, "is_minimal", name="is_minimal")
Expand Down Expand Up @@ -215,14 +215,13 @@ def url_for_label(label):
number = int(parts_of_label[2])
return url_for(".render_Dirichletwebpage", modulus=modulus, orbit_label=orbit_label, number=number)

def display_galois_orbit(modulus, first_label, last_label, degree):
def display_galois_orbit(modulus, first, last, degree):

if degree == 1:
orbit = first_label.split(".")[1]
disp = r'<a href="{0}/{1}">\(\chi_{{{0}}}({1}, \cdot)\)</a>'.format(modulus, orbit)
disp = r'<a href="{0}/{1}">\(\chi_{{{0}}}({1}, \cdot)\)</a>'.format(modulus, first)
return f'<p style="margin-top: 0px;margin-bottom:0px;">\n{disp}\n</p>'
else:
orbit = [lab.split(".")[1] for lab in [first_label, last_label]]
orbit = [first, last]
disp = [r'<a href="{0}/{1}">\(\chi_{{{0}}}({1}, \cdot)\)</a>'.format(modulus, o) for o in orbit]
if degree == 2:
disp = "$,$&nbsp".join(disp)
Expand All @@ -233,20 +232,20 @@ def display_galois_orbit(modulus, first_label, last_label, degree):

character_columns = SearchColumns([
LinkCol("label", "character.dirichlet.galois_orbit_label", "Orbit label", lambda label: label.replace(".", "/"), align="center"),
MultiProcessedCol("conrey", "character.dirichlet.conrey", "Conrey labels", ["modulus", "first_label", "last_label", "degree"],
MultiProcessedCol("conrey", "character.dirichlet.conrey", "Conrey labels", ["modulus", "first", "last", "degree"],
display_galois_orbit, align="center", short_title="Conrey labels", apply_download=False),
MathCol("modulus", "character.dirichlet.modulus", "Modulus"),
MathCol("conductor", "character.dirichlet.conductor", "Conductor"),
MathCol("order", "character.dirichlet.order", "Order"),
ProcessedCol("parity", "character.dirichlet.parity", "Parity", lambda parity: "even" if parity == 1 else "odd"),
ProcessedCol("is_even", "character.dirichlet.parity", "Parity", lambda is_even: "even" if is_even else "odd"),
CheckCol("is_primitive", "character.dirichlet.primitive", "Primitive")])

@search_wrap(
table=db.char_orbits,
table=db.char_dirichlet,
title="Dirichlet character search results",
err_title="Dirichlet character search input error",
columns=character_columns,
shortcuts={"jump": jump, "download": Downloader(db.char_orbits)},
shortcuts={"jump": jump, "download": Downloader(db.char_dirichlet)},
url_for_label=url_for_label,
learnmore=learn,
random_projection="label",
Expand Down Expand Up @@ -361,8 +360,8 @@ def make_webchar(args, get_bread=False):
return WebDBDirichletOrbit(**args)
if args.get('orbit_label') is None:
chi = ConreyCharacter(modulus, number)
db_orbit_label = db.char_orbits.lucky(
{'modulus': modulus, 'first_label': "{}.{}".format(modulus, chi.min_conrey_conj)},
db_orbit_label = db.char_dirichlet.lucky(
{'modulus': modulus, 'first': chi.min_conrey_conj},
projection='label'
)
args['orbit_label'] = db_orbit_label.split('.')[-1]
Expand Down Expand Up @@ -471,8 +470,8 @@ def render_Dirichletwebpage(modulus=None, orbit_label=None, number=None):

if modulus <= ORBIT_MAX_MOD:
chi = ConreyCharacter(modulus, number)
db_orbit_label = db.char_orbits.lucky(
{'modulus': modulus, 'first_label': "{}.{}".format(modulus, chi.min_conrey_conj)},
db_orbit_label = db.char_dirichlet.lucky(
{'modulus': modulus, 'first': chi.min_conrey_conj},
projection='label'
)
real_orbit_label = db_orbit_label.split('.')[-1]
Expand Down Expand Up @@ -514,18 +513,18 @@ def render_Dirichletwebpage(modulus=None, orbit_label=None, number=None):

@characters_page.route("/Dirichlet/data/<label>")
def dirchar_data(label):
if label.count(".") == 2:
modulus, orbit_label, number = label.split(".")
title = f"Dirichlet character data - {modulus}.{number}"
tail = [(f"{modulus}.{number}", url_for(".render_Dirichletwebpage", modulus=modulus, number=number)),
("Data", " ")]
return datapage(f"{modulus}.{orbit_label}", "char_orbits", title=title, bread=bread(tail))
elif label.count(".") == 1:
if label.count(".") == 1:
modulus, orbit_label = label.split(".")
title = f"Dirichlet character data - {modulus}.{orbit_label}"
tail = [(label, url_for(".render_Dirichletwebpage", modulus=modulus, orbit_label=orbit_label)),
("Data", " ")]
return datapage(label, "char_orbits", title=title, bread=bread(tail))
return datapage(label, "char_dirichlet", title=title, bread=bread(tail))
elif label.count(".") == 2:
modulus, orbit_label, number = label.split(".")
title = f"Dirichlet character data - {modulus}.{orbit_label}.{number}"
tail = [(label, url_for(".render_Dirichletwebpage", modulus=modulus, number=number)),
("Data", " ")]
return datapage(f"{modulus}.{orbit_label}", "char_dirichlet", title=title, bread=bread(tail))
else:
return abort(404, f"Invalid label {label}")

Expand Down Expand Up @@ -595,7 +594,7 @@ def random_Dirichletwebpage():
def interesting():
return interesting_knowls(
"character.dirichlet",
db.char_orbits,
db.char_dirichlet,
url_for_label=url_for_label,
title="Some interesting Dirichlet characters",
bread=bread("Interesting"),
Expand Down Expand Up @@ -692,7 +691,7 @@ def yesno(x):


class DirichStats(StatsDisplay):
table = db.char_orbits
table = db.char_dirichlet
baseurl_func = ".render_DirichletNavigation"
stat_list = [
{"cols": ["conductor"]},
Expand Down Expand Up @@ -734,9 +733,9 @@ class DirichStats(StatsDisplay):
"is_real": yesno}

def __init__(self):
self.nchars = db.char_orbits.sum('degree')
self.norbits = db.char_orbits.count()
self.maxmod = db.char_orbits.max("modulus")
self.nchars = db.char_dirichlet.sum('degree')
self.norbits = db.char_dirichlet.count()
self.maxmod = db.char_dirichlet.max("modulus")

@property
def short_summary(self):
Expand Down
2 changes: 1 addition & 1 deletion lmfdb/characters/templates/Character.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ <h2>
function value_handler(evt) {
evt.preventDefault();
var val = $("#calc-value-input").val();
$("#calc-value-output").load("{{ url_character(type=type, calc='value', number_field = nflabel, modulus = modlabel, number=numlabel) }}" + "?val=" + val,
$("#calc-value-output").load("{{ url_character(type=type, calc='value', modulus = modlabel, number=numlabel) }}" + "?val=" + val,
function() {
{# render the output #}
renderMathInElement($("#calc-value-output").get(0), katexOpts);
Expand Down
4 changes: 2 additions & 2 deletions lmfdb/characters/test_characters.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_Dirichletmethods(self):
class DirichletSearchTest(LmfdbTest):
def test_nchars(self):
from lmfdb import db
nchars = db.char_orbits.sum('degree')
nchars = db.char_dirichlet.sum('degree')
W = self.tc.get('/Character/Dirichlet/')
assert comma(nchars) in W.get_data(as_text=True)

Expand Down Expand Up @@ -249,6 +249,6 @@ def test_sage_code_gens(self):

def test_underlying_data(self):
W = self.tc.get('/Character/Dirichlet/data/289.j.7').get_data(as_text=True)
assert 'is_minimal' in W and 'last_label' in W
assert 'is_minimal' in W and 'last' in W
W = self.tc.get('/Character/Dirichlet/data/289.j').get_data(as_text=True)
assert 'is_minimal' in W
47 changes: 16 additions & 31 deletions lmfdb/characters/web_character.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
2. an object type (list of groups, character group, character)

For Dirichlet characters of modulus up to 100,000, the database holds data for
character orbits in char_orbits. For these objects, there are the "DB" classes
character orbits in char_dirichlet. For these objects, there are the "DB" classes
that replace on-the-fly computation with database lookups.

The code thus defines, from the generic top class WebCharObject
Expand Down Expand Up @@ -45,6 +45,7 @@

from flask import url_for
from collections import defaultdict
from sage.databases.cremona import cremona_letter_code
from sage.all import (gcd, ZZ, Rational, Integers, cached_method,
euler_phi, latex)
from sage.misc.lazy_attribute import lazy_attribute
Expand All @@ -60,7 +61,7 @@
logger = make_logger("DC")

def parity_string(n):
return "odd" if n == -1 else "even"
return ("even" if n else "odd") if isinstance(n, bool) else ("odd" if n == -1 else "even")

def bool_string(b):
return "yes" if b else "no"
Expand Down Expand Up @@ -523,16 +524,13 @@ def properties(self):
def friends(self):
from lmfdb.lfunctions.LfunctionDatabase import get_lfunction_by_url
f = []
cglink = url_character(type=self.type,number_field=self.nflabel,modulus=self.modlabel)
f.append( ("Character group", cglink) )
if self.nflabel:
f.append( ('Number field', '/NumberField/' + self.nflabel) )
if self.type == 'Dirichlet' and self.chi.is_primitive() and self.conductor < 100000:
url = url_character(type=self.type, number_field=self.nflabel, modulus=self.modlabel, number=self.numlabel)
if self.type == 'Dirichlet' and self.chi.is_primitive() and self.conductor < 1000000:
url = url_character(type=self.type, modulus=self.modlabel, number=self.numlabel)
lfun_label = get_lfunction_by_url(url[1:], projection='label')
if lfun_label:
f.append(('L-function', url_for('by_full_label', lfun_label)))
if self.type == 'Dirichlet':
f.append( ("Character group", url_character(type=self.type,modulus=self.modlabel)) )
f.append( ('Sato-Tate group', url_for('st.by_label', label=f'0.1.{self.order}')))
if len(self.vflabel)>0:
f.append( ("Value field", url_for("number_fields.by_label", label=self.vflabel)) )
Expand Down Expand Up @@ -579,13 +577,10 @@ def _compute(self):
def _populate_from_db(self):

gal_orbit = self.chi.galois_orbit()
min_conrey_conj = gal_orbit[0]

orbit_data = db.char_orbits.lucky(
{'modulus': self.modulus, 'first_label': "{}.{}".format(self.modulus, min_conrey_conj)}
)
orbit_data = db.char_dirichlet.lucky({'modulus': self.modulus, 'first': gal_orbit[0]})

self.orbit_index = orbit_data['orbit_index']
self.orbit_index = orbit_data['orbit']
self.orbit_label = orbit_data['label'].split(".")[1]
self.order = int(orbit_data['order'])
self.conductor = self.chi.conductor() # this also sets indlabel
Expand Down Expand Up @@ -615,10 +610,6 @@ def _set_generators_and_genvalues(self):
self.genvalues = self.textuple([self._tex_value(v) for v in vals])

def _set_values_and_groupelts(self):
"""
The char_orbits db collection does not contain `values`,
so these are computed on the fly.
"""
if self.modulus == 1:
self.groupelts = [1]
self.values = [r"\(1\)"]
Expand Down Expand Up @@ -673,7 +664,7 @@ def _set_isminimal(self, orbit_data):
self.isminimal = bool_string(orbit_data['is_minimal'])

def _set_parity(self, orbit_data):
self.parity = parity_string(int(orbit_data['parity']))
self.parity = parity_string(orbit_data['is_even'])

def _set_galoisorbit(self, gal_orbit):
if self.modulus == 1:
Expand Down Expand Up @@ -903,12 +894,8 @@ def char_dbdata(self, mod, num):
valuepairs = compute_values(chi, self.groupelts)
min_conrey_conj = chi.min_conrey_conj

# This next db lookup takes ages, I don't know how to speed it up
orbit_label = db.char_orbits.lucky(
{'modulus': mod, 'first_label': "{}.{}".format(mod, min_conrey_conj)},
projection='label'
)
logger.debug(f"[DC DB query] modulus = {mod}, first_label = {mod},{min_conrey_conj} -> {orbit_label}")
orbit_label = db.char_dirichlet.lucky({'modulus':mod,'first':min_conrey_conj},projection='label')
logger.debug(f"[DC DB query] modulus = {mod}, first = {min_conrey_conj} -> {orbit_label}")

return is_prim, order, orbit_label, valuepairs

Expand Down Expand Up @@ -1138,9 +1125,7 @@ def title(self):

def _populate_from_db(self):

orbit_data = db.char_orbits.lucky(
{'modulus': self.modulus, 'label': self.label}
)
orbit_data = db.char_dirichlet.lookup(self.label)
if orbit_data is None:
raise ValueError

Expand All @@ -1149,10 +1134,10 @@ def _populate_from_db(self):
self.degree = orbit_data['degree']
self.isprimitive = bool_string(orbit_data['is_primitive'])
self.isminimal = bool_string(orbit_data['is_minimal'])
self.parity = parity_string(int(orbit_data['parity']))
self.parity = parity_string(orbit_data['is_even'])
self._set_kernel_field_poly(orbit_data)
self.orbit_index = orbit_data['orbit_index']
self.inducing = orbit_data['primitive_label']
self.orbit_index = orbit_data['orbit']
self.inducing = "{}.{}".format(orbit_data['conductor'],cremona_letter_code(orbit_data['primitive_orbit']-1))
self.ind_orbit_label = self.inducing.split(".")[1]

# The rest of the function is setting the Galois orbit
Expand All @@ -1170,7 +1155,7 @@ def _populate_from_db(self):
]

def _set_kernel_field_poly(self, orbit_data):
an_orbit_rep = int(orbit_data['first_label'].split(".")[1])
an_orbit_rep = orbit_data['first']
chi = ConreyCharacter(self.modulus, an_orbit_rep)
self.first_chi = chi
if self.order <= 100:
Expand Down
2 changes: 1 addition & 1 deletion lmfdb/classical_modular_forms/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ def contains_level(D):
if level > ORBIT_MAX_MOD:
raise ValueError("The level is too large.")
# Check that this character is actually primitive
conductor = db.char_orbits.lucky({'modulus':level, 'orbit_index': orbit}, 'conductor')
conductor = db.char_dirichlet.lucky({'modulus':level, 'orbit': orbit}, 'conductor')
if conductor is None:
raise ValueError("No character orbit with this label exists.")
if conductor != level:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ <h2 id="qexp-anchor"> {{ KNOWL('cmf.space_trace_form', title='Trace form') }} </

<h2> {{ KNOWL('cmf.decomposition.new.gamma1', title='Decomposition') }} of \({{ space.new_latex() }}\)</h2>

<p> We only show spaces with {{ KNOWL('character.dirichlet.parity',title="odd parity" if space.weight_parity == -1 else "even parity")}}, since no modular forms exist when this condition is not satisfied. Within each space \( S_k^{\mathrm{new}}(N, \chi) \) we list the newforms together with their dimension.</p>
<p> We only show spaces with {{ KNOWL('character.dirichlet.parity',title="odd parity" if space.weight_parity == -1 else "even parity")}}, since no modular forms exist when this condition is not satisfied. Within each space \( S_k^{\mathrm{new}}(N, \chi) \) we list available newforms together with their dimension.</p>

<table class="mfdata">
<thead>
Expand Down
Loading
Loading