Skip to content
15 changes: 13 additions & 2 deletions lmfdb/abvar/fq/isog_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ def __init__(self, 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"] = []
self.__dict__.update(dbdata)

@classmethod
Expand Down Expand Up @@ -232,9 +237,15 @@ def properties(self):
("Primitive", "yes" if self.is_primitive else "no"),
]
if self.has_principal_polarization != 0:
props += [("Principally polarizable", "yes" if self.has_principal_polarization == 1 else "no")]
props += [(
"Principally polarizable",
"yes" if self.has_principal_polarization == 1 else "no",
)]
if self.has_jacobian != 0:
props += [("Contains a Jacobian", "yes" if self.has_jacobian == 1 else "no")]
props += [(
"Contains a Jacobian",
"yes" if self.has_jacobian == 1 else "no",
)]
return props

# at some point we were going to display the weil_numbers instead of the frobenius angles
Expand Down
40 changes: 38 additions & 2 deletions lmfdb/abvar/fq/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from lmfdb.utils import redirect_no_cache
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

logger = make_logger("abvarfq")

Expand Down Expand Up @@ -399,8 +400,31 @@ def nbsp(knowl, label):
jacobian = YesNoMaybeBox(
"jacobian",
label="Jacobian",
knowl="ag.jacobian"
knowl="ag.jacobian",
)

# Cyclic group of points (advanced yes/no box)
cyclic = YesNoBox(
"cyclic",
label="Cyclic group of points",
knowl="av.fq.cyclic_group_points",
advanced=True,
)

# Non-cyclic primes with mode selector (include / exactly / subset)
noncyclic_mode = SubsetBox(
"noncyclic_primes_mode",
advanced=True,
)
noncyclic_primes = TextBoxWithSelect(
"noncyclic_primes",
label="Non-cyclic primes",
select_box=noncyclic_mode,
knowl="av.fq.noncyclic_primes",
example="2 or 2,3,5",
advanced=True,
)

uglabel = "Use %s in the following inputs" % display_knowl("av.decomposition", "Geometric decomposition")
use_geom_decomp = CheckBox(
"use_geom_decomp",
Expand Down Expand Up @@ -506,6 +530,7 @@ def short_label(d):
[newton_polygon, abvar_point_count, curve_point_count, simple_factors],
[newton_elevation, jac_cnt, hyp_cnt, twist_count, max_twist_degree],
[angle_rank, angle_corank, geom_deg, p_corank, geom_squarefree],
[cyclic, noncyclic_primes],
use_geom_refine,
[dim1, dim2, dim3, dim4, dim5],
[dim1d, dim2d, dim3d, number_field, galois_group],
Expand All @@ -516,6 +541,7 @@ def short_label(d):
[g, geom_simple],
[initial_coefficients, polarizable],
[p_rank, jacobian],
[cyclic, noncyclic_primes],
[p_corank, geom_squarefree],
[jac_cnt, hyp_cnt],
[angle_rank, angle_corank],
Expand All @@ -536,6 +562,7 @@ def short_label(d):
[count],
]


def search_types(self, info):
return self._search_again(info, [
('List', 'List of isogeny classes'),
Expand All @@ -552,6 +579,7 @@ def common_parse(info, query):
parse_bool(info, query, "primitive", qfield="is_primitive")
parse_bool_unknown(info, query, "jacobian", qfield="has_jacobian")
parse_bool_unknown(info, query, "polarizable", qfield="has_principal_polarization")
parse_bool(info, query, "cyclic", qfield="is_cyclic")
parse_ints(info, query, "p_rank")
parse_ints(info, query, "p_corank", qfield="p_rank_deficit")
parse_ints(info, query, "angle_rank")
Expand All @@ -561,6 +589,14 @@ def common_parse(info, query):
parse_ints(info, query, "hyp_cnt", qfield="hyp_count", name="Number of Hyperelliptic Jacobians")
parse_ints(info, query, "twist_count")
parse_ints(info, query, "max_twist_degree")
parse_primes(
info,
query,
"noncyclic_primes",
qfield="noncyclic_primes",
mode=info.get("noncyclic_primes_mode"),
)

parse_ints(info, query, "size")
parse_newton_polygon(info, query, "newton_polygon", qfield="slopes")
parse_string_start(info, query, "initial_coefficients", qfield="poly_str", initial_segment=["1"])
Expand Down Expand Up @@ -680,7 +716,7 @@ def extended_code(c):
ProcessedCol("number_fields", "av.fq.number_field", "Number fields", lambda nfs: ", ".join(nf_display_knowl(nf, field_pretty(nf)) for nf in nfs), default=False),
SearchCol("galois_groups_pretty", "nf.galois_group", "Galois groups", download_col="galois_groups", default=False),
SearchCol("decomposition_display_search", "av.decomposition", "Isogeny factors", download_col="decompositionraw")],
db_cols=["label", "g", "q", "poly", "p_rank", "p_rank_deficit", "is_simple", "is_geometrically_simple", "simple_distinct", "simple_multiplicities", "is_primitive", "primitive_models", "curve_count", "curve_counts", "abvar_count", "abvar_counts", "jacobian_count", "hyp_count", "number_fields", "galois_groups", "slopes", "newton_elevation", "twist_count", "max_twist_degree", "geometric_extension_degree", "angle_rank", "angle_corank", "is_supersingular", "has_principal_polarization", "has_jacobian"])
db_cols=["label", "g", "q", "poly", "p_rank", "p_rank_deficit", "is_simple", "is_geometrically_simple", "simple_distinct", "simple_multiplicities", "is_primitive", "primitive_models", "curve_count", "curve_counts", "abvar_count", "abvar_counts", "jacobian_count", "hyp_count", "number_fields", "galois_groups", "slopes", "newton_elevation", "twist_count", "max_twist_degree", "geometric_extension_degree", "angle_rank", "angle_corank", "is_supersingular", "has_principal_polarization", "has_jacobian", "is_cyclic", "noncyclic_primes"])

def abvar_postprocess(res, info, query):
gals = set()
Expand Down
21 changes: 21 additions & 0 deletions lmfdb/abvar/fq/templates/show-abvarfq.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ <h2>Invariants</h2>
{% if cl.size is not none %}
<tr><td>{{KNOWL('av.fq.isogeny_class_size',title='Isomorphism classes')}}:</td><td>&nbsp;&nbsp;{{cl.size}}</td></tr>
{% endif %}
{# --- NEW: cyclicity invariants --- #}
{% if cl.is_cyclic is not none %}
<tr>
<td>{{ KNOWL('av.fq.cyclic_group_points', title='Cyclic group of points') }}:</td>
<td>&nbsp;&nbsp;
{% if cl.is_cyclic %}
yes
{% else %}
no
{% endif %}
</td>
</tr>
{% endif %}


{% if cl.noncyclic_primes %}
<tr>
<td>{{ KNOWL('av.fq.noncyclic_primes', title='Non-cyclic primes') }}:</td>
<td>&nbsp;&nbsp;${{ cl.noncyclic_primes|join(', ') }}$</td>
</tr>
{% endif %}
</table>
</p>

Expand Down
9 changes: 9 additions & 0 deletions lmfdb/abvar/fq/test_av.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,12 @@ def test_download_curves(self):

page = self.tc.get('Variety/Abelian/Fq/download_curves/5.3.ac_e_ai_v_abl', follow_redirects=True)
assert 'No curves for abelian variety isogeny class 5.3.ac_e_ai_v_abl' in page.get_data(as_text=True)

def test_cyclic_group_of_points_display(self):
r"""
Check that the cyclic group of points information is displayed
on the isogeny-class page.
"""
page = self.tc.get("/Variety/Abelian/Fq/2/9/aj_bl").get_data(as_text=True)
assert "Cyclic" in page

78 changes: 70 additions & 8 deletions lmfdb/abvar/fq/test_browse_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ def test_index_page(self):
"""
homepage = self.tc.get("/Variety/Abelian/Fq/").get_data(as_text=True)
assert "by dimension and base field" in homepage
assert "Cyclic group of points" in homepage

def test_stats_page(self):
self.check_args("/Variety/Abelian/Fq/stats","Abelian variety isogeny classes: Statistics")
self.check_args("/Variety/Abelian/Fq/stats", "Abelian variety isogeny classes: Statistics")

# TODO test dynamic stats

Expand Down Expand Up @@ -43,7 +44,10 @@ def test_lookup(self):
r"""
Check that Variety/Abelian/Fq/?jump works
"""
self.check_args("/Variety/Abelian/Fq/?jump=x^6-3*x^5%2B3*x^4-2*x^3%2B6*x^2-12*x%2B8", "3.2.ad_d_ac")
self.check_args(
"/Variety/Abelian/Fq/?jump=x^6-3*x^5%2B3*x^4-2*x^3%2B6*x^2-12*x%2B8",
"3.2.ad_d_ac"
)

# Various searches
# Many things are checked twice: Once from main index/browse page, and once from the refining search page
Expand Down Expand Up @@ -148,7 +152,10 @@ def test_search_newton(self):
# slope not a rational number
self.check_args("/Variety/Abelian/Fq/?newton_polygon=t", "is not a valid input")
# slopes are not increasing
self.check_args("/Variety/Abelian/Fq/?start=&count=&newton_polygon=%5B1%2C1%2F2%2C0%5D", "Slopes must be increasing")
self.check_args(
"/Variety/Abelian/Fq/?start=&count=&newton_polygon=%5B1%2C1%2F2%2C0%5D",
"Slopes must be increasing"
)

def test_search_initcoeffs(self):
r"""
Expand All @@ -157,7 +164,10 @@ def test_search_initcoeffs(self):
self.check_args("/Variety/Abelian/Fq/?initial_coefficients=%5B1%2C-1%2C3%2C9%5D", "4.3.b_ab_d_j")
self.check_args("/Variety/Abelian/Fq/?initial_coefficients=%5B1%2C-1%2C3%2C9%5D", "4.3.b_ab_d_j")
# there should be only one match, if ranges were supported
self.check_args("/Variety/Abelian/Fq/?angle_ranks=&initial_coefficients=%5B3%2C+9%2C+10%2C+87-100%5D", "Ranges not supported")
self.check_args(
"/Variety/Abelian/Fq/?angle_ranks=&initial_coefficients=%5B3%2C+9%2C+10%2C+87-100%5D",
"Ranges not supported"
)

def test_search_pointcountsav(self):
r"""
Expand Down Expand Up @@ -187,8 +197,14 @@ def test_search_isogfactor(self):
Check that we can search by decomposition into isogeny factors
"""
# [3.5.ah_y_ach,*]
self.check_args("/Variety/Abelian/Fq/?simple_quantifier=include&simple_factors=3.5.ah_y_ach", "4.5.ak_by_agk_qb")
self.check_args("/Variety/Abelian/Fq/?p_rank=4&dim1_factors=2&dim2_factors=2&dim1_distinct=1&dim2_distinct=1", "6.2.ag_p_aw_bh_acu_ey")
self.check_args(
"/Variety/Abelian/Fq/?simple_quantifier=include&simple_factors=3.5.ah_y_ach",
"4.5.ak_by_agk_qb"
)
self.check_args(
"/Variety/Abelian/Fq/?p_rank=4&dim1_factors=2&dim2_factors=2&dim1_distinct=1&dim2_distinct=1",
"6.2.ag_p_aw_bh_acu_ey"
)
self.check_args("/Variety/Abelian/Fq/?dim1_factors=6&dim1_distinct=1", "5 matches")

def test_search_numberfield(self):
Expand Down Expand Up @@ -274,8 +290,54 @@ def test_search_combos(self):
self.check_args("/Variety/Abelian/Fq/?p_rank=2&initial_coefficients=%5B1%2C-1%2C3%2C9%5D", "4.3.b_ab_d_j")
self.check_args("/Variety/Abelian/Fq/?p_rank=2&initial_coefficients=%5B1%2C-1%2C3%2C9%5D", "4.3.b_ab_d_j")
# initial coefficients and point counts of the abelian variety
self.check_args("/Variety/Abelian/Fq/?initial_coefficients=%5B1%2C-1%2C3%2C9%5D&abvar_point_count=%5B75%2C7125%5D", "No matches")
self.check_args("/Variety/Abelian/Fq/?initial_coefficients=%5B1%2C-1%2C3%2C9%5D&abvar_point_count=%5B75%2C7125%5D", "No matches")
self.check_args(
"/Variety/Abelian/Fq/?initial_coefficients=%5B1%2C-1%2C3%2C9%5D&abvar_point_count=%5B75%2C7125%5D",
"No matches"
)
self.check_args(
"/Variety/Abelian/Fq/?initial_coefficients=%5B1%2C-1%2C3%2C9%5D&abvar_point_count=%5B75%2C7125%5D",
"No matches"
)
# Combining unknown fields on Jacobian and Principal polarization.
self.check_args("/Variety/Abelian/Fq/?g=3&jacobian=no&polarizable=not_no", "3.2.a_a_ae")
self.check_args("/Variety/Abelian/Fq/?g=3&jacobian=no&polarizable=yes", "3.2.a_ac_a")

def test_search_cyclic_group(self):
r"""
Check that we can restrict to cyclic or non-cyclic groups of points
using the cyclic search parameter.
"""
# 1.2.ac has cyclic group of points (is_cyclic = True)
self.check_args(
"/Variety/Abelian/Fq/?cyclic=yes",
">1.2.ac<",
)

# 1.3.a has non-cyclic group of points (is_cyclic = False)
self.check_args(
"/Variety/Abelian/Fq/?cyclic=no",
">1.3.a<",
)

# And make sure they do not appear in the wrong list
self.not_check_args(
"/Variety/Abelian/Fq/?cyclic=yes",
">1.3.a<",
)
self.not_check_args(
"/Variety/Abelian/Fq/?cyclic=no",
">1.2.ac<",
)

def test_search_noncyclic_primes(self):
r"""
Check that the noncyclic_primes search parameter is accepted and
finds classes that are non-cyclic at p = 2.
"""
self.check_args(
"/Variety/Abelian/Fq/?noncyclic_primes=2",
">1.3.a<",
)



Loading