Skip to content
Open
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
109 changes: 19 additions & 90 deletions lmfdb/ecnf/isog_class.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import url_for
from lmfdb import db
from lmfdb.utils import encode_plot, names_and_urls, web_latex
from lmfdb.utils import make_graph, setup_isogeny_graph, names_and_urls, web_latex
from lmfdb.logger import make_logger
from lmfdb.ecnf.WebEllipticCurve import web_ainvs, FIELD
from lmfdb.number_fields.web_number_field import field_pretty, nf_display_knowl
Expand Down Expand Up @@ -85,9 +85,19 @@ def make_class(self):

# Create isogeny graph:
self.graph = make_graph(self.isogeny_matrix)
P = self.graph.plot(edge_labels=True)
self.graph_img = encode_plot(P, transparent=True)
self.graph_link = '<img src="%s" width="200" height="150"/>' % self.graph_img
self.graph_data, self.graph_link, self.graph_layouts, self.graph_default_layout = setup_isogeny_graph(self.graph)
# Attach curve URLs and labels to nodes
for el in self.graph_data:
if el['group'] == 'nodes':
idx = int(el['data']['id']) - 1 # 1-indexed labels
if 0 <= idx < len(self.db_curves):
c = self.db_curves[idx]
el['data']['url'] = curve_url(c)
el['data']['label'] = c['iso_label'] + str(c['number'])
ts = c.get('torsion_structure', [])
el['data']['torsion'] = ' x '.join('Z/%dZ' % t for t in ts) if ts else 'Trivial'
if c.get('cm'):
el['data']['cm'] = str(c['cm'])
self.isogeny_matrix_str = latex(Matrix(self.isogeny_matrix))

self.field = FIELD(self.field_label)
Expand Down Expand Up @@ -152,10 +162,11 @@ def make_class(self):
self.friends += [('L-function not available', "")]

self.properties = [('Base field', self.field_name),
('Label', self.class_label)]
if self.class_size > 1:
self.properties.append((None, self.graph_link))
self.properties.append(('Conductor', '%s' % self.conductor_label))
('Label', self.class_label),
('Number of curves', str(self.class_size)),
('Graph', ''),
(None, self.graph_link),
('Conductor', '%s' % self.conductor_label)]

if self.rk != '?':
self.properties += [('Rank', '%s' % self.rk)]
Expand All @@ -171,88 +182,6 @@ def make_class(self):
('isogeny class %s' % self.short_label, self.urls['class'])]


def make_graph(M):
"""
Code extracted from Sage's elliptic curve isogeny class (reshaped
in the case maxdegree==12)
"""
from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix
from sage.graphs.graph import Graph
n = M.nrows() # = M.ncols()
G = Graph(unfill_isogeny_matrix(M), format='weighted_adjacency_matrix')
MM = fill_isogeny_matrix(M)
# The maximum degree classifies the shape of the isogeny
# graph, though the number of vertices is often enough.
# This only holds over Q, so this code will need to change
# once other isogeny classes are implemented.
if n == 1:
# one vertex
pass
elif n == 2:
# one edge, two vertices. We align horizontally and put
# the lower number on the left vertex.
G.set_pos(pos={0: [-0.5, 0], 1: [0.5, 0]})
else:
maxdegree = max(max(MM))
if n == 3:
# o--o--o
centervert = [i for i in range(3) if max(MM.row(i)) < maxdegree][0]
other = [i for i in range(3) if i != centervert]
G.set_pos(pos={centervert: [0, 0], other[0]: [-1, 0], other[1]: [1, 0]})
elif maxdegree == 4:
# o--o<8
centervert = [i for i in range(4) if max(MM.row(i)) < maxdegree][0]
other = [i for i in range(4) if i != centervert]
G.set_pos(pos={centervert: [0, 0], other[0]: [0, 1], other[1]: [-0.8660254, -0.5], other[2]: [0.8660254, -0.5]})
elif maxdegree == 27 and n == 4:
# o--o--o--o
centers = [i for i in range(4) if list(MM.row(i)).count(3) == 2]
left = [j for j in range(4) if MM[centers[0], j] == 3 and j not in centers][0]
right = [j for j in range(4) if MM[centers[1], j] == 3 and j not in centers][0]
G.set_pos(pos={left: [-1.5, 0], centers[0]: [-0.5, 0], centers[1]: [0.5, 0], right: [1.5, 0]})
elif n == 4:
# square
opp = [i for i in range(1, 4) if not MM[0, i].is_prime()][0]
other = [i for i in range(1, 4) if i != opp]
G.set_pos(pos={0: [1, 1], other[0]: [-1, 1], opp: [-1, -1], other[1]: [1, -1]})
elif maxdegree == 8:
# 8>o--o<8
centers = [i for i in range(6) if list(MM.row(i)).count(2) == 3]
left = [j for j in range(6) if MM[centers[0], j] == 2 and j not in centers]
right = [j for j in range(6) if MM[centers[1], j] == 2 and j not in centers]
G.set_pos(pos={centers[0]: [-0.5, 0], left[0]: [-1, 0.8660254], left[1]: [-1, -0.8660254], centers[1]: [0.5, 0], right[0]: [1, 0.8660254], right[1]: [1, -0.8660254]})
elif maxdegree == 18 and n == 6:
# two squares joined on an edge
centers = [i for i in range(6) if list(MM.row(i)).count(3) == 2]
top = [j for j in range(6) if MM[centers[0], j] == 3]
bl = [j for j in range(6) if MM[top[0], j] == 2][0]
br = [j for j in range(6) if MM[top[1], j] == 2][0]
G.set_pos(pos={centers[0]: [0, 0.5], centers[1]: [0, -0.5], top[0]: [-1, 0.5], top[1]: [1, 0.5], bl: [-1, -0.5], br: [1, -0.5]})
elif maxdegree == 16 and n == 8:
# tree from bottom, 3 regular except for the leaves.
centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
center = [i for i in centers if len([j for j in centers if MM[i, j] == 2]) == 2][0]
centers.remove(center)
bottom = [j for j in range(8) if MM[center, j] == 2 and j not in centers][0]
left = [j for j in range(8) if MM[centers[0], j] == 2 and j != center]
right = [j for j in range(8) if MM[centers[1], j] == 2 and j != center]
G.set_pos(pos={center: [0, 0], bottom: [0, -1], centers[0]: [-0.8660254, 0.5], centers[1]: [0.8660254, 0.5], left[0]: [-0.8660254, 1.5], right[0]: [0.8660254, 1.5], left[1]: [-1.7320508, 0], right[1]: [1.7320508, 0]})
elif maxdegree == 12:
# tent
centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
left = [j for j in range(8) if MM[centers[0], j] == 2]
right = []
for i in range(3):
right.append([j for j in range(8) if MM[centers[1], j] == 2 and MM[left[i], j] == 3][0])
G.set_pos(pos={centers[0]: [-0.3, 0], centers[1]: [0.3, 0],
left[0]: [-0.14, 0.15], right[0]: [0.14, 0.15],
left[1]: [-0.14, -0.15], right[1]: [0.14, -0.15],
left[2]: [-0.14, -0.3], right[2]: [0.14, -0.3]})

G.relabel(list(range(1, n + 1)))
return G


def make_iso_matrix(clist): # clist is a list of ECNFs
Elist = [E.E for E in clist]
cl = Elist[0].isogeny_class()
Expand Down
10 changes: 7 additions & 3 deletions lmfdb/ecnf/templates/ecnf-isoclass.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@ <h2>{{ KNOWL('ec.isogeny_matrix',title='Isogeny matrix') }}</h2>

<h2>{{ KNOWL('ec.isogeny_graph',title='Isogeny graph') }}</h2>
{% if cl.isogeny_matrix %}
<center>
<img src="{{cl.graph_img}}" />
</center>
<div id="isogeny-graph"></div>

{% set graph_layouts = cl.graph_layouts %}
{% include "cytoscape_scripts.html" %}
<script>
initIsogenyGraph('isogeny-graph', {{ cl.graph_data | tojson }}, {{ cl.graph_layouts | tojson }}, {{ cl.graph_default_layout | tojson }});
</script>
{% else %}
<p>Not available.</p>
{% endif %}
Expand Down
105 changes: 16 additions & 89 deletions lmfdb/elliptic_curves/isog_class.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask import url_for
from lmfdb.utils import encode_plot, prop_int_pretty, raw_typeset, integer_squarefree_part, list_to_factored_poly_otherorder
from lmfdb.utils import make_graph, setup_isogeny_graph, prop_int_pretty, raw_typeset, integer_squarefree_part, list_to_factored_poly_otherorder
from lmfdb.elliptic_curves import ec_logger
from lmfdb.elliptic_curves.web_ec import split_lmfdb_label, split_cremona_label, OPTIMALITY_BOUND, CREMONA_BOUND
from lmfdb.number_fields.web_number_field import field_pretty
Expand Down Expand Up @@ -127,9 +127,20 @@ def perm(i): return next(c for c in self.curves if c['Cnumber'] == i+1)['lmfdb_n
# Create isogeny graph with appropriate vertex labels:

self.graph = make_graph(M, [c['short_label'] for c in self.curves])
P = self.graph.plot(edge_labels=True, vertex_size=1000)
self.graph_img = encode_plot(P, transparent=True)
self.graph_link = '<img src="%s" width="200" height="150"/>' % self.graph_img
self.graph_data, self.graph_link, self.graph_layouts, self.graph_default_layout = setup_isogeny_graph(self.graph)
# Attach curve metadata to nodes for tooltip display
curve_by_label = {c['short_label']: c for c in self.curves}
for el in self.graph_data:
if el['group'] == 'nodes':
label = el['data']['label']
c = curve_by_label.get(label)
if c:
el['data']['url'] = c['curve_url_lmfdb']
el['data']['torsion'] = ' x '.join('Z/%dZ' % t for t in c['torsion_structure']) if c['torsion_structure'] else 'Trivial'
el['data']['degree'] = c['degree']
el['data']['faltings_height'] = str(c['FH'])
el['data']['optimal'] = bool(c.get('optimal'))
el['data']['j_inv'] = str(c['j_inv'])

self.newform = raw_typeset(PowerSeriesRing(QQ, 'q')(classdata['anlist'], 20, check=True))
self.newform_label = ".".join([str(self.conductor), str(2), 'a', self.iso_label])
Expand Down Expand Up @@ -170,8 +181,7 @@ def perm(i): return next(c for c in self.curves if c['Cnumber'] == i+1)['lmfdb_n
('CM', '%s' % self.CMfield),
('Rank', prop_int_pretty(self.rank))
]
if ncurves > 1:
self.properties += [('Graph', ''),(None, self.graph_link)]
self.properties += [('Graph', ''), (None, self.graph_link)]

self.downloads = [('q-expansion to text', url_for(".download_EC_qexp", label=self.lmfdb_iso, limit=1000)),
('All stored data to text', url_for(".download_EC_all", label=self.lmfdb_iso)),
Expand Down Expand Up @@ -207,86 +217,3 @@ def perm(i): return next(c for c in self.curves if c['Cnumber'] == i+1)['lmfdb_n
else:
self.has_lfunction = False

def make_graph(M, vertex_labels=None):
"""
Code extracted from Sage's elliptic curve isogeny class (reshaped
in the case maxdegree==12)
"""
from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix
from sage.graphs.graph import Graph
n = M.nrows() # = M.ncols()
G = Graph(unfill_isogeny_matrix(M), format='weighted_adjacency_matrix')
MM = fill_isogeny_matrix(M)
# The maximum degree classifies the shape of the isogeny
# graph, though the number of vertices is often enough.
# This only holds over Q, so this code will need to change
# once other isogeny classes are implemented.
if n == 1:
# one vertex
pass
elif n == 2:
# one edge, two vertices. We align horizontally and put
# the lower number on the left vertex.
G.set_pos(pos={0:[-0.5,0],1:[0.5,0]})
else:
maxdegree = max(max(MM))
if n == 3:
# o--o--o
centervert = [i for i in range(3) if max(MM.row(i)) < maxdegree][0]
other = [i for i in range(3) if i != centervert]
G.set_pos(pos={centervert:[0,0],other[0]:[-1,0],other[1]:[1,0]})
elif maxdegree == 4:
# o--o<8
centervert = [i for i in range(4) if max(MM.row(i)) < maxdegree][0]
other = [i for i in range(4) if i != centervert]
G.set_pos(pos={centervert:[0,0],other[0]:[0,1],other[1]:[-0.8660254,-0.5],other[2]:[0.8660254,-0.5]})
elif maxdegree == 27:
# o--o--o--o
centers = [i for i in range(4) if list(MM.row(i)).count(3) == 2]
left = [j for j in range(4) if MM[centers[0],j] == 3 and j not in centers][0]
right = [j for j in range(4) if MM[centers[1],j] == 3 and j not in centers][0]
G.set_pos(pos={left:[-1.5,0],centers[0]:[-0.5,0],centers[1]:[0.5,0],right:[1.5,0]})
elif n == 4:
# square
opp = [i for i in range(1,4) if not MM[0,i].is_prime()][0]
other = [i for i in range(1,4) if i != opp]
G.set_pos(pos={0:[1,1],other[0]:[-1,1],opp:[-1,-1],other[1]:[1,-1]})
elif maxdegree == 8:
# 8>o--o<8
centers = [i for i in range(6) if list(MM.row(i)).count(2) == 3]
left = [j for j in range(6) if MM[centers[0],j] == 2 and j not in centers]
right = [j for j in range(6) if MM[centers[1],j] == 2 and j not in centers]
G.set_pos(pos={centers[0]:[-0.5,0],left[0]:[-1,0.8660254],left[1]:[-1,-0.8660254],centers[1]:[0.5,0],right[0]:[1,0.8660254],right[1]:[1,-0.8660254]})
elif maxdegree == 18:
# two squares joined on an edge
centers = [i for i in range(6) if list(MM.row(i)).count(3) == 2]
top = [j for j in range(6) if MM[centers[0],j] == 3]
bl = [j for j in range(6) if MM[top[0],j] == 2][0]
br = [j for j in range(6) if MM[top[1],j] == 2][0]
G.set_pos(pos={centers[0]:[0,0.5],centers[1]:[0,-0.5],top[0]:[-1,0.5],top[1]:[1,0.5],bl:[-1,-0.5],br:[1,-0.5]})
elif maxdegree == 16:
# tree from bottom, 3 regular except for the leaves.
centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
center = [i for i in centers if len([j for j in centers if MM[i,j] == 2]) == 2][0]
centers.remove(center)
bottom = [j for j in range(8) if MM[center,j] == 2 and j not in centers][0]
left = [j for j in range(8) if MM[centers[0],j] == 2 and j != center]
right = [j for j in range(8) if MM[centers[1],j] == 2 and j != center]
G.set_pos(pos={center:[0,0],bottom:[0,-1],centers[0]:[-0.8660254,0.5],centers[1]:[0.8660254,0.5],left[0]:[-0.8660254,1.5],right[0]:[0.8660254,1.5],left[1]:[-1.7320508,0],right[1]:[1.7320508,0]})
elif maxdegree == 12:
# tent
centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
left = [j for j in range(8) if MM[centers[0],j] == 2]
right = []
for i in range(3):
right.append([j for j in range(8) if MM[centers[1],j] == 2 and MM[left[i],j] == 3][0])
G.set_pos(pos={centers[0]:[-0.3,0],centers[1]:[0.3,0],
left[0]:[-0.14,0.15], right[0]:[0.14,0.15],
left[1]:[-0.14,-0.15],right[1]:[0.14,-0.15],
left[2]:[-0.14,-0.3],right[2]:[0.14,-0.3]})

if vertex_labels:
G.relabel(vertex_labels)
else:
G.relabel(list(range(1, n + 1)))
return G
16 changes: 11 additions & 5 deletions lmfdb/elliptic_curves/templates/ec-isoclass.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ <h2>
});
</script>

{% if info.class_size>1 %}
{% if info.class_size > 1 %}

<h2>{{ KNOWL('ec.isogeny_matrix',title='Isogeny matrix') }}</h2>

Expand All @@ -118,14 +118,20 @@ <h2>{{ KNOWL('ec.isogeny_matrix',title='Isogeny matrix') }}</h2>
\({{info.isogeny_matrix_str}}\)
</p>

{% endif %}

<h2> {{ KNOWL('ec.isogeny_graph', title='Isogeny graph') }} </h2>
{{ place_code('plot') }}
{% if info.class_size > 1 %}
<p>The vertices are labelled with {{info.label_type}} labels. </p>
<center>
<img src="{{info.graph_img}}" />
</center>

{% endif %}
<div id="isogeny-graph"></div>

{% set graph_layouts = info.graph_layouts %}
{% include "cytoscape_scripts.html" %}
<script>
initIsogenyGraph('isogeny-graph', {{ info.graph_data | tojson }}, {{ info.graph_layouts | tojson }}, {{ info.graph_default_layout | tojson }});
</script>

<h2>Elliptic curves in class {{info.class_label}}</h2>
{{ place_code('curves') }}
Expand Down
Loading
Loading