-
Notifications
You must be signed in to change notification settings - Fork 241
Expand file tree
/
Copy pathtest_numberfield.py
More file actions
249 lines (211 loc) · 13.4 KB
/
Copy pathtest_numberfield.py
File metadata and controls
249 lines (211 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
from lmfdb.tests import LmfdbTest
class NumberFieldTest(LmfdbTest):
# All tests should pass
def test_Q(self):
self.check_args('/NumberField/Q', r'\chi_{1}')
self.check_args('/NumberField/1.1.1.1', r'\chi_{1}')
def test_hard_degree10(self):
self.check_args('/NumberField/10.10.1107649855354064.1', '10T36')
self.check_args('/NumberField/10.10.138420300533025695415730492558689.1', '10T38')
def test_hard_degree16(self):
self.check_args('/NumberField/16.0.13307764731675384304522756096.1', '16T1535')
def test_search_ramif_cl_deg(self):
self.check_args('/NumberField/?degree=5&class_group=[2%2C2]&ur_primes=7&discriminant=&ram_quantifier=exactly&ram_primes=2%2C3%2C5', '5.1.27000000000.8')
def test_abelian_conductor(self):
self.check_args('/NumberField/5.5.5719140625.2', '275') # conductor
def test_stuff_not_computed(self):
self.check_args('/NumberField/23.23.931347256889446325436632107655346061164193665348344821578377438399536607931200329.1', 'ot computed')
def test_search_poly_mean2parser(self):
# X^3-4x+2
self.check_args('/NumberField/?jump=X**3-4x%2B2&search=Go', '3.3.148.1') # label
# z^3 - 4*z + 2
self.check_args('/NumberField/?jump=z%5E3+-+4*z%2B2', '3.3.148.1') # label
def test_search_zeta(self):
self.check_args('/NumberField/?jump=Qzeta23&search=Go', '[3]') # class group
self.check_args('/NumberField/?jump=Qzeta_23&search=Go', '[3]') # class group
self.check_args('/NumberField/?jump=qzeta23%2B&search=Go', '1014.3133') # regulator
self.check_args('/NumberField/?jump=qzeta_23%2B&search=Go', '1014.3133') # regulator
def test_search_sqrt(self):
self.check_args('/NumberField/?jump=Qsqrt-163&search=Go', '41') # minpoly
self.check_args('/NumberField/?jump=q(sqrt-163)&search=Go', '41') # minpoly
def test_search_multiple_fields(self):
# Test comma-separated list of field labels
self.check_args('/NumberField/?jump=2.2.5.1%2c+3.3.49.1&search=Go', '2.2.5.1')
self.check_args('/NumberField/?jump=2.2.5.1%2c+3.3.49.1&search=Go', '3.3.49.1')
# Test comma-separated list with different input formats
self.check_args('/NumberField/?jump=Qsqrt5%2c+x%5E2-3&search=Go', '2.2.5.1')
self.check_args('/NumberField/?jump=Qsqrt5%2c+x%5E2-3&search=Go', '2.2.12.1')
def test_search_abelian_jump(self):
# Abelian fields entered by a non-reduced defining polynomial are
# identified without running polredabs (issue #5471).
from sage.all import pari
from urllib.parse import quote
# the degree 47 field of conductor 283 from the issue, entered via
# the minimal polynomial of z + z^2 for z a root of the stored
# polynomial
label = "47.47.60558628944427886416035618894711378994697503545758179730765967261479053047453845062877188530544503628007178201769.1"
coeffs = self.db.nf_fields.lookup(label, "coeffs")
g = pari([int(c) for c in coeffs]).Polrev()
T = pari("x + x^2").Mod(g).charpoly()
self.check_args('/NumberField/?jump=' + quote(str(T)),
[label, 'uses a different defining polynomial'])
# same for a moderate degree: Q(zeta_32), degree 16
T = pari("x + x^2").Mod(pari("polcyclo(32)")).charpoly()
self.check_args('/NumberField/?jump=' + quote(str(T)),
'16.0.18446744073709551616.1')
def test_abelian_nf_label(self):
# the underlying fast path for issue #5471
from sage.all import pari
from lmfdb.number_fields.web_number_field import abelian_nf_label
# degree 8: Q(zeta_20), entered via the minimal polynomial of z + 3z^3
T = (pari("x") + 3 * pari("x^3")).Mod(pari("polcyclo(20)")).charpoly()
assert abelian_nf_label(T) == "8.0.4000000.1"
# non-Galois and Galois-but-non-abelian inputs are left to the
# polredabs path
assert abelian_nf_label(pari("x^8 - 2")) is None
assert abelian_nf_label(pari("polcompositum(x^4 - 2, x^2 + 1)[1]")) is None
# same field, but entered so that the order we can certify maximal is
# very far from maximal: the index is divisible by two primes above
# 10^5, which stay out of S, so nfroots is called with a conditional
# structure (hence gets the defining polynomial, not the nf)
m = 100003 * 100019
T = (m * pari("x")).Mod(pari("polcyclo(20)")).charpoly()
assert abelian_nf_label(T) == "8.0.4000000.1"
def test_known_discriminant_primes(self):
# a large ramified prime shows up in the discriminant as a prime
# power, not as a prime (issue #5471)
from sage.all import ZZ
from lmfdb.number_fields.web_number_field import _known_discriminant_primes
q = ZZ(100003)
S = _known_discriminant_primes(ZZ(2)**20 * ZZ(5)**10 * q**7)
assert S == [ZZ(2), ZZ(5), q]
# a cofactor with two large prime factors is left unfactored
assert _known_discriminant_primes(ZZ(2)**20 * q * ZZ(100019)) == [ZZ(2)]
def test_jump_degree_too_large(self):
# for degrees beyond anything in the database the jump returns
# quickly instead of attempting polredabs (issue #5471): here a
# degree 94 subfield of Q(zeta_283), for which polredabs takes
# more than five minutes
from sage.all import pari
from urllib.parse import quote
T = pari.polsubcyclo(283, 94)
if T.type() == 't_VEC':
T = T[0]
self.check_args('/NumberField/?jump=' + quote(str(T)),
'does not define a number field in the database')
def test_search_disc(self):
self.check_args('/NumberField/?discriminant=1988-2014', '401') # factor of one of the discriminants
def test_url_label(self):
self.check_args('/NumberField/2.2.5.1', '0.481211825') # regulator
def test_url_naturallabel(self):
self.check_args('/NumberField/Qsqrt5', '0.481211825') # regulator
def test_url_naturallabel_custom(self):
# Test various different custom nicknames for number fields
self.check_args('/NumberField/Qi', '2.0.4.1')
self.check_args('/NumberField/Qphi', '2.2.5.1')
self.check_args('/NumberField/Qcbrt2', '3.1.108.1')
self.check_args('/NumberField/Q(sqrt2+sqrt3)', '4.4.2304.1')
self.check_args('/NumberField/Q(sqrt2,sqrt3)', '4.4.2304.1')
self.check_args('/NumberField/Q(sqrt(1 + sqrt2))', '4.2.1024.1')
self.check_args('/NumberField/Q(sqrt2,sqrt3,cbrt2)', '12.4.320979616137216.3')
self.check_args('/NumberField/Q(sqrt2,-sqrt2)', '2.2.8.1')
self.check_args('/NumberField/Q(sqrt2-sqrt2)', '1.1.1.1')
def test_arith_equiv(self):
self.check_args('/NumberField/7.3.6431296.1', '7.3.6431296.2') # arith equiv field
def test_sextic_twin(self):
self.check_args('/NumberField/6.0.10816.1', 'Twin sextic algebra')
def test_how_computed(self):
self.check_args('/NumberField/Source', 'Hunter searches')
def test_galois_group_page(self):
self.check_args('/NumberField/GaloisGroups', 'abstract group may have')
def test_imaginary_quadratic_page(self):
self.check_args('/NumberField/QuadraticImaginaryClassGroups', 'extensive computations')
def test_discriminants_page(self):
self.check_args('/NumberField/Source', 'Jones-David Roberts')
def test_field_labels_page(self):
self.check_args('/NumberField/FieldLabels', 'with the same signature and absolute value of the')
def test_url_bad(self):
self.check_args('/NumberField/junk', 'Error') # error message
def test_random_field(self):
self.check_args('/NumberField/random', 'Discriminant')
def test_statistics(self):
self.check_args('/NumberField/stats', 'Class number')
def test_pretty_labels(self):
# Test "prettified" latex labels for number fields
self.check_args('/NumberField/1.1.1.1', r'\Q')
self.check_args('/NumberField/2.0.4.1', r'\Q(\sqrt{-1})')
self.check_args('/NumberField/4.4.1600.1', r'\Q(\sqrt{2}, \sqrt{5})')
self.check_args('/NumberField/6.0.16807.1', r'\Q(\zeta_{7})')
self.check_args('/NumberField/3.3.49.1', r'\Q(\zeta_{7})^+')
self.check_args('/NumberField/3.1.300.1', r'\Q(\sqrt[3]{10})')
self.check_args('/NumberField/4.2.2048.1', r'\Q(\sqrt[4]{2})')
self.check_args('/NumberField/4.0.512.1', r'\Q(\sqrt{1 + i})')
self.check_args('/NumberField/4.2.1024.1', r'\Q(\sqrt{1 + \sqrt{2}})')
self.check_args('/NumberField/4.0.2048.2', r'\Q(\sqrt{-2 + \sqrt{2}})')
self.check_args('/NumberField/8.8.3317760000.1', r'\Q(\sqrt{2}, \sqrt{3}, \sqrt{5})')
self.check_args('/NumberField/16.0.11007531417600000000.1', r'\Q(i, \sqrt{2}, \sqrt{3}, \sqrt{5})')
self.check_args('/NumberField/32.0.4026692887688564776141139207792885760000000000000000.1', r'\Q(i, \sqrt{2}, \sqrt{3}, \sqrt{5}, \sqrt{7})')
def test_signature_search(self):
# Square brackets
self.check_args('/NumberField/?start=0°ree=6&signature=%5B0%2C3%5D&count=100', '6.0.61131.1')
self.check_args('/NumberField/?start=0°ree=7&signature=%5B3%2C2%5D&count=100', '7.3.1420409.1')
# Round brackets
self.check_args('/NumberField/?start=0°ree=6&signature=%280%2C3%29&count=100', '6.0.61131.1')
self.check_args('/NumberField/?start=0°ree=7&signature=%283%2C2%29&count=100', '7.3.1420409.1')
def test_signature_display(self):
# Verify that signatures are displayed with parentheses, not square brackets
self.check_args('/NumberField/6.0.61131.1', '(0, 3)') # degree 6 field with signature (0, 3)
self.check_args('/NumberField/7.3.1420409.1', '(3, 2)') # degree 7 field with signature (3, 2)
def test_relative_class_number(self):
self.check_args('/NumberField/4.0.1327873600.2', '2108')
def test_fundamental_units(self):
self.check_args('NumberField/2.2.10069.1', '43388173')
self.check_args('NumberField/3.3.10004569.1', '22153437467081345')
def test_split_ors(self):
self.check_args('/NumberField/?signature=%5B0%2C3%5D&galois_group=S3', '6.0.177147.2')
self.check_args('/NumberField/?signature=%5B3%2C0%5D&galois_group=S3', '3.3.229.1')
self.check_args('/NumberField/?signature=[4%2C0]&galois_group=C2xC2&class_number=3%2C6','4.4.1311025.1')
self.check_args('/NumberField/?signature=[4%2C0]&galois_group=C2xC2&class_number=6%2C3','4.4.1311025.1')
self.check_args('/NumberField/?signature=[4%2C0]&galois_group=C2xC2&class_number=5-6%2C3','4.4.485809.1')
def test_underlying_data(self):
self.check_args('NumberField/2.2.10069.1', ['Underlying data', 'data/2.2.10069.1'])
def test_diagram_search(self):
# The Diagram button should be offered on the search page
self.check_args('/NumberField/?degree=2', 'Diagram search')
# Default axes use the computed signed discriminant (disc = disc_sign * disc_abs),
# which is not a stored column; this used to 500 with KeyError: 'disc'.
page = self.tc.get('/NumberField/?search_type=Diagram°ree=2&count=100').get_data(as_text=True)
assert 'my_dataviz' in page
assert 'discriminant' in page
# 2.0.3.1 is the field of discriminant -3, so its x-coordinate is -3
assert '"label": "2.0.3.1"' in page and '"x": "-3"' in page
# The computed absolute-discriminant axis also works
page2 = self.tc.get('/NumberField/?hst=Diagram&x-axis=disc_abs&y-axis=rd°ree=3&count=50').get_data(as_text=True)
assert 'my_dataviz' in page2
assert 'absolute discriminant' in page2
def test_errors(self):
self.check_args('NumberField/18.0.10490638424...4432.1/download/sage', 'Invalid label')
self.check_args('NumberField/4.3.2.1/download/sage', 'There is no number field with label 4.3.2.1')
def test_signature_download(self):
# Test that signature is downloaded as [r1, r2] not [r2, degree]
# For degree 2 fields with negative discriminant: signature is [0, 1] (complex)
# For degree 2 fields with positive discriminant: signature is [2, 0] (real)
url = ('/NumberField/?download=1'
'&query=%7B%27degree%27%3A+2%2C+%27%24or%27%3A+%5B%7B%27disc_sign%27%3A+'
'-1%2C+%27disc_abs%27%3A+%7B%27%24gte%27%3A+1%2C+%27%24lte%27%3A+3%7D%2C+'
'%27degree%27%3A+2%7D%2C+%7B%27disc_sign%27%3A+1%2C+%27disc_abs%27%3A+'
'%7B%27%24lte%27%3A+5%2C+%27%24gte%27%3A+1%7D%2C+%27degree%27%3A+2%7D%5D%7D'
'°ree=2&discriminant=-3-5&showcol=signature&Submit=text')
page = self.tc.get(url).get_data(as_text=True)
# Check that signature format is [r1, r2] where:
# - For imaginary quadratic fields (disc < 0, degree=2): r1=0, r2=1, so signature=[0, 1]
# - For real quadratic fields (disc > 0, degree=2): r1=2, r2=0, so signature=[2, 0]
# The bug was that it showed [r2, degree] = [1, 2] or [0, 2] instead
assert '[0, 1]' in page # imaginary quadratic field (with space, no quotes)
assert '[2, 0]' in page # real quadratic field (with space, no quotes)
# Make sure we're NOT getting the buggy format [r2, degree]
assert '[1, 2]' not in page # wrong format for imaginary quadratic
assert '[0, 2]' not in page # wrong format for real quadratic
# Also ensure we're not getting quoted strings
assert '"[0, 1]"' not in page
assert '"[2, 0]"' not in page