-
Notifications
You must be signed in to change notification settings - Fork 241
Expand file tree
/
Copy pathtest_numberfield.py
More file actions
247 lines (208 loc) · 13.9 KB
/
Copy pathtest_numberfield.py
File metadata and controls
247 lines (208 loc) · 13.9 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
from psycodict.utils import SearchParsingError
from lmfdb.tests import LmfdbTest
from lmfdb.utils.search_parsing import parse_subfield
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 parse_subfield_query(self, inp):
"""The query produced by the "Intermediate field" search box."""
query = {}
parse_subfield({'subfield': inp}, query, field='subfield', qfield='subfields')
return query
def test_parse_subfield(self):
# A single intermediate field gives a scalar $contains, as it did before
# lists were allowed
self.assertEqual(self.parse_subfield_query('x^2-2'),
{'subfields': {'$contains': '-2.0.1'}})
# A comma-separated list gives a single $contains holding every entry, in
# the order given. On the text[] subfields column that compiles to the
# Postgres containment subfields @> ARRAY[...], i.e. the AND of the
# containment conditions -- not the first entry alone, and not an OR.
self.assertEqual(self.parse_subfield_query('x^2-2,x^2-3'),
{'subfields': {'$contains': ['-2.0.1', '-3.0.1']}})
self.assertEqual(self.parse_subfield_query('x^2-3,x^2-2'),
{'subfields': {'$contains': ['-3.0.1', '-2.0.1']}})
# Labels, polynomials and nicknames may be mixed freely
self.assertEqual(self.parse_subfield_query('2.2.8.1,Qsqrt3'),
{'subfields': {'$contains': ['-2.0.1', '-3.0.1']}})
# Whitespace is stripped by the search parser
self.assertEqual(self.parse_subfield_query('x^2-2, x^2-3'),
{'subfields': {'$contains': ['-2.0.1', '-3.0.1']}})
def test_parse_subfield_errors(self):
# An empty entry, from a leading, trailing or repeated comma, is an error:
# dropping it would silently weaken the search, and dropping the only
# entries of "," would drop the constraint altogether. An entry that is
# empty only after unsupported characters are removed ('é' below) is
# an error too, and a malformed entry is an error as before.
for bad in [',', 'x^2-2,', ',x^2-2', 'x^2-2,,x^2-3', 'x^2-2,é', 'x^2-2,notafield']:
query = {}
# The parser flashes the error before re-raising, so it needs a request
with self.app.test_request_context():
with self.assertRaises(SearchParsingError):
parse_subfield({'subfield': bad}, query, field='subfield', qfield='subfields')
self.assertEqual(query, {}, "%s did not leave the query untouched" % bad)
def test_search_subfield(self):
# An end to end check that a list search really is an AND. Among the
# quartic fields of discriminant at most 3000, both Q(sqrt2,sqrt3) =
# 4.4.2304.1 and Q(zeta_8) = 4.0.256.1 contain Q(sqrt2), but only the
# first also contains Q(sqrt3), so it alone answers the list search.
# (The degree and discriminant bounds keep the search fast; the query
# itself is checked without a database search in test_parse_subfield.)
self.check_args('/NumberField/?degree=4&discriminant=1-3000&subfield=x%5E2-2',
['4.4.2304.1', '4.0.256.1'])
self.check_args('/NumberField/?degree=4&discriminant=1-3000&subfield=x%5E2-2%2Cx%5E2-3',
'4.4.2304.1')
self.not_check_args('/NumberField/?degree=4&discriminant=1-3000&subfield=x%5E2-2%2Cx%5E2-3',
'4.0.256.1')
# A malformed or empty entry gives a clean error page, not a 500
self.check_args('/NumberField/?subfield=x%5E2-2%2Cnotafield', 'not a valid field nickname or label')
self.check_args('/NumberField/?subfield=x%5E2-2%2C', 'Entries in the comma-separated list must be nonempty')
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