Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/rings/finite_rings/conway_polynomials.py
8820 views
1
"""
2
Routines for Conway and pseudo-Conway polynomials.
3
4
AUTHORS:
5
6
- David Roe
7
8
- Jean-Pierre Flori
9
10
- Peter Bruin
11
"""
12
from sage.structure.sage_object import SageObject
13
from sage.rings.finite_rings.constructor import FiniteField
14
import sage.databases.conway
15
16
def conway_polynomial(p, n):
17
"""
18
Return the Conway polynomial of degree `n` over ``GF(p)``.
19
20
If the requested polynomial is not known, this function raises a
21
``RuntimeError`` exception.
22
23
INPUT:
24
25
- ``p`` -- prime number
26
27
- ``n`` -- positive integer
28
29
OUTPUT:
30
31
- the Conway polynomial of degree `n` over the finite field
32
``GF(p)``, loaded from a table.
33
34
.. NOTE::
35
36
The first time this function is called a table is read from
37
disk, which takes a fraction of a second. Subsequent calls do
38
not require reloading the table.
39
40
See also the ``ConwayPolynomials()`` object, which is the table of
41
Conway polynomials used by this function.
42
43
EXAMPLES::
44
45
sage: conway_polynomial(2,5)
46
x^5 + x^2 + 1
47
sage: conway_polynomial(101,5)
48
x^5 + 2*x + 99
49
sage: conway_polynomial(97,101)
50
Traceback (most recent call last):
51
...
52
RuntimeError: requested Conway polynomial not in database.
53
"""
54
(p, n) = (int(p), int(n))
55
R = FiniteField(p)['x']
56
try:
57
return R(sage.databases.conway.ConwayPolynomials()[p][n])
58
except KeyError:
59
raise RuntimeError("requested Conway polynomial not in database.")
60
61
def exists_conway_polynomial(p, n):
62
"""
63
Check whether the Conway polynomial of degree `n` over ``GF(p)``
64
is known.
65
66
INPUT:
67
68
- ``p`` -- prime number
69
70
- ``n`` -- positive integer
71
72
OUTPUT:
73
74
- boolean: ``True`` if the Conway polynomial of degree `n` over
75
``GF(p)`` is in the database, ``False`` otherwise.
76
77
If the Conway polynomial is in the database, it can be obtained
78
using the command ``conway_polynomial(p,n)``.
79
80
EXAMPLES::
81
82
sage: exists_conway_polynomial(2,3)
83
True
84
sage: exists_conway_polynomial(2,-1)
85
False
86
sage: exists_conway_polynomial(97,200)
87
False
88
sage: exists_conway_polynomial(6,6)
89
False
90
"""
91
return sage.databases.conway.ConwayPolynomials().has_polynomial(p,n)
92
93
class PseudoConwayLattice(SageObject):
94
r"""
95
A pseudo-Conway lattice over a given finite prime field.
96
97
The Conway polynomial `f_n` of degree `n` over `\Bold{F}_p` is
98
defined by the following four conditions:
99
100
- `f_n` is irreducible.
101
102
- In the quotient field `\Bold{F}_p[x]/(f_n)`, the element
103
`x\bmod f_n` generates the multiplicative group.
104
105
- The minimal polynomial of `(x\bmod f_n)^{\frac{p^n-1}{p^m-1}}`
106
equals the Conway polynomial `f_m`, for every divisor `m` of
107
`n`.
108
109
- `f_n` is lexicographically least among all such polynomials,
110
under a certain ordering.
111
112
The final condition is needed only in order to make the Conway
113
polynomial unique. We define a pseudo-Conway lattice to be any
114
family of polynomials, indexed by the positive integers,
115
satisfying the first three conditions.
116
117
INPUT:
118
119
- ``p`` -- prime number
120
121
- ``use_database`` -- boolean. If ``True``, use actual Conway
122
polynomials whenever they are available in the database. If
123
``False``, always compute pseudo-Conway polynomials.
124
125
EXAMPLES::
126
127
sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice
128
sage: PCL = PseudoConwayLattice(2, use_database=False)
129
sage: PCL.polynomial(3)
130
x^3 + x + 1
131
"""
132
133
def __init__(self, p, use_database=True):
134
"""
135
TESTS::
136
137
sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice
138
sage: PCL = PseudoConwayLattice(3)
139
sage: PCL.polynomial(3)
140
x^3 + 2*x + 1
141
142
sage: PCL = PseudoConwayLattice(5, use_database=False)
143
sage: PCL.polynomial(12)
144
x^12 + 2*x^11 + x^10 + 4*x^9 + 4*x^8 + 4*x^7 + x^6 + 4*x^5 + x^4 + 3*x + 2
145
sage: PCL.polynomial(6)
146
x^6 + x^5 + 4*x^4 + 3*x^3 + 3*x^2 + 2*x + 2
147
sage: PCL.polynomial(11)
148
x^11 + x^6 + 3*x^3 + 4*x + 3
149
"""
150
self.p = p
151
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
152
self.ring = PolynomialRing(FiniteField(p), 'x')
153
if use_database:
154
C = sage.databases.conway.ConwayPolynomials()
155
self.nodes = {n: self.ring(C.polynomial(p, n))
156
for n in C.degrees(p)}
157
else:
158
self.nodes = {}
159
160
def polynomial(self, n):
161
r"""
162
Return the pseudo-Conway polynomial of degree `n` in this
163
lattice.
164
165
INPUT:
166
167
- ``n`` -- positive integer
168
169
OUTPUT:
170
171
- a pseudo-Conway polynomial of degree `n` for the prime `p`.
172
173
ALGORITHM:
174
175
Uses an algorithm described in [HL99]_, modified to find
176
pseudo-Conway polynomials rather than Conway polynomials. The
177
major difference is that we stop as soon as we find a
178
primitive polynomial.
179
180
REFERENCE:
181
182
.. [HL99] L. Heath and N. Loehr (1999). New algorithms for
183
generating Conway polynomials over finite fields.
184
Proceedings of the tenth annual ACM-SIAM symposium on
185
discrete algorithms, pp. 429-437.
186
187
EXAMPLES::
188
189
sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice
190
sage: PCL = PseudoConwayLattice(2, use_database=False)
191
sage: PCL.polynomial(3)
192
x^3 + x + 1
193
sage: PCL.polynomial(4)
194
x^4 + x^3 + 1
195
sage: PCL.polynomial(60)
196
x^60 + x^59 + x^58 + x^55 + x^54 + x^53 + x^52 + x^51 + x^48 + x^46 + x^45 + x^42 + x^41 + x^39 + x^38 + x^37 + x^35 + x^32 + x^31 + x^30 + x^28 + x^24 + x^22 + x^21 + x^18 + x^17 + x^16 + x^15 + x^14 + x^10 + x^8 + x^7 + x^5 + x^3 + x^2 + x + 1
197
"""
198
if self.nodes.has_key(n):
199
return self.nodes[n]
200
201
p = self.p
202
203
if n == 1:
204
f = self.ring.gen() - FiniteField(p).multiplicative_generator()
205
self.nodes[1] = f
206
return f
207
208
# Work in an arbitrary field K of order p**n.
209
K = FiniteField(p**n, names='a')
210
211
# TODO: something like the following
212
# gcds = [n.gcd(d) for d in self.nodes.keys()]
213
# xi = { m: (...) for m in gcds }
214
xi = {q: self.polynomial(n//q).any_root(K, -n//q, assume_squarefree=True)
215
for q in n.prime_divisors()}
216
217
# The following is needed to ensure that in the concrete instantiation
218
# of the "new" extension all previous choices are compatible.
219
_frobenius_shift(K, xi)
220
221
# Construct a compatible element having order the lcm of orders
222
q, x = xi.popitem()
223
v = p**(n//q) - 1
224
for q, xitem in xi.iteritems():
225
w = p**(n//q) - 1
226
g, alpha, beta = v.xgcd(w)
227
x = x**beta * xitem**alpha
228
v = v.lcm(w)
229
230
r = p**n - 1
231
# Get the missing part of the order to be primitive
232
g = r // v
233
# Iterate through g-th roots of x until a primitive one is found
234
z = x.nth_root(g)
235
root = K.multiplicative_generator()**v
236
while z.multiplicative_order() != r:
237
z *= root
238
# The following should work but tries to create a huge list
239
# whose length overflows Python's ints for large parameters
240
#Z = x.nth_root(g, all=True)
241
#for z in Z:
242
# if z.multiplicative_order() == r:
243
# break
244
f = z.minimal_polynomial()
245
self.nodes[n] = f
246
return f
247
248
def check_consistency(self, n):
249
"""
250
Check that the pseudo-Conway polynomials of degree dividing
251
`n` in this lattice satisfy the required compatibility
252
conditions.
253
254
EXAMPLES::
255
256
sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice
257
sage: PCL = PseudoConwayLattice(2, use_database=False)
258
sage: PCL.check_consistency(6)
259
sage: PCL.check_consistency(60) # long
260
261
"""
262
p = self.p
263
K = FiniteField(p**n, modulus = self.polynomial(n), names='a')
264
a = K.gen()
265
for m in n.divisors():
266
assert (a**((p**n-1)//(p**m-1))).minimal_polynomial() == self.polynomial(m)
267
268
269
def _find_pow_of_frobenius(p, n, x, y):
270
"""
271
Find the power of Frobenius which yields `x` when applied to `y`.
272
273
INPUT:
274
275
- ``p`` -- prime number
276
277
- ``n`` -- positive integer
278
279
- ``x`` -- an element of a field `K` of `p^n` elements so that
280
the multiplicative order of `x` is `p^n - 1`.
281
282
- ``y`` -- an element of `K` with the same minimal polynomial as
283
`x`.
284
285
OUTPUT:
286
287
- an element `i` of the integers modulo `n` such that `x = y^{p^i}`.
288
289
EXAMPLES::
290
291
sage: from sage.rings.finite_rings.conway_polynomials import _find_pow_of_frobenius
292
sage: K.<a> = GF(3^14)
293
sage: x = K.multiplicative_generator()
294
sage: y = x^27
295
sage: _find_pow_of_frobenius(3, 14, x, y)
296
11
297
298
"""
299
from integer_mod import mod
300
for i in xrange(n):
301
if x == y: break
302
y = y**p
303
else:
304
raise RuntimeError, "No appropriate power of Frobenius found"
305
return mod(i, n)
306
307
def _crt_non_coprime(running, a):
308
"""
309
Extension of the ``crt`` method of ``IntegerMod`` to the case of
310
non-relatively prime modulus.
311
312
EXAMPLES::
313
314
sage: from sage.rings.finite_rings.conway_polynomials import _crt_non_coprime
315
sage: a = _crt_non_coprime(mod(14, 18), mod(20,30)); a
316
50
317
sage: a.modulus()
318
90
319
sage: _crt_non_coprime(mod(13, 18), mod(20,30))
320
Traceback (most recent call last):
321
...
322
AssertionError
323
324
"""
325
g = running.modulus().gcd(a.modulus())
326
if g == 1:
327
return running.crt(a)
328
else:
329
assert running % g == a % g
330
running_modulus = running.modulus()
331
a_modulus = a.modulus()
332
for qq in g.prime_divisors():
333
a_val_unit = a_modulus.val_unit(qq)
334
running_val_unit = running_modulus.val_unit(qq)
335
if a_val_unit[0] > running_val_unit[0]:
336
running_modulus = running_val_unit[1]
337
else:
338
a_modulus = a_val_unit[1]
339
return (running % running_modulus).crt(a % a_modulus)
340
341
def _frobenius_shift(K, generators, check_only=False):
342
"""
343
Given a field `K` of degree `n` over ``GF(p)`` and a dictionary
344
holding, for each divisor `q` of `n`, an element with minimal
345
polynomial a pseudo-Conway polynomial of degree `n/q`, modify
346
these generators into a compatible system.
347
348
Such a system of generators is said to be compatible if for each
349
pair of prime divisors `q_1` and `q_2` and each common divisor `m`
350
of `n/q_1` and `n/q_2`, the equality
351
352
``generators[q1]^((p^(n/q1)-1)/(p^m-1)) == generators[q2]^((p^(n/q2)-1)/(p^m-1))``
353
354
holds.
355
356
INPUT:
357
358
- ``K`` -- a finite field of degree `n` over its prime field
359
360
- ``generators`` -- a dictionary, indexed by prime divisors `q` of
361
`n`, whose entries are elements of `K` satisfying the `n/q`
362
pseudo-Conway polynomial.
363
364
- ``check_only`` -- if ``True``, just check that the given
365
generators form a compatible system.
366
367
EXAMPLES::
368
369
sage: R.<x> = GF(2)[]
370
sage: f30 = x^30 + x^28 + x^27 + x^25 + x^24 + x^20 + x^19 + x^18 + x^16 + x^15 + x^12 + x^10 + x^7 + x^2 + 1
371
sage: f20 = x^20 + x^19 + x^15 + x^13 + x^12 + x^11 + x^9 + x^8 + x^7 + x^4 + x^2 + x + 1
372
sage: f12 = x^12 + x^10 + x^9 + x^8 + x^4 + x^2 + 1
373
sage: K.<a> = GF(2^60, modulus='first_lexicographic')
374
sage: x30 = f30.any_root(K)
375
sage: x20 = f20.any_root(K)
376
sage: x12 = f12.any_root(K)
377
sage: generators = {2: x30, 3: x20, 5: x12}
378
sage: from sage.rings.finite_rings.conway_polynomials import _frobenius_shift, _find_pow_of_frobenius
379
sage: _frobenius_shift(K, generators)
380
sage: _find_pow_of_frobenius(2, 30, x30, generators[2])
381
0
382
sage: _find_pow_of_frobenius(2, 20, x20, generators[3])
383
13
384
sage: _find_pow_of_frobenius(2, 12, x12, generators[5])
385
8
386
387
"""
388
if len(generators) == 1:
389
return generators
390
p = K.characteristic()
391
n = K.degree()
392
compatible = {}
393
from integer_mod import mod
394
for m in n.divisors():
395
compatible[m] = {}
396
for q, x in generators.iteritems():
397
for m in (n//q).divisors():
398
compatible[m][q] = x**((p**(n//q)-1)//(p**m-1))
399
if check_only:
400
for m in n.divisors():
401
try:
402
q, x = compatible[m].popitem()
403
except KeyError:
404
break
405
for qq, xx in compatible[m].iteritems():
406
assert x == xx
407
return
408
crt = {}
409
qlist = sorted(generators.keys())
410
for j in range(1, len(qlist)):
411
for i in range(j):
412
crt[(i, j)] = []
413
for m in n.divisors():
414
mqlist = sorted(compatible[m].keys())
415
for k in range(1,len(mqlist)):
416
j = qlist.index(mqlist[k])
417
i = qlist.index(mqlist[k-1])
418
crt[(i,j)].append(_find_pow_of_frobenius(p, m, compatible[m][qlist[j]], compatible[m][qlist[i]]))
419
from integer_mod import mod
420
pairs = crt.keys()
421
for i, j in pairs:
422
L = crt[(i,j)]
423
running = mod(0,1)
424
for a in L:
425
running = _crt_non_coprime(running, a)
426
crt[(i,j)] = [(mod(running, q**(running.modulus().valuation(q))), running.modulus().valuation(q)) for q in qlist]
427
crt[(j,i)] = [(-a, level) for a, level in crt[(i,j)]]
428
# Let x_j be the power of Frobenius we apply to generators[qlist[j]], for 0 < j < len(qlist)
429
# We have some direct conditions on the x_j: x_j reduces to each entry in crt[(0,j)].
430
# But we also have the equations x_j - x_i reduces to each entry in crt[(i,j)].
431
# We solve for x_j one prime at a time. For each prime, we have an equations of the form
432
# x_j - x_i = c_ij. The modulus of the currently known value of x_j, x_i and c_ij will all be powers
433
# (possibly 0, possibly different) of the same prime.
434
435
# We can set x_0=0 everywhere, can get an initial setting of x_j from the c_0j.
436
# We go through prime by prime.
437
import bisect
438
frob_powers=[mod(0,1) for q in qlist]
439
def find_leveller(qindex, level, x, xleveled, searched, i):
440
searched[i] = True
441
crt_possibles = []
442
for j in range(1,len(qlist)):
443
if i==j: continue
444
if crt[(i,j)][qindex][1] >= level:
445
if xleveled[j]:
446
return [j]
447
elif not searched.has_key(j):
448
crt_possibles.append(j)
449
for j in crt_possibles:
450
path = find_leveller(qindex, level, x, xleveled, searched, j)
451
if path is not None:
452
path.append(j)
453
return path
454
return None
455
def propagate_levelling(qindex, level, x, xleveled, i):
456
for j in range(1, len(qlist)):
457
if i==j: continue
458
if not xleveled[j] and crt[(i,j)][qindex][1] >= level:
459
newxj = x[i][0] + crt[(i,j)][qindex][0]
460
x[j] = (newxj, min(x[i][1], crt[(i,j)][qindex][1]))
461
xleveled[j] = True
462
propagate_levelling(qindex, level, x, xleveled, j)
463
464
for qindex in range(len(qlist)):
465
q = qlist[qindex]
466
# We include the initial 0 to match up our indexing with crt.
467
x = [0] + [crt[(0,j)][qindex] for j in range(1,len(qlist))]
468
# We first check that our equations are consistent and
469
# determine which powers of q occur as moduli.
470
levels = []
471
for j in range(2, len(qlist)):
472
for i in range(j):
473
# we need crt[(0,j)] = crt[(0,i)] + crt[(i,j)]
474
if i != 0:
475
assert x[j][0] == x[i][0] + crt[(i,j)][qindex][0]
476
level = crt[(i,j)][qindex][1]
477
if level > 0:
478
ins = bisect.bisect_left(levels,level)
479
if ins == len(levels):
480
levels.append(level)
481
elif levels[ins] != level:
482
levels.insert(ins, level)
483
for level in levels:
484
xleveled = [0] + [x[i][1] >= level for i in range(1,len(qlist))]
485
while True:
486
try:
487
i = xleveled.index(False, 1)
488
searched = {}
489
levelling_path = find_leveller(qindex, level, x, xleveled, searched, i)
490
if levelling_path is None:
491
# Any lift will work, since there are no constraints.
492
x[i] = (mod(x[i][0].lift(), q**level), level)
493
xleveled[i] = True
494
propagate_levelling(qindex, level, x, xleveled, i)
495
else:
496
levelling_path.append(i)
497
for m in range(1,len(path)):
498
# This point on the path may have already
499
# been leveled in a previous propagation.
500
if not xleveled[path[m]]:
501
newx = x[path[m-1]][0] + crt[(path[m-1],path[m])][qindex][0]
502
x[path[m]] = (newx, min(x[path[m-1]][1], crt[(path[m-1],path[m])][qindex][1]))
503
xleveled[path[m]] = True
504
propagate_levelling(qindex, level, x, xleveled, path[m])
505
except ValueError:
506
break
507
for j in range(1,len(qlist)):
508
frob_powers[j] = frob_powers[j].crt(x[j][0])
509
for j in range(1, len(qlist)):
510
generators[qlist[j]] = generators[qlist[j]]**(p**(-frob_powers[j]).lift())
511
_frobenius_shift(K, generators, check_only=True)
512
513