Repository: LMFDB/lmfdb

Issue:
q-expansion download to Sage code bugs
The code to download a CMF q-expansion to Sage has a couple of issues,

- The first is that a_{p^k}'s are not set properly for bad p https://github.com/LMFDB/lmfdb/blob/1ebf496611f83255e4b593975bd68b6d404b7ddd/lmfdb/classical_modular_forms/download.py#L128 (this can be seen on http://www.lmfdb.org/ModularForm/GL2/Q/holomorphic/14/2/a/a/, download the q-expansion code for sage and the q^4 term disappears).
Maybe how does this look as a replacement:
```sage
            '    k = RR(an_list_bound).log(p).floor() + 1',
            '    if p.divides(level):',
            '        euler_factor = [1, -ap, p**(weight - 1) * char_values[p]]',
            '    else:',
            '        euler_factor = [1, -ap]',
            '    foo = (1/PS(euler_factor)).padded_list(k)',
            '    for i in range(1, k):',
            '        an[p**i] = foo[i]',
```

- The second is that https://github.com/LMFDB/lmfdb/blob/1ebf496611f83255e4b593975bd68b6d404b7ddd/lmfdb/classical_modular_forms/download.py#L169 sets all character values to be 1 in dimension 1 but the character could be order 2 as in http://www.lmfdb.org/ModularForm/GL2/Q/holomorphic/23/1/b/a/ (the q^25 term differs in the downloaded q-exp) maybe it just needs to be changed to an order 1 special case?


Discussion:

Resolving patch:
diff --git a/lmfdb/classical_modular_forms/download.py b/lmfdb/classical_modular_forms/download.py
--- a/lmfdb/classical_modular_forms/download.py
+++ b/lmfdb/classical_modular_forms/download.py
@@ -74,10 +74,6 @@ def _get_traces(self, label):
             '            if gcd(k, pp) == 1:',
             '                an[pp*k] = an[pp]*an[k]']
 
-    field_and_convert_sage_dim1 = [
-            'K = QQ',
-            'convert_elt_to_field = lambda elt: K(elt)']
-
     field_and_convert_sage_powbasis = [
             'from sage.all import PolynomialRing, NumberField',
             'R = PolynomialRing(QQ, "x")',
@@ -126,19 +122,18 @@ def _get_traces(self, label):
             'PS = PowerSeriesRing(K, "q")',
             'for p, ap in zip(primes, aps):',
             '    if p.divides(level):',
-            '        an[p] = ap',
+            '        euler_factor = [1, -ap]',
             '    else:',
-            '        k = RR(an_list_bound).log(p).floor() + 1',
             '        euler_factor = [1, -ap, p**(weight - 1) * char_values[p]]',
-            '        foo = (1/PS(euler_factor)).padded_list(k)',
-            '        for i in range(1, k):',
-            '            an[p**i] = foo[i]',
+            '    k = RR(an_list_bound).log(p).floor() + 1',
+            '    foo = (1/PS(euler_factor)).padded_list(k)',
+            '    for i in range(1, k):',
+            '        an[p**i] = foo[i]',
             'extend_multiplicatively(an)',
             'return PS(an)']
 
     header = ["from sage.all import prod, floor, prime_powers, gcd, QQ, primes_first_n, next_prime, RR\n"]
-    qexp_dim1_function_body = {'sage': header + extend_multiplicatively_sage + field_and_convert_sage_dim1 + convert_aps + ['char_values = dict(zip(good_primes, [1]*len(good_primes)))'] + an_code_sage }
-    qexp_function_body_generic = {'sage': header +  discrete_log_sage + extend_multiplicatively_sage +  field_and_convert_sage_generic + convert_aps + char_values_sage_generic + an_code_sage}
+    qexp_function_body_generic = {'sage': header + discrete_log_sage + extend_multiplicatively_sage +  field_and_convert_sage_generic + convert_aps + char_values_sage_generic + an_code_sage}
     qexp_function_body_powbasis = {'sage': header +  discrete_log_sage + extend_multiplicatively_sage +  field_and_convert_sage_powbasis + convert_aps + char_values_sage_generic + an_code_sage}
     qexp_function_body_sparse_cyclotomic = {'sage': header +  discrete_log_sage + extend_multiplicatively_sage +  field_and_convert_sage_sparse_cyclotomic + convert_aps + char_values_sage_generic + an_code_sage}
 
@@ -151,7 +146,6 @@ def download_qexp(self, label, lang='sage'):
         if hecke_nf is None:
             return abort(404, "No q-expansion found for %s" % label)
 
-        dim = hecke_nf['hecke_ring_rank']
         aps = hecke_nf['ap']
         level, weight = map(int, label.split('.')[:2])
         level_data = self.assign(lang, 'level', level);
@@ -165,39 +159,34 @@ def download_qexp(self, label, lang='sage'):
         explain += c + ' We generate the q-expansion using the Hecke eigenvalues a_p at the primes.\n'
         aps_data = self.assign(lang, 'aps_data', aps);
         code = ''
-        if dim == 1:
-            func_body = self.get('qexp_dim1_function_body',{}).get(lang,[])
-            basis_data = poly_data = hecke_ring_character_values = ''
+        hecke_ring_character_values = self.assign(lang, 'hecke_ring_character_values', hecke_nf['hecke_ring_character_values']);
+
+        if hecke_nf['hecke_ring_cyclotomic_generator'] > 0:
+            func_body =  self.get('qexp_function_body_sparse_cyclotomic',{}).get(lang,[])
+            explain += c + ' Each a_p is given as list of pairs\n'
+            explain += c + ' Each pair (c, e) corresponds to c*zeta^e\n'
+            basis_data = ''
+            poly_data =  self.assign(lang, 'poly_data', hecke_nf['hecke_ring_cyclotomic_generator'])
         else:
-            hecke_ring_character_values = self.assign(lang, 'hecke_ring_character_values', hecke_nf['hecke_ring_character_values']);
-
-            if hecke_nf['hecke_ring_cyclotomic_generator'] > 0:
-                func_body =  self.get('qexp_function_body_sparse_cyclotomic',{}).get(lang,[])
-                explain += c + ' Each a_p is given as list of pairs\n'
-                explain += c + ' Each pair (c, e) corresponds to c*zeta^e\n'
-                basis_data = ''
-                poly_data =  self.assign(lang, 'poly_data', hecke_nf['hecke_ring_cyclotomic_generator'])
+            explain += c + ' Each a_p is given as a linear combination\n'
+            explain += c + ' of the following basis for the coefficient ring.\n'
+            poly_data = '\n' + c + ' The following line gives the coefficients of\n'
+            poly_data += c + ' the defining polynomial for the coefficient field.\n'
+            poly_data =  self.assign(lang, 'poly_data', hecke_nf['field_poly'], level = 1)
+            if hecke_nf['hecke_ring_power_basis']:
+                basis_data = '\n' + c + ' The basis for the coefficient ring is just the power basis\n'
+                basis_data += c + ' in the root of the defining polynomial above.\n'
+                func_body = self.get('qexp_function_body_powbasis',{}).get(lang,[])
             else:
-                explain += c + ' Each a_p is given as a linear combination\n'
-                explain += c + ' of the following basis for the coefficient ring.\n'
-                poly_data = '\n' + c + ' The following line gives the coefficients of\n'
-                poly_data += c + ' the defining polynomial for the coefficient field.\n'
-                poly_data =  self.assign(lang, 'poly_data', hecke_nf['field_poly'], level = 1)
-                if hecke_nf['hecke_ring_power_basis']:
-                    basis_data = '\n' + c + ' The basis for the coefficient ring is just the power basis\n'
-                    basis_data += c + ' in the root of the defining polynomial above.\n'
-                    basis_data = ''
-                    func_body = self.get('qexp_function_body_powbasis',{}).get(lang,[])
-                else:
-                    basis_data = '\n' + c + ' The entries in the following list give a basis for the\n'
-                    basis_data += c + ' coefficient ring in terms of a root of the defining polynomial above.\n'
-                    basis_data += c + ' Each line consists of the coefficients of the numerator, and a denominator.\n'
-                    basis_data += self.assign(lang,  'basis_data ', zip(hecke_nf['hecke_ring_numerators'], hecke_nf['hecke_ring_denominators']))
-                    basis_data += '\n'
-                    func_body = self.get('qexp_function_body_generic',{}).get(lang,[])
-
-            if lang in ['sage']:
-                explain += c + ' To create the q-expansion as a power series, type "qexp%smake_data()%s"\n' % (self.assignment_defn[lang], self.line_end[lang])
+                basis_data = '\n' + c + ' The entries in the following list give a basis for the\n'
+                basis_data += c + ' coefficient ring in terms of a root of the defining polynomial above.\n'
+                basis_data += c + ' Each line consists of the coefficients of the numerator, and a denominator.\n'
+                basis_data += self.assign(lang,  'basis_data ', zip(hecke_nf['hecke_ring_numerators'], hecke_nf['hecke_ring_denominators']))
+                basis_data += '\n'
+                func_body = self.get('qexp_function_body_generic',{}).get(lang,[])
+
+        if lang in ['sage']:
+            explain += c + ' To create the q-expansion as a power series, type "qexp%smake_data()%s"\n' % (self.assignment_defn[lang], self.line_end[lang])
 
 
         if lang in ['sage']:


Test patch:
diff --git a/lmfdb/classical_modular_forms/test_cmf.py b/lmfdb/classical_modular_forms/test_cmf.py
--- a/lmfdb/classical_modular_forms/test_cmf.py
+++ b/lmfdb/classical_modular_forms/test_cmf.py
@@ -534,7 +534,7 @@ def test_satake(self):
     def test_download_qexp(self):
         for label, exp in [
                 ['11.7.b.a'
-                    , '[0, 10, -64]'],
+                    , '[0, 10, 64]'],
                 ['11.2.a.a'
                     , '[-2, -1, 2]'],
                 ['21.2.g.a'
@@ -544,11 +544,11 @@ def test_download_qexp(self):
                 ['13.2.e.a'
                     , '[-a - 1, 2*a - 2, a]'],
                 ['340.1.ba.b'
-                    , '[z, 0, 0]'],
+                    , '[z, 0, z^2]'],
                 ['24.3.h.a'
-                    , '[-2, 3, 0]'],
+                    , '[-2, 3, 4]'],
                 ['24.3.h.c'
-                    , '[a, -a^2 - 3, 0]'],
+                    , '[a, -a^2 - 3, a^2]'],
                 ]:
             sage_code = self.tc.get('/ModularForm/GL2/Q/holomorphic/download_qexp/%s' % label, follow_redirects=True).data
             assert "make_data" in sage_code