123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- r"""
- Galois resolvents
- Each of the functions in ``sympy.polys.numberfields.galoisgroups`` that
- computes Galois groups for a particular degree $n$ uses resolvents. Given the
- polynomial $T$ whose Galois group is to be computed, a resolvent is a
- polynomial $R$ whose roots are defined as functions of the roots of $T$.
- One way to compute the coefficients of $R$ is by approximating the roots of $T$
- to sufficient precision. This module defines a :py:class:`~.Resolvent` class
- that handles this job, determining the necessary precision, and computing $R$.
- In some cases, the coefficients of $R$ are symmetric in the roots of $T$,
- meaning they are equal to fixed functions of the coefficients of $T$. Therefore
- another approach is to compute these functions once and for all, and record
- them in a lookup table. This module defines code that can compute such tables.
- The tables for polynomials $T$ of degrees 4 through 6, produced by this code,
- are recorded in the resolvent_lookup.py module.
- """
- from sympy.core.evalf import (
- evalf, fastlog, _evalf_with_bounded_error, quad_to_mpmath,
- )
- from sympy.core.symbol import symbols, Dummy
- from sympy.polys.densetools import dup_eval
- from sympy.polys.domains import ZZ
- from sympy.polys.numberfields.resolvent_lookup import resolvent_coeff_lambdas
- from sympy.polys.orderings import lex
- from sympy.polys.polyroots import preprocess_roots
- from sympy.polys.polytools import Poly
- from sympy.polys.rings import xring
- from sympy.polys.specialpolys import symmetric_poly
- from sympy.utilities.lambdify import lambdify
- from mpmath import MPContext
- from mpmath.libmp.libmpf import prec_to_dps
- class GaloisGroupException(Exception):
- ...
- class ResolventException(GaloisGroupException):
- ...
- class Resolvent:
- r"""
- If $G$ is a subgroup of the symmetric group $S_n$,
- $F$ a multivariate polynomial in $\mathbb{Z}[X_1, \ldots, X_n]$,
- $H$ the stabilizer of $F$ in $G$ (i.e. the permutations $\sigma$ such that
- $F(X_{\sigma(1)}, \ldots, X_{\sigma(n)}) = F(X_1, \ldots, X_n)$), and $s$
- a set of left coset representatives of $H$ in $G$, then the resolvent
- polynomial $R(Y)$ is the product over $\sigma \in s$ of
- $Y - F(X_{\sigma(1)}, \ldots, X_{\sigma(n)})$.
- For example, consider the resolvent for the form
- $$F = X_0 X_2 + X_1 X_3$$
- and the group $G = S_4$. In this case, the stabilizer $H$ is the dihedral
- group $D4 = < (0123), (02) >$, and a set of representatives of $G/H$ is
- $\{I, (01), (03)\}$. The resolvent can be constructed as follows:
- >>> from sympy.combinatorics.permutations import Permutation
- >>> from sympy.core.symbol import symbols
- >>> from sympy.polys.numberfields.galoisgroups import Resolvent
- >>> X = symbols('X0 X1 X2 X3')
- >>> F = X[0]*X[2] + X[1]*X[3]
- >>> s = [Permutation([0, 1, 2, 3]), Permutation([1, 0, 2, 3]),
- ... Permutation([3, 1, 2, 0])]
- >>> R = Resolvent(F, X, s)
- This resolvent has three roots, which are the conjugates of ``F`` under the
- three permutations in ``s``:
- >>> R.root_lambdas[0](*X)
- X0*X2 + X1*X3
- >>> R.root_lambdas[1](*X)
- X0*X3 + X1*X2
- >>> R.root_lambdas[2](*X)
- X0*X1 + X2*X3
- Resolvents are useful for computing Galois groups. Given a polynomial $T$
- of degree $n$, we will use a resolvent $R$ where $Gal(T) \leq G \leq S_n$.
- We will then want to substitute the roots of $T$ for the variables $X_i$
- in $R$, and study things like the discriminant of $R$, and the way $R$
- factors over $\mathbb{Q}$.
- From the symmetry in $R$'s construction, and since $Gal(T) \leq G$, we know
- from Galois theory that the coefficients of $R$ must lie in $\mathbb{Z}$.
- This allows us to compute the coefficients of $R$ by approximating the
- roots of $T$ to sufficient precision, plugging these values in for the
- variables $X_i$ in the coefficient expressions of $R$, and then simply
- rounding to the nearest integer.
- In order to determine a sufficient precision for the roots of $T$, this
- ``Resolvent`` class imposes certain requirements on the form ``F``. It
- could be possible to design a different ``Resolvent`` class, that made
- different precision estimates, and different assumptions about ``F``.
- ``F`` must be homogeneous, and all terms must have unit coefficient.
- Furthermore, if $r$ is the number of terms in ``F``, and $t$ the total
- degree, and if $m$ is the number of conjugates of ``F``, i.e. the number
- of permutations in ``s``, then we require that $m < r 2^t$. Again, it is
- not impossible to work with forms ``F`` that violate these assumptions, but
- this ``Resolvent`` class requires them.
- Since determining the integer coefficients of the resolvent for a given
- polynomial $T$ is one of the main problems this class solves, we take some
- time to explain the precision bounds it uses.
- The general problem is:
- Given a multivariate polynomial $P \in \mathbb{Z}[X_1, \ldots, X_n]$, and a
- bound $M \in \mathbb{R}_+$, compute an $\varepsilon > 0$ such that for any
- complex numbers $a_1, \ldots, a_n$ with $|a_i| < M$, if the $a_i$ are
- approximated to within an accuracy of $\varepsilon$ by $b_i$, that is,
- $|a_i - b_i| < \varepsilon$ for $i = 1, \ldots, n$, then
- $|P(a_1, \ldots, a_n) - P(b_1, \ldots, b_n)| < 1/2$. In other words, if it
- is known that $P(a_1, \ldots, a_n) = c$ for some $c \in \mathbb{Z}$, then
- $P(b_1, \ldots, b_n)$ can be rounded to the nearest integer in order to
- determine $c$.
- To derive our error bound, consider the monomial $xyz$. Defining
- $d_i = b_i - a_i$, our error is
- $|(a_1 + d_1)(a_2 + d_2)(a_3 + d_3) - a_1 a_2 a_3|$, which is bounded
- above by $|(M + \varepsilon)^3 - M^3|$. Passing to a general monomial of
- total degree $t$, this expression is bounded by
- $M^{t-1}\varepsilon(t + 2^t\varepsilon/M)$ provided $\varepsilon < M$,
- and by $(t+1)M^{t-1}\varepsilon$ provided $\varepsilon < M/2^t$.
- But since our goal is to make the error less than $1/2$, we will choose
- $\varepsilon < 1/(2(t+1)M^{t-1})$, which implies the condition that
- $\varepsilon < M/2^t$, as long as $M \geq 2$.
- Passing from the general monomial to the general polynomial is easy, by
- scaling and summing error bounds.
- In our specific case, we are given a homogeneous polynomial $F$ of
- $r$ terms and total degree $t$, all of whose coefficients are $\pm 1$. We
- are given the $m$ permutations that make the conjugates of $F$, and
- we want to bound the error in the coefficients of the monic polynomial
- $R(Y)$ having $F$ and its conjugates as roots (i.e. the resolvent).
- For $j$ from $1$ to $m$, the coefficient of $Y^{m-j}$ in $R(Y)$ is the
- $j$th elementary symmetric polynomial in the conjugates of $F$. This sums
- the products of these conjugates, taken $j$ at a time, in all possible
- combinations. There are $\binom{m}{j}$ such combinations, and each product
- of $j$ conjugates of $F$ expands to a sum of $r^j$ terms, each of unit
- coefficient, and total degree $jt$. An error bound for the $j$th coeff of
- $R$ is therefore
- $$\binom{m}{j} r^j (jt + 1) M^{jt - 1} \varepsilon$$
- When our goal is to evaluate all the coefficients of $R$, we will want to
- use the maximum of these error bounds. It is clear that this bound is
- strictly increasing for $j$ up to the ceiling of $m/2$. After that point,
- the first factor $\binom{m}{j}$ begins to decrease, while the others
- continue to increase. However, the binomial coefficient never falls by more
- than a factor of $1/m$ at a time, so our assumptions that $M \geq 2$ and
- $m < r 2^t$ are enough to tell us that the constant coefficient of $R$,
- i.e. that where $j = m$, has the largest error bound. Therefore we can use
- $$r^m (mt + 1) M^{mt - 1} \varepsilon$$
- as our error bound for all the coefficients.
- Note that this bound is also (more than) adequate to determine whether any
- of the roots of $R$ is an integer. Each of these roots is a single
- conjugate of $F$, which contains less error than the trace, i.e. the
- coefficient of $Y^{m - 1}$. By rounding the roots of $R$ to the nearest
- integers, we therefore get all the candidates for integer roots of $R$. By
- plugging these candidates into $R$, we can check whether any of them
- actually is a root.
- Note: We take the definition of resolvent from Cohen, but the error bound
- is ours.
- References
- ==========
- .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
- (Def 6.3.2)
- """
- def __init__(self, F, X, s):
- r"""
- Parameters
- ==========
- F : :py:class:`~.Expr`
- polynomial in the symbols in *X*
- X : list of :py:class:`~.Symbol`
- s : list of :py:class:`~.Permutation`
- representing the cosets of the stabilizer of *F* in
- some subgroup $G$ of $S_n$, where $n$ is the length of *X*.
- """
- self.F = F
- self.X = X
- self.s = s
- # Number of conjugates:
- self.m = len(s)
- # Total degree of F (computed below):
- self.t = None
- # Number of terms in F (computed below):
- self.r = 0
- for monom, coeff in Poly(F).terms():
- if abs(coeff) != 1:
- raise ResolventException('Resolvent class expects forms with unit coeffs')
- t = sum(monom)
- if t != self.t and self.t is not None:
- raise ResolventException('Resolvent class expects homogeneous forms')
- self.t = t
- self.r += 1
- m, t, r = self.m, self.t, self.r
- if not m < r * 2**t:
- raise ResolventException('Resolvent class expects m < r*2^t')
- M = symbols('M')
- # Precision sufficient for computing the coeffs of the resolvent:
- self.coeff_prec_func = Poly(r**m*(m*t + 1)*M**(m*t - 1))
- # Precision sufficient for checking whether any of the roots of the
- # resolvent are integers:
- self.root_prec_func = Poly(r*(t + 1)*M**(t - 1))
- # The conjugates of F are the roots of the resolvent.
- # For evaluating these to required numerical precisions, we need
- # lambdified versions.
- # Note: for a given permutation sigma, the conjugate (sigma F) is
- # equivalent to lambda [sigma^(-1) X]: F.
- self.root_lambdas = [
- lambdify((~s[j])(X), F)
- for j in range(self.m)
- ]
- # For evaluating the coeffs, we'll also need lambdified versions of
- # the elementary symmetric functions for degree m.
- Y = symbols('Y')
- R = symbols(' '.join(f'R{i}' for i in range(m)))
- f = 1
- for r in R:
- f *= (Y - r)
- C = Poly(f, Y).coeffs()
- self.esf_lambdas = [lambdify(R, c) for c in C]
- def get_prec(self, M, target='coeffs'):
- r"""
- For a given upper bound *M* on the magnitude of the complex numbers to
- be plugged in for this resolvent's symbols, compute a sufficient
- precision for evaluating those complex numbers, such that the
- coefficients, or the integer roots, of the resolvent can be determined.
- Parameters
- ==========
- M : real number
- Upper bound on magnitude of the complex numbers to be plugged in.
- target : str, 'coeffs' or 'roots', default='coeffs'
- Name the task for which a sufficient precision is desired.
- This is either determining the coefficients of the resolvent
- ('coeffs') or determining its possible integer roots ('roots').
- The latter may require significantly lower precision.
- Returns
- =======
- int $m$
- such that $2^{-m}$ is a sufficient upper bound on the
- error in approximating the complex numbers to be plugged in.
- """
- # As explained in the docstring for this class, our precision estimates
- # require that M be at least 2.
- M = max(M, 2)
- f = self.coeff_prec_func if target == 'coeffs' else self.root_prec_func
- r, _, _, _ = evalf(2*f(M), 1, {})
- return fastlog(r) + 1
- def approximate_roots_of_poly(self, T, target='coeffs'):
- """
- Approximate the roots of a given polynomial *T* to sufficient precision
- in order to evaluate this resolvent's coefficients, or determine
- whether the resolvent has an integer root.
- Parameters
- ==========
- T : :py:class:`~.Poly`
- target : str, 'coeffs' or 'roots', default='coeffs'
- Set the approximation precision to be sufficient for the desired
- task, which is either determining the coefficients of the resolvent
- ('coeffs') or determining its possible integer roots ('roots').
- The latter may require significantly lower precision.
- Returns
- =======
- list of elements of :ref:`CC`
- """
- ctx = MPContext()
- # Because sympy.polys.polyroots._integer_basis() is called when a CRootOf
- # is formed, we proactively extract the integer basis now. This means that
- # when we call T.all_roots(), every root will be a CRootOf, not a Mul
- # of Integer*CRootOf.
- coeff, T = preprocess_roots(T)
- coeff = ctx.mpf(str(coeff))
- scaled_roots = T.all_roots(radicals=False)
- # Since we're going to be approximating the roots of T anyway, we can
- # get a good upper bound on the magnitude of the roots by starting with
- # a very low precision approx.
- approx0 = [coeff * quad_to_mpmath(_evalf_with_bounded_error(r, m=0)) for r in scaled_roots]
- # Here we add 1 to account for the possible error in our initial approximation.
- M = max(abs(b) for b in approx0) + 1
- m = self.get_prec(M, target=target)
- n = fastlog(M._mpf_) + 1
- p = m + n + 1
- ctx.prec = p
- d = prec_to_dps(p)
- approx1 = [r.eval_approx(d, return_mpmath=True) for r in scaled_roots]
- approx1 = [coeff*ctx.mpc(r) for r in approx1]
- return approx1
- @staticmethod
- def round_mpf(a):
- if isinstance(a, int):
- return a
- # If we use python's built-in `round()`, we lose precision.
- # If we use `ZZ` directly, we may add or subtract 1.
- return ZZ(a.context.nint(a))
- def round_roots_to_integers_for_poly(self, T):
- """
- For a given polynomial *T*, round the roots of this resolvent to the
- nearest integers.
- Explanation
- ===========
- None of the integers returned by this method is guaranteed to be a
- root of the resolvent; however, if the resolvent has any integer roots
- (for the given polynomial *T*), then they must be among these.
- If the coefficients of the resolvent are also desired, then this method
- should not be used. Instead, use the ``eval_for_poly`` method. This
- method may be significantly faster than ``eval_for_poly``.
- Parameters
- ==========
- T : :py:class:`~.Poly`
- Returns
- =======
- dict
- Keys are the indices of those permutations in ``self.s`` such that
- the corresponding root did round to a rational integer.
- Values are :ref:`ZZ`.
- """
- approx_roots_of_T = self.approximate_roots_of_poly(T, target='roots')
- approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas]
- return {
- i: self.round_mpf(r.real)
- for i, r in enumerate(approx_roots_of_self)
- if self.round_mpf(r.imag) == 0
- }
- def eval_for_poly(self, T, find_integer_root=False):
- r"""
- Compute the integer values of the coefficients of this resolvent, when
- plugging in the roots of a given polynomial.
- Parameters
- ==========
- T : :py:class:`~.Poly`
- find_integer_root : ``bool``, default ``False``
- If ``True``, then also determine whether the resolvent has an
- integer root, and return the first one found, along with its
- index, i.e. the index of the permutation ``self.s[i]`` it
- corresponds to.
- Returns
- =======
- Tuple ``(R, a, i)``
- ``R`` is this resolvent as a dense univariate polynomial over
- :ref:`ZZ`, i.e. a list of :ref:`ZZ`.
- If *find_integer_root* was ``True``, then ``a`` and ``i`` are the
- first integer root found, and its index, if one exists.
- Otherwise ``a`` and ``i`` are both ``None``.
- """
- approx_roots_of_T = self.approximate_roots_of_poly(T, target='coeffs')
- approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas]
- approx_coeffs_of_self = [c(*approx_roots_of_self) for c in self.esf_lambdas]
- R = []
- for c in approx_coeffs_of_self:
- if self.round_mpf(c.imag) != 0:
- # If precision was enough, this should never happen.
- raise ResolventException(f"Got non-integer coeff for resolvent: {c}")
- R.append(self.round_mpf(c.real))
- a0, i0 = None, None
- if find_integer_root:
- for i, r in enumerate(approx_roots_of_self):
- if self.round_mpf(r.imag) != 0:
- continue
- if not dup_eval(R, (a := self.round_mpf(r.real)), ZZ):
- a0, i0 = a, i
- break
- return R, a0, i0
- def wrap(text, width=80):
- """Line wrap a polynomial expression. """
- out = ''
- col = 0
- for c in text:
- if c == ' ' and col > width:
- c, col = '\n', 0
- else:
- col += 1
- out += c
- return out
- def s_vars(n):
- """Form the symbols s1, s2, ..., sn to stand for elem. symm. polys. """
- return symbols([f's{i + 1}' for i in range(n)])
- def sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=False):
- """
- Compute the coefficients of a resolvent as functions of the coefficients of
- the associated polynomial.
- F must be a sparse polynomial.
- """
- import time, sys
- # Roots of resolvent as multivariate forms over vars X:
- root_forms = [
- F.compose(list(zip(X, sigma(X))))
- for sigma in s
- ]
- # Coeffs of resolvent (besides lead coeff of 1) as symmetric forms over vars X:
- Y = [Dummy(f'Y{i}') for i in range(len(s))]
- coeff_forms = []
- for i in range(1, len(s) + 1):
- if verbose:
- print('----')
- print(f'Computing symmetric poly of degree {i}...')
- sys.stdout.flush()
- t0 = time.time()
- G = symmetric_poly(i, *Y)
- t1 = time.time()
- if verbose:
- print(f'took {t1 - t0} seconds')
- print('lambdifying...')
- sys.stdout.flush()
- t0 = time.time()
- C = lambdify(Y, (-1)**i*G)
- t1 = time.time()
- if verbose:
- print(f'took {t1 - t0} seconds')
- sys.stdout.flush()
- coeff_forms.append(C)
- coeffs = []
- for i, f in enumerate(coeff_forms):
- if verbose:
- print('----')
- print(f'Plugging root forms into elem symm poly {i+1}...')
- sys.stdout.flush()
- t0 = time.time()
- g = f(*root_forms)
- t1 = time.time()
- coeffs.append(g)
- if verbose:
- print(f'took {t1 - t0} seconds')
- sys.stdout.flush()
- # Now symmetrize these coeffs. This means recasting them as polynomials in
- # the elementary symmetric polys over X.
- symmetrized = []
- symmetrization_times = []
- ss = s_vars(len(X))
- for i, A in list(enumerate(coeffs)):
- if verbose:
- print('-----')
- print(f'Coeff {i+1}...')
- sys.stdout.flush()
- t0 = time.time()
- B, rem, _ = A.symmetrize()
- t1 = time.time()
- if rem != 0:
- msg = f"Got nonzero remainder {rem} for resolvent (F, X, s) = ({F}, {X}, {s})"
- raise ResolventException(msg)
- B_str = str(B.as_expr(*ss))
- symmetrized.append(B_str)
- symmetrization_times.append(t1 - t0)
- if verbose:
- print(wrap(B_str))
- print(f'took {t1 - t0} seconds')
- sys.stdout.flush()
- return symmetrized, symmetrization_times
- def define_resolvents():
- """Define all the resolvents for polys T of degree 4 through 6. """
- from sympy.combinatorics.galois import PGL2F5
- from sympy.combinatorics.permutations import Permutation
- R4, X4 = xring("X0,X1,X2,X3", ZZ, lex)
- X = X4
- # The one resolvent used in `_galois_group_degree_4_lookup()`:
- F40 = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2
- s40 = [
- Permutation(3),
- Permutation(3)(0, 1),
- Permutation(3)(0, 2),
- Permutation(3)(0, 3),
- Permutation(3)(1, 2),
- Permutation(3)(2, 3),
- ]
- # First resolvent used in `_galois_group_degree_4_root_approx()`:
- F41 = X[0]*X[2] + X[1]*X[3]
- s41 = [
- Permutation(3),
- Permutation(3)(0, 1),
- Permutation(3)(0, 3)
- ]
- R5, X5 = xring("X0,X1,X2,X3,X4", ZZ, lex)
- X = X5
- # First resolvent used in `_galois_group_degree_5_hybrid()`,
- # and only one used in `_galois_group_degree_5_lookup_ext_factor()`:
- F51 = ( X[0]**2*(X[1]*X[4] + X[2]*X[3])
- + X[1]**2*(X[2]*X[0] + X[3]*X[4])
- + X[2]**2*(X[3]*X[1] + X[4]*X[0])
- + X[3]**2*(X[4]*X[2] + X[0]*X[1])
- + X[4]**2*(X[0]*X[3] + X[1]*X[2]))
- s51 = [
- Permutation(4),
- Permutation(4)(0, 1),
- Permutation(4)(0, 2),
- Permutation(4)(0, 3),
- Permutation(4)(0, 4),
- Permutation(4)(1, 4)
- ]
- R6, X6 = xring("X0,X1,X2,X3,X4,X5", ZZ, lex)
- X = X6
- # First resolvent used in `_galois_group_degree_6_lookup()`:
- H = PGL2F5()
- term0 = X[0]**2*X[5]**2*(X[1]*X[4] + X[2]*X[3])
- terms = {term0.compose(list(zip(X, s(X)))) for s in H.elements}
- F61 = sum(terms)
- s61 = [Permutation(5)] + [Permutation(5)(0, n) for n in range(1, 6)]
- # Second resolvent used in `_galois_group_degree_6_lookup()`:
- F62 = X[0]*X[1]*X[2] + X[3]*X[4]*X[5]
- s62 = [Permutation(5)] + [
- Permutation(5)(i, j + 3) for i in range(3) for j in range(3)
- ]
- return {
- (4, 0): (F40, X4, s40),
- (4, 1): (F41, X4, s41),
- (5, 1): (F51, X5, s51),
- (6, 1): (F61, X6, s61),
- (6, 2): (F62, X6, s62),
- }
- def generate_lambda_lookup(verbose=False, trial_run=False):
- """
- Generate the whole lookup table of coeff lambdas, for all resolvents.
- """
- jobs = define_resolvents()
- lambda_lists = {}
- total_time = 0
- time_for_61 = 0
- time_for_61_last = 0
- for k, (F, X, s) in jobs.items():
- symmetrized, times = sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=verbose)
- total_time += sum(times)
- if k == (6, 1):
- time_for_61 = sum(times)
- time_for_61_last = times[-1]
- sv = s_vars(len(X))
- head = f'lambda {", ".join(str(v) for v in sv)}:'
- lambda_lists[k] = ',\n '.join([
- f'{head} ({wrap(f)})'
- for f in symmetrized
- ])
- if trial_run:
- break
- table = (
- "# This table was generated by a call to\n"
- "# `sympy.polys.numberfields.galois_resolvents.generate_lambda_lookup()`.\n"
- f"# The entire job took {total_time:.2f}s.\n"
- f"# Of this, Case (6, 1) took {time_for_61:.2f}s.\n"
- f"# The final polynomial of Case (6, 1) alone took {time_for_61_last:.2f}s.\n"
- "resolvent_coeff_lambdas = {\n")
- for k, L in lambda_lists.items():
- table += f" {k}: [\n"
- table += " " + L + '\n'
- table += " ],\n"
- table += "}\n"
- return table
- def get_resolvent_by_lookup(T, number):
- """
- Use the lookup table, to return a resolvent (as dup) for a given
- polynomial *T*.
- Parameters
- ==========
- T : Poly
- The polynomial whose resolvent is needed
- number : int
- For some degrees, there are multiple resolvents.
- Use this to indicate which one you want.
- Returns
- =======
- dup
- """
- degree = T.degree()
- L = resolvent_coeff_lambdas[(degree, number)]
- T_coeffs = T.rep.rep[1:]
- return [ZZ(1)] + [c(*T_coeffs) for c in L]
- # Use
- # (.venv) $ python -m sympy.polys.numberfields.galois_resolvents
- # to reproduce the table found in resolvent_lookup.py
- if __name__ == "__main__":
- import sys
- verbose = '-v' in sys.argv[1:]
- trial_run = '-t' in sys.argv[1:]
- table = generate_lambda_lookup(verbose=verbose, trial_run=trial_run)
- print(table)
|