Repository: LMFDB/lmfdb

Issue:
Character orbit knowls on newforms are broken
After clicking for a random classical newform, we might find ourselves at a page like https://www.lmfdb.org/ModularForm/GL2/Q/holomorphic/5908/2/a/c/

This has character orbit `5908.a`. Clicking on that knowl shows a knowl stating "Invalid label for Dirichlet character: 5908.a". This is true, I suppose, as this should be a Dirichlet orbit (and `5908.a` is a valid orbit label). I suspect that we call the wrong knowl builder.

I think I should be able to examine this tomorrow.
Character orbit knowls on newforms are broken
After clicking for a random classical newform, we might find ourselves at a page like https://www.lmfdb.org/ModularForm/GL2/Q/holomorphic/5908/2/a/c/

This has character orbit `5908.a`. Clicking on that knowl shows a knowl stating "Invalid label for Dirichlet character: 5908.a". This is true, I suppose, as this should be a Dirichlet orbit (and `5908.a` is a valid orbit label). I suspect that we call the wrong knowl builder.

I think I should be able to examine this tomorrow.


Discussion:

Resolving patch:
diff --git a/lmfdb/characters/main.py b/lmfdb/characters/main.py
--- a/lmfdb/characters/main.py
+++ b/lmfdb/characters/main.py
@@ -533,33 +533,21 @@ def dirchar_data(label):
         return abort(404, f"Invalid label {label}")
 
 def _dir_knowl_data(label, orbit=False):
-    modulus, number = label.split('.')
-    modulus = int(modulus)
     try:
-        numbers = int(number)
-    except ValueError:
-        return "Invalid label for Dirichlet character: %s" % label
-    try:
-        if isinstance(numbers, list):
-            number = numbers[0]
-
-            def conrey_link(i):
-                return "<a href='%s'> %s.%s</a>" % (url_for("characters.render_Dirichletwebpage", modulus=modulus, number=i), modulus, i)
-            if len(numbers) <= 2:
-                numbers = [conrey_link(k) for k in numbers]
-            else:
-                numbers = [conrey_link(numbers[0]), '&#8230;', conrey_link(numbers[-1])]
+        parts = label.split('.')
+        modulus = int(parts[0])
+        if orbit:
+            assert(modulus <= ORBIT_MAX_MOD)
+            args = {'type': 'Dirichlet', 'modulus': modulus, 'orbit_label': parts[1]}
         else:
-            number = numbers
-            numbers = None
-
-        args = {'type': 'Dirichlet', 'modulus': modulus, 'number': number}
+            number = int(parts[1])
+            args = {'type': 'Dirichlet', 'modulus': modulus, 'number': number}
         webchar = make_webchar(args)
 
-        if orbit and modulus <= ORBIT_MAX_MOD:
+        if orbit:
             inf = "Dirichlet character orbit %s.%s\n" % (modulus, webchar.orbit_label)
         else:
-            inf = r"Dirichlet character \(\chi_{%s}(%s, \cdot)\)" % (modulus, number) + "\n"
+            inf = r"Dirichlet character \(\chi_{%s}(%s, \cdot)\)" % (modulus, parts[1]) + "\n"
         inf += "<div><table class='chardata'>\n"
 
         def row_wrap(header, val):
@@ -569,8 +557,6 @@ def row_wrap(header, val):
         inf += row_wrap('Degree', euler_phi(webchar.order))
         inf += row_wrap('Minimal', webchar.isminimal)
         inf += row_wrap('Parity', webchar.parity)
-        if numbers:
-            inf += row_wrap('Characters', ",&nbsp;".join(numbers))
         if modulus <= ORBIT_MAX_MOD:
             if not orbit:
                 inf += row_wrap('Orbit label', '%s.%s' % (modulus, webchar.orbit_label))
@@ -580,7 +566,7 @@ def row_wrap(header, val):
             inf += '<div align="right">\n'
             inf += '<a href="%s">%s.%s home page</a>\n' % (str(url_for("characters.render_Dirichletwebpage", modulus=modulus, orbit_label=webchar.orbit_label)), modulus, webchar.orbit_label)
             inf += '</div>\n'
-        elif numbers is None:
+        else:
             inf += '<div align="right">\n'
             inf += '<a href="%s">%s home page</a>\n' % (str(url_for("characters.render_Dirichletwebpage", modulus=modulus, number=number)), label)
             inf += '</div>\n'
diff --git a/lmfdb/characters/web_character.py b/lmfdb/characters/web_character.py
--- a/lmfdb/characters/web_character.py
+++ b/lmfdb/characters/web_character.py
@@ -1140,6 +1140,7 @@ def _populate_from_db(self):
         self.isminimal = bool_string(orbit_data['is_minimal'])
         self.parity = parity_string(int(orbit_data['parity']))
         self._set_kernel_field_poly(orbit_data)
+        self.orbit_index = orbit_data['orbit_index']
         self.inducing = orbit_data['primitive_label']
         self.ind_orbit_label = self.inducing.split(".")[1]
 
diff --git a/lmfdb/elliptic_curves/web_ec.py b/lmfdb/elliptic_curves/web_ec.py
--- a/lmfdb/elliptic_curves/web_ec.py
+++ b/lmfdb/elliptic_curves/web_ec.py
@@ -230,6 +230,32 @@ def short_latex_equation(ainvs):
 def latex_equations(ainvs):
     return [latex_equation(ainvs),homogeneous_latex_equation(ainvs),short_latex_equation(ainvs)]
 
+def sextic_twist_discriminant(ainvs):
+    r"""
+    Return D such that this is the sextic twist by D of 27.a4 (whose c6=-216) -- only for j=0
+    """
+    a1,a2,a3,a4,a6 = ainvs
+    D = -108 * (a1**6 + 12*a1**4*a2 + 48*a1**2*a2**2 + 64*a2**3 - 432*a3**2 - 1728*a6) # = -216*c6
+
+    # Remove 6th powers and invert: the minus sign in the exponent is
+    # because the text says that 27.a4 is this curve's sextic twist by
+    # D, not the other way round.
+
+    return D.sign() * prod(p ** ((-e)%6) for p,e in D.factor())
+
+def quartic_twist_discriminant(ainvs):
+    r"""
+    Return D such that this is the quartic twist by D of 32.a3 (whose c4=48) -- only for j=1728
+    """
+    a1,a2,a3,a4,a6 = ainvs
+    D = 27 * (a1**4 + 8*a1**2*a2 + 16*a2**2 - 24*a1*a3 - 48*a4)
+
+    # Remove 4th powers and invert: the minus sign in the exponent is
+    # because the text says that 32.a3 is this curve's quartic twist
+    # by D, not the other way round.
+
+    return D.sign() * prod(p ** ((-e)%4) for p,e in D.factor())
+
 class WebEC():
     """
     Class for an elliptic curve over Q
@@ -322,6 +348,17 @@ def make_curve(self):
         data['minq_label'] = db.ec_curvedata.lucky({'ainvs': self.min_quad_twist_ainvs},
                                                    projection='lmfdb_label' if self.label_type == 'LMFDB' else 'Clabel')
         data['minq_info'] = '(itself)' if minqD==1 else '(by {})'.format(minqD)
+        data['minq_url'] = url_for(".by_ec_label", label=data["minq_label"])
+
+        # higher minimal twists:
+        if self.cm == -3:
+            data['min_sextic_twist_disc'] = sextic_twist_discriminant(self.ainvs)
+            data['min_sextic_twist_label'] = '27.a4'
+            data['min_sextic_twist_url'] = url_for(".by_ec_label", label='27.a4')
+        if self.cm == -4:
+            data['min_quartic_twist_disc'] = quartic_twist_discriminant(self.ainvs)
+            data['min_quartic_twist_label'] = '32.a3'
+            data['min_quartic_twist_url'] = url_for(".by_ec_label", label='32.a3')
 
         # modular degree:
 
@@ -489,8 +526,12 @@ def make_curve(self):
 
         self.friends = [
             ('Isogeny class ' + self.class_name, self.class_url),
-            ('Minimal quadratic twist %s %s' % (data['minq_info'], data['minq_label']), url_for(".by_ec_label", label=data['minq_label'])),
-            ('All twists ', url_for(".rational_elliptic_curves", jinv=data['j_invariant']))]
+            (f'Minimal quadratic twist {data["minq_label"]}', data['minq_url'])]
+        if self.cm == -3:
+            self.friends.append((f'Minimal sextic twist {data["min_sextic_twist_label"]}', data['min_sextic_twist_url']))
+        if self.cm == -4:
+            self.friends.append((f'Minimal quartic twist {data["min_quartic_twist_label"]}', data['min_quartic_twist_url']))
+        self.friends.append(('All twists ', url_for(".rational_elliptic_curves", jinv=data['j_invariant'])))
 
         lfun_url = url_for("l_functions.l_function_ec_page", conductor_label=N, isogeny_class_label=iso)
         origin_url = lfun_url.lstrip('/L/').rstrip('/')


Test patch: