Skip to content

Commit

Permalink
Merge pull request #6254 from roed314/field_is
Browse files Browse the repository at this point in the history
Additional search options for number fields
  • Loading branch information
jwj61 authored Nov 17, 2024
2 parents 6e047f9 + bb2c2a1 commit 23c8d40
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 17 deletions.
83 changes: 83 additions & 0 deletions lmfdb/galois_groups/transitive_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,3 +936,86 @@ def get_aliases():
aliases[ky].append(nt)
aliases[ky].sort()
return aliases

# These dictionaries are used by number field parsing code when user requests a dihedral galois group
dihedral_gal = {
2: "2T1",
4: "4T2",
6: "6T2",
8: "8T4",
10: "10T2",
12: "12T3",
14: "14T2",
16: "16T13",
18: "18T5",
20: "20T4",
22: "22T2",
24: "24T13",
26: "26T2",
28: "28T4",
30: "30T3",
32: "32T31",
34: "34T2",
36: "36T10",
38: "38T2",
40: "40T12",
42: "42T5",
44: "44T4",
46: "46T2",
}

dihedral_ngal = {
3: "3T2",
4: "4T3",
5: "5T2",
6: "6T3",
7: "7T2",
8: "8T6",
9: "9T3",
10: "10T3",
11: "11T2",
12: "12T12",
13: "13T2",
14: "14T3",
15: "15T2",
16: "16T56",
17: "17T2",
18: "18T13",
19: "19T2",
20: "20T10",
21: "21T5",
22: "22T3",
23: "23T2",
24: "24T34",
25: "25T4",
26: "26T3",
27: "27T8",
28: "28T10",
29: "29T2",
30: "30T14",
31: "31T2",
32: "32T374",
33: "33T3",
34: "34T3",
35: "35T4",
36: "36T47",
37: "37T2",
38: "38T3",
39: "39T4",
40: "40T46",
41: "41T2",
42: "42T11",
43: "43T2",
44: "44T9",
45: "45T4",
46: "46T3",
47: "47T2",
}

multiquad = {
2: "2T1",
4: "4T2",
8: "8T3",
16: "16T4",
32: "32T39",
}
71 changes: 60 additions & 11 deletions lmfdb/number_fields/number_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
from lmfdb.utils import (
web_latex, to_dict, coeff_to_poly, comma, format_percentage,
flash_error, display_knowl, CountBox, Downloader, prop_int_pretty,
SearchArray, TextBox, YesNoBox, YesNoMaybeBox, SubsetNoExcludeBox,
SearchArray, TextBox, YesNoBox, YesNoMaybeBox, SubsetNoExcludeBox, SelectBox,
SubsetBox, TextBoxWithSelect, parse_bool_unknown, parse_posints,
clean_input, nf_string_to_label, parse_galgrp, parse_ints, parse_bool,
parse_signed_ints, parse_primes, parse_bracketed_posints, parse_nf_string,
parse_floats, parse_subfield, search_wrap, parse_padicfields,
parse_floats, parse_subfield, search_wrap, parse_padicfields, integer_options,
raw_typeset, raw_typeset_poly, flash_info, input_string_to_poly,
raw_typeset_int, compress_poly_Q, compress_polynomial)
from lmfdb.utils.web_display import compress_int
Expand All @@ -25,7 +25,8 @@
cclasses_display_knowl,character_table_display_knowl,
group_phrase, galois_group_data, transitive_group_display_knowl,
group_cclasses_knowl_guts, group_pretty_and_nTj, knowl_cache,
group_character_table_knowl_guts, group_alias_table)
group_character_table_knowl_guts, group_alias_table,
dihedral_gal, dihedral_ngal, multiquad)
from lmfdb.number_fields import nf_page, nf_logger
from lmfdb.number_fields.web_number_field import (
field_pretty, WebNumberField, nf_knowl_guts, factor_base_factor,
Expand Down Expand Up @@ -898,7 +899,44 @@ def number_field_search(info, query):
parse_posints(info,query,'relative_class_number')
parse_ints(info,query,'num_ram')
parse_bool(info,query,'cm_field',qfield='cm')
parse_bool(info,query,'is_galois')
fi = info.get("field_is")
if fi == "cyc":
query["gal_is_cyclic"] = True
elif fi == "ab":
query["gal_is_abelian"] = True
elif fi in ["dih_ngal", "dih_gal", "multi_quad"]:
if fi == "dih_ngal":
opts = dihedral_ngal
elif fi == "dih_gal":
opts = dihedral_gal
else:
opts = multiquad
if "degree" in info:
opts = {n: opts[n] for n in integer_options(info["degree"], contained_in=list(opts), lower_bound=1, upper_bound=47) if n in opts}
if "galois_label" in query:
# Added by parse_galgrp, so we intersect with opts
if isinstance(query["galois_label"], dict):
ggopt = set(query["galois_label"]["$in"])
else:
ggopt = {query["galois_label"]}
opts = {n: gg for (n, gg) in opts.items() if gg in ggopt}
if len(opts) == 0:
# Incompatible with specified degree or galois labels, so we add an impossible condition
query["degree"] = -1
elif len(opts) == 1:
n, gg = list(opts.items())[0]
query["degree"] = n
query["galois_label"] = gg
else:
query["degree"] = {"$in": list(opts)}
query["galois_label"] = {"$in": list(opts.values())}
elif fi == "gal":
query["is_galois"] = True
elif fi == "solv":
query["gal_is_solvable"] = True
elif fi == "nsolv":
query["gal_is_solvable"] = False

parse_bracketed_posints(info,query,'class_group',check_divisibility='increasing',process=int)
parse_primes(info,query,'ur_primes',name='Unramified primes',
qfield='ramps',mode='exclude')
Expand Down Expand Up @@ -1176,10 +1214,21 @@ def __init__(self):
knowl="nf.galois_search",
example="C5",
example_span="[8,3], 8.3, C5 or 7T2")
is_galois = YesNoBox(
name="is_galois",
label="Galois",
knowl="nf.galois_group")
field_is_opts = [
("", ""),
("cyc", "cyclic"),
("ab", "abelian"),
("multi_quad", "multi-quadratic"),
("dih_ngal", "dihedral non-Galois"),
("dih_gal", "dihedral Galois"),
("gal", "Galois"),
("solv", "solvable"),
("nsolv", "nonsolvable"),]
field_is = SelectBox(
name="field_is",
label="Field is",
knowl="nf.field_is",
options=field_is_opts)
regulator = TextBox(
name="regulator",
label="Regulator",
Expand Down Expand Up @@ -1259,7 +1308,7 @@ def __init__(self):
self.browse_array = [
[degree, signature],
[discriminant, rd],
[gal, is_galois],
[gal, field_is],
[num_ram, grd],
[class_number, class_group],
[ram_primes, ur_primes],
Expand All @@ -1271,13 +1320,13 @@ def __init__(self):

self.refine_array = [
[degree, signature, num_ram, ram_primes, ur_primes ],
[gal, is_galois, subfield, class_group, class_number],
[gal, field_is, subfield, class_group, class_number],
[discriminant, rd, grd, cm_field, relative_class_number],
[regulator, completion, monogenic, index, inessentialprimes],
[is_minimal_sibling]]

#[degree, signature, class_number, class_group, cm_field],
#[num_ram, ram_primes, ur_primes, gal, is_galois],
#[num_ram, ram_primes, ur_primes, gal, field_is],
#[discriminant, rd, grd, regulator, subfield],
#[completion, is_minimal_sibling, monogenic, index, inessentialprimes],
#[relative_class_number]]
20 changes: 14 additions & 6 deletions lmfdb/utils/search_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,20 +380,28 @@ def parse_range2rat(arg, key, process):

# We parse into a list of singletons and pairs, like [[-5,-2], 10, 11, [16,100]]
# If split0, we split ranges [-a,b] that cross 0 into [-a, -1], [1, b]
def parse_range3(arg, split0=False):
def parse_range3(arg, split0=False, lower_bound=None, upper_bound=None):
if isinstance(arg, str):
arg = arg.replace(" ", "")
if "," in arg:
return sum([parse_range3(a, split0) for a in arg.split(",")], [])
return sum([parse_range3(a, split0, lower_bound, upper_bound) for a in arg.split(",")], [])
elif "-" in arg[1:]:
ix = arg.index("-", 1)
start, end = arg[:ix], arg[ix + 1:]
if start:
low = ZZ(str(start))
if lower_bound is not None:
low = max(low, lower_bound)
elif lower_bound is not None:
low = lower_bound
else:
raise SearchParsingError("It needs to be an integer (such as 25), a range of integers (such as 2-10 or 2..10), or a comma-separated list of these (such as 4,9,16 or 4-25, 81-121).")
if end:
high = ZZ(str(end))
if upper_bound is not None:
high = min(high, upper_bound)
elif upper_bound is not None:
high = upper_bound
else:
raise SearchParsingError("It needs to be an integer (such as 25), a range of integers (such as 2-10 or 2..10), or a comma-separated list of these (such as 4,9,16 or 4-25, 81-121).")
if low == high:
Expand All @@ -413,15 +421,15 @@ def parse_range3(arg, split0=False):
else:
return [ZZ(str(arg))]

def integer_options(arg, max_opts=None, contained_in=None):
def integer_options(arg, max_opts=None, contained_in=None, lower_bound=None, upper_bound=None):
if not LIST_RE.match(arg) and MULT_PARSE.fullmatch(arg):
# Make input work using some arithmetic expressions
try:
ast_expression = ast.parse(arg.replace("^", "**"), mode="eval")
arg = str(int(PowMulNodeVisitor().visit(ast_expression).body))
except (TypeError, ValueError, SyntaxError):
raise SearchParsingError("Unable to evaluate expression.")
intervals = parse_range3(arg)
intervals = parse_range3(arg, lower_bound=lower_bound, upper_bound=upper_bound)
check = max_opts is not None and contained_in is None
if check and len(intervals) > max_opts:
raise ValueError("Too many options.")
Expand Down Expand Up @@ -698,12 +706,12 @@ def parse_not_element_of(inp, query, qfield, parse_singleton=int):
# Parses signed ints as an int and a sign the fields these are stored are passed in as qfield = (sign_field, abs_field)
# see SearchParser.__call__ for actual arguments when calling
@search_parser(clean_info=True, prep_ranges=True)
def parse_signed_ints(inp, query, qfield, parse_one=None):
def parse_signed_ints(inp, query, qfield, parse_one=None, lower_bound=None, upper_bound=None):
if parse_one is None:
def parse_one(x): return (int(x.sign()), int(x.abs())) if x != 0 else (1, 0)
sign_field, abs_field = qfield
if SIGNED_LIST_RE.match(inp):
parsed = parse_range3(inp, split0=True)
parsed = parse_range3(inp, split0=True, lower_bound=lower_bound, upper_bound=upper_bound)
# if there is only one part, we don't need an $or
if len(parsed) == 1:
parsed = parsed[0]
Expand Down

0 comments on commit 23c8d40

Please sign in to comment.