Path: blob/master/src/sage/rings/finite_rings/conway_polynomials.py
8820 views
"""1Routines for Conway and pseudo-Conway polynomials.23AUTHORS:45- David Roe67- Jean-Pierre Flori89- Peter Bruin10"""11from sage.structure.sage_object import SageObject12from sage.rings.finite_rings.constructor import FiniteField13import sage.databases.conway1415def conway_polynomial(p, n):16"""17Return the Conway polynomial of degree `n` over ``GF(p)``.1819If the requested polynomial is not known, this function raises a20``RuntimeError`` exception.2122INPUT:2324- ``p`` -- prime number2526- ``n`` -- positive integer2728OUTPUT:2930- the Conway polynomial of degree `n` over the finite field31``GF(p)``, loaded from a table.3233.. NOTE::3435The first time this function is called a table is read from36disk, which takes a fraction of a second. Subsequent calls do37not require reloading the table.3839See also the ``ConwayPolynomials()`` object, which is the table of40Conway polynomials used by this function.4142EXAMPLES::4344sage: conway_polynomial(2,5)45x^5 + x^2 + 146sage: conway_polynomial(101,5)47x^5 + 2*x + 9948sage: conway_polynomial(97,101)49Traceback (most recent call last):50...51RuntimeError: requested Conway polynomial not in database.52"""53(p, n) = (int(p), int(n))54R = FiniteField(p)['x']55try:56return R(sage.databases.conway.ConwayPolynomials()[p][n])57except KeyError:58raise RuntimeError("requested Conway polynomial not in database.")5960def exists_conway_polynomial(p, n):61"""62Check whether the Conway polynomial of degree `n` over ``GF(p)``63is known.6465INPUT:6667- ``p`` -- prime number6869- ``n`` -- positive integer7071OUTPUT:7273- boolean: ``True`` if the Conway polynomial of degree `n` over74``GF(p)`` is in the database, ``False`` otherwise.7576If the Conway polynomial is in the database, it can be obtained77using the command ``conway_polynomial(p,n)``.7879EXAMPLES::8081sage: exists_conway_polynomial(2,3)82True83sage: exists_conway_polynomial(2,-1)84False85sage: exists_conway_polynomial(97,200)86False87sage: exists_conway_polynomial(6,6)88False89"""90return sage.databases.conway.ConwayPolynomials().has_polynomial(p,n)9192class PseudoConwayLattice(SageObject):93r"""94A pseudo-Conway lattice over a given finite prime field.9596The Conway polynomial `f_n` of degree `n` over `\Bold{F}_p` is97defined by the following four conditions:9899- `f_n` is irreducible.100101- In the quotient field `\Bold{F}_p[x]/(f_n)`, the element102`x\bmod f_n` generates the multiplicative group.103104- The minimal polynomial of `(x\bmod f_n)^{\frac{p^n-1}{p^m-1}}`105equals the Conway polynomial `f_m`, for every divisor `m` of106`n`.107108- `f_n` is lexicographically least among all such polynomials,109under a certain ordering.110111The final condition is needed only in order to make the Conway112polynomial unique. We define a pseudo-Conway lattice to be any113family of polynomials, indexed by the positive integers,114satisfying the first three conditions.115116INPUT:117118- ``p`` -- prime number119120- ``use_database`` -- boolean. If ``True``, use actual Conway121polynomials whenever they are available in the database. If122``False``, always compute pseudo-Conway polynomials.123124EXAMPLES::125126sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice127sage: PCL = PseudoConwayLattice(2, use_database=False)128sage: PCL.polynomial(3)129x^3 + x + 1130"""131132def __init__(self, p, use_database=True):133"""134TESTS::135136sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice137sage: PCL = PseudoConwayLattice(3)138sage: PCL.polynomial(3)139x^3 + 2*x + 1140141sage: PCL = PseudoConwayLattice(5, use_database=False)142sage: PCL.polynomial(12)143x^12 + 2*x^11 + x^10 + 4*x^9 + 4*x^8 + 4*x^7 + x^6 + 4*x^5 + x^4 + 3*x + 2144sage: PCL.polynomial(6)145x^6 + x^5 + 4*x^4 + 3*x^3 + 3*x^2 + 2*x + 2146sage: PCL.polynomial(11)147x^11 + x^6 + 3*x^3 + 4*x + 3148"""149self.p = p150from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing151self.ring = PolynomialRing(FiniteField(p), 'x')152if use_database:153C = sage.databases.conway.ConwayPolynomials()154self.nodes = {n: self.ring(C.polynomial(p, n))155for n in C.degrees(p)}156else:157self.nodes = {}158159def polynomial(self, n):160r"""161Return the pseudo-Conway polynomial of degree `n` in this162lattice.163164INPUT:165166- ``n`` -- positive integer167168OUTPUT:169170- a pseudo-Conway polynomial of degree `n` for the prime `p`.171172ALGORITHM:173174Uses an algorithm described in [HL99]_, modified to find175pseudo-Conway polynomials rather than Conway polynomials. The176major difference is that we stop as soon as we find a177primitive polynomial.178179REFERENCE:180181.. [HL99] L. Heath and N. Loehr (1999). New algorithms for182generating Conway polynomials over finite fields.183Proceedings of the tenth annual ACM-SIAM symposium on184discrete algorithms, pp. 429-437.185186EXAMPLES::187188sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice189sage: PCL = PseudoConwayLattice(2, use_database=False)190sage: PCL.polynomial(3)191x^3 + x + 1192sage: PCL.polynomial(4)193x^4 + x^3 + 1194sage: PCL.polynomial(60)195x^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 + 1196"""197if self.nodes.has_key(n):198return self.nodes[n]199200p = self.p201202if n == 1:203f = self.ring.gen() - FiniteField(p).multiplicative_generator()204self.nodes[1] = f205return f206207# Work in an arbitrary field K of order p**n.208K = FiniteField(p**n, names='a')209210# TODO: something like the following211# gcds = [n.gcd(d) for d in self.nodes.keys()]212# xi = { m: (...) for m in gcds }213xi = {q: self.polynomial(n//q).any_root(K, -n//q, assume_squarefree=True)214for q in n.prime_divisors()}215216# The following is needed to ensure that in the concrete instantiation217# of the "new" extension all previous choices are compatible.218_frobenius_shift(K, xi)219220# Construct a compatible element having order the lcm of orders221q, x = xi.popitem()222v = p**(n//q) - 1223for q, xitem in xi.iteritems():224w = p**(n//q) - 1225g, alpha, beta = v.xgcd(w)226x = x**beta * xitem**alpha227v = v.lcm(w)228229r = p**n - 1230# Get the missing part of the order to be primitive231g = r // v232# Iterate through g-th roots of x until a primitive one is found233z = x.nth_root(g)234root = K.multiplicative_generator()**v235while z.multiplicative_order() != r:236z *= root237# The following should work but tries to create a huge list238# whose length overflows Python's ints for large parameters239#Z = x.nth_root(g, all=True)240#for z in Z:241# if z.multiplicative_order() == r:242# break243f = z.minimal_polynomial()244self.nodes[n] = f245return f246247def check_consistency(self, n):248"""249Check that the pseudo-Conway polynomials of degree dividing250`n` in this lattice satisfy the required compatibility251conditions.252253EXAMPLES::254255sage: from sage.rings.finite_rings.conway_polynomials import PseudoConwayLattice256sage: PCL = PseudoConwayLattice(2, use_database=False)257sage: PCL.check_consistency(6)258sage: PCL.check_consistency(60) # long259260"""261p = self.p262K = FiniteField(p**n, modulus = self.polynomial(n), names='a')263a = K.gen()264for m in n.divisors():265assert (a**((p**n-1)//(p**m-1))).minimal_polynomial() == self.polynomial(m)266267268def _find_pow_of_frobenius(p, n, x, y):269"""270Find the power of Frobenius which yields `x` when applied to `y`.271272INPUT:273274- ``p`` -- prime number275276- ``n`` -- positive integer277278- ``x`` -- an element of a field `K` of `p^n` elements so that279the multiplicative order of `x` is `p^n - 1`.280281- ``y`` -- an element of `K` with the same minimal polynomial as282`x`.283284OUTPUT:285286- an element `i` of the integers modulo `n` such that `x = y^{p^i}`.287288EXAMPLES::289290sage: from sage.rings.finite_rings.conway_polynomials import _find_pow_of_frobenius291sage: K.<a> = GF(3^14)292sage: x = K.multiplicative_generator()293sage: y = x^27294sage: _find_pow_of_frobenius(3, 14, x, y)29511296297"""298from integer_mod import mod299for i in xrange(n):300if x == y: break301y = y**p302else:303raise RuntimeError, "No appropriate power of Frobenius found"304return mod(i, n)305306def _crt_non_coprime(running, a):307"""308Extension of the ``crt`` method of ``IntegerMod`` to the case of309non-relatively prime modulus.310311EXAMPLES::312313sage: from sage.rings.finite_rings.conway_polynomials import _crt_non_coprime314sage: a = _crt_non_coprime(mod(14, 18), mod(20,30)); a31550316sage: a.modulus()31790318sage: _crt_non_coprime(mod(13, 18), mod(20,30))319Traceback (most recent call last):320...321AssertionError322323"""324g = running.modulus().gcd(a.modulus())325if g == 1:326return running.crt(a)327else:328assert running % g == a % g329running_modulus = running.modulus()330a_modulus = a.modulus()331for qq in g.prime_divisors():332a_val_unit = a_modulus.val_unit(qq)333running_val_unit = running_modulus.val_unit(qq)334if a_val_unit[0] > running_val_unit[0]:335running_modulus = running_val_unit[1]336else:337a_modulus = a_val_unit[1]338return (running % running_modulus).crt(a % a_modulus)339340def _frobenius_shift(K, generators, check_only=False):341"""342Given a field `K` of degree `n` over ``GF(p)`` and a dictionary343holding, for each divisor `q` of `n`, an element with minimal344polynomial a pseudo-Conway polynomial of degree `n/q`, modify345these generators into a compatible system.346347Such a system of generators is said to be compatible if for each348pair of prime divisors `q_1` and `q_2` and each common divisor `m`349of `n/q_1` and `n/q_2`, the equality350351``generators[q1]^((p^(n/q1)-1)/(p^m-1)) == generators[q2]^((p^(n/q2)-1)/(p^m-1))``352353holds.354355INPUT:356357- ``K`` -- a finite field of degree `n` over its prime field358359- ``generators`` -- a dictionary, indexed by prime divisors `q` of360`n`, whose entries are elements of `K` satisfying the `n/q`361pseudo-Conway polynomial.362363- ``check_only`` -- if ``True``, just check that the given364generators form a compatible system.365366EXAMPLES::367368sage: R.<x> = GF(2)[]369sage: 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 + 1370sage: f20 = x^20 + x^19 + x^15 + x^13 + x^12 + x^11 + x^9 + x^8 + x^7 + x^4 + x^2 + x + 1371sage: f12 = x^12 + x^10 + x^9 + x^8 + x^4 + x^2 + 1372sage: K.<a> = GF(2^60, modulus='first_lexicographic')373sage: x30 = f30.any_root(K)374sage: x20 = f20.any_root(K)375sage: x12 = f12.any_root(K)376sage: generators = {2: x30, 3: x20, 5: x12}377sage: from sage.rings.finite_rings.conway_polynomials import _frobenius_shift, _find_pow_of_frobenius378sage: _frobenius_shift(K, generators)379sage: _find_pow_of_frobenius(2, 30, x30, generators[2])3800381sage: _find_pow_of_frobenius(2, 20, x20, generators[3])38213383sage: _find_pow_of_frobenius(2, 12, x12, generators[5])3848385386"""387if len(generators) == 1:388return generators389p = K.characteristic()390n = K.degree()391compatible = {}392from integer_mod import mod393for m in n.divisors():394compatible[m] = {}395for q, x in generators.iteritems():396for m in (n//q).divisors():397compatible[m][q] = x**((p**(n//q)-1)//(p**m-1))398if check_only:399for m in n.divisors():400try:401q, x = compatible[m].popitem()402except KeyError:403break404for qq, xx in compatible[m].iteritems():405assert x == xx406return407crt = {}408qlist = sorted(generators.keys())409for j in range(1, len(qlist)):410for i in range(j):411crt[(i, j)] = []412for m in n.divisors():413mqlist = sorted(compatible[m].keys())414for k in range(1,len(mqlist)):415j = qlist.index(mqlist[k])416i = qlist.index(mqlist[k-1])417crt[(i,j)].append(_find_pow_of_frobenius(p, m, compatible[m][qlist[j]], compatible[m][qlist[i]]))418from integer_mod import mod419pairs = crt.keys()420for i, j in pairs:421L = crt[(i,j)]422running = mod(0,1)423for a in L:424running = _crt_non_coprime(running, a)425crt[(i,j)] = [(mod(running, q**(running.modulus().valuation(q))), running.modulus().valuation(q)) for q in qlist]426crt[(j,i)] = [(-a, level) for a, level in crt[(i,j)]]427# Let x_j be the power of Frobenius we apply to generators[qlist[j]], for 0 < j < len(qlist)428# We have some direct conditions on the x_j: x_j reduces to each entry in crt[(0,j)].429# But we also have the equations x_j - x_i reduces to each entry in crt[(i,j)].430# We solve for x_j one prime at a time. For each prime, we have an equations of the form431# x_j - x_i = c_ij. The modulus of the currently known value of x_j, x_i and c_ij will all be powers432# (possibly 0, possibly different) of the same prime.433434# We can set x_0=0 everywhere, can get an initial setting of x_j from the c_0j.435# We go through prime by prime.436import bisect437frob_powers=[mod(0,1) for q in qlist]438def find_leveller(qindex, level, x, xleveled, searched, i):439searched[i] = True440crt_possibles = []441for j in range(1,len(qlist)):442if i==j: continue443if crt[(i,j)][qindex][1] >= level:444if xleveled[j]:445return [j]446elif not searched.has_key(j):447crt_possibles.append(j)448for j in crt_possibles:449path = find_leveller(qindex, level, x, xleveled, searched, j)450if path is not None:451path.append(j)452return path453return None454def propagate_levelling(qindex, level, x, xleveled, i):455for j in range(1, len(qlist)):456if i==j: continue457if not xleveled[j] and crt[(i,j)][qindex][1] >= level:458newxj = x[i][0] + crt[(i,j)][qindex][0]459x[j] = (newxj, min(x[i][1], crt[(i,j)][qindex][1]))460xleveled[j] = True461propagate_levelling(qindex, level, x, xleveled, j)462463for qindex in range(len(qlist)):464q = qlist[qindex]465# We include the initial 0 to match up our indexing with crt.466x = [0] + [crt[(0,j)][qindex] for j in range(1,len(qlist))]467# We first check that our equations are consistent and468# determine which powers of q occur as moduli.469levels = []470for j in range(2, len(qlist)):471for i in range(j):472# we need crt[(0,j)] = crt[(0,i)] + crt[(i,j)]473if i != 0:474assert x[j][0] == x[i][0] + crt[(i,j)][qindex][0]475level = crt[(i,j)][qindex][1]476if level > 0:477ins = bisect.bisect_left(levels,level)478if ins == len(levels):479levels.append(level)480elif levels[ins] != level:481levels.insert(ins, level)482for level in levels:483xleveled = [0] + [x[i][1] >= level for i in range(1,len(qlist))]484while True:485try:486i = xleveled.index(False, 1)487searched = {}488levelling_path = find_leveller(qindex, level, x, xleveled, searched, i)489if levelling_path is None:490# Any lift will work, since there are no constraints.491x[i] = (mod(x[i][0].lift(), q**level), level)492xleveled[i] = True493propagate_levelling(qindex, level, x, xleveled, i)494else:495levelling_path.append(i)496for m in range(1,len(path)):497# This point on the path may have already498# been leveled in a previous propagation.499if not xleveled[path[m]]:500newx = x[path[m-1]][0] + crt[(path[m-1],path[m])][qindex][0]501x[path[m]] = (newx, min(x[path[m-1]][1], crt[(path[m-1],path[m])][qindex][1]))502xleveled[path[m]] = True503propagate_levelling(qindex, level, x, xleveled, path[m])504except ValueError:505break506for j in range(1,len(qlist)):507frob_powers[j] = frob_powers[j].crt(x[j][0])508for j in range(1, len(qlist)):509generators[qlist[j]] = generators[qlist[j]]**(p**(-frob_powers[j]).lift())510_frobenius_shift(K, generators, check_only=True)511512513