Coverage for /usr/lib/python3/dist-packages/sympy/polys/numberfields/galois_resolvents.py: 14%
222 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1r"""
2Galois resolvents
4Each of the functions in ``sympy.polys.numberfields.galoisgroups`` that
5computes Galois groups for a particular degree $n$ uses resolvents. Given the
6polynomial $T$ whose Galois group is to be computed, a resolvent is a
7polynomial $R$ whose roots are defined as functions of the roots of $T$.
9One way to compute the coefficients of $R$ is by approximating the roots of $T$
10to sufficient precision. This module defines a :py:class:`~.Resolvent` class
11that handles this job, determining the necessary precision, and computing $R$.
13In some cases, the coefficients of $R$ are symmetric in the roots of $T$,
14meaning they are equal to fixed functions of the coefficients of $T$. Therefore
15another approach is to compute these functions once and for all, and record
16them in a lookup table. This module defines code that can compute such tables.
17The tables for polynomials $T$ of degrees 4 through 6, produced by this code,
18are recorded in the resolvent_lookup.py module.
20"""
22from sympy.core.evalf import (
23 evalf, fastlog, _evalf_with_bounded_error, quad_to_mpmath,
24)
25from sympy.core.symbol import symbols, Dummy
26from sympy.polys.densetools import dup_eval
27from sympy.polys.domains import ZZ
28from sympy.polys.numberfields.resolvent_lookup import resolvent_coeff_lambdas
29from sympy.polys.orderings import lex
30from sympy.polys.polyroots import preprocess_roots
31from sympy.polys.polytools import Poly
32from sympy.polys.rings import xring
33from sympy.polys.specialpolys import symmetric_poly
34from sympy.utilities.lambdify import lambdify
36from mpmath import MPContext
37from mpmath.libmp.libmpf import prec_to_dps
40class GaloisGroupException(Exception):
41 ...
44class ResolventException(GaloisGroupException):
45 ...
48class Resolvent:
49 r"""
50 If $G$ is a subgroup of the symmetric group $S_n$,
51 $F$ a multivariate polynomial in $\mathbb{Z}[X_1, \ldots, X_n]$,
52 $H$ the stabilizer of $F$ in $G$ (i.e. the permutations $\sigma$ such that
53 $F(X_{\sigma(1)}, \ldots, X_{\sigma(n)}) = F(X_1, \ldots, X_n)$), and $s$
54 a set of left coset representatives of $H$ in $G$, then the resolvent
55 polynomial $R(Y)$ is the product over $\sigma \in s$ of
56 $Y - F(X_{\sigma(1)}, \ldots, X_{\sigma(n)})$.
58 For example, consider the resolvent for the form
59 $$F = X_0 X_2 + X_1 X_3$$
60 and the group $G = S_4$. In this case, the stabilizer $H$ is the dihedral
61 group $D4 = < (0123), (02) >$, and a set of representatives of $G/H$ is
62 $\{I, (01), (03)\}$. The resolvent can be constructed as follows:
64 >>> from sympy.combinatorics.permutations import Permutation
65 >>> from sympy.core.symbol import symbols
66 >>> from sympy.polys.numberfields.galoisgroups import Resolvent
67 >>> X = symbols('X0 X1 X2 X3')
68 >>> F = X[0]*X[2] + X[1]*X[3]
69 >>> s = [Permutation([0, 1, 2, 3]), Permutation([1, 0, 2, 3]),
70 ... Permutation([3, 1, 2, 0])]
71 >>> R = Resolvent(F, X, s)
73 This resolvent has three roots, which are the conjugates of ``F`` under the
74 three permutations in ``s``:
76 >>> R.root_lambdas[0](*X)
77 X0*X2 + X1*X3
78 >>> R.root_lambdas[1](*X)
79 X0*X3 + X1*X2
80 >>> R.root_lambdas[2](*X)
81 X0*X1 + X2*X3
83 Resolvents are useful for computing Galois groups. Given a polynomial $T$
84 of degree $n$, we will use a resolvent $R$ where $Gal(T) \leq G \leq S_n$.
85 We will then want to substitute the roots of $T$ for the variables $X_i$
86 in $R$, and study things like the discriminant of $R$, and the way $R$
87 factors over $\mathbb{Q}$.
89 From the symmetry in $R$'s construction, and since $Gal(T) \leq G$, we know
90 from Galois theory that the coefficients of $R$ must lie in $\mathbb{Z}$.
91 This allows us to compute the coefficients of $R$ by approximating the
92 roots of $T$ to sufficient precision, plugging these values in for the
93 variables $X_i$ in the coefficient expressions of $R$, and then simply
94 rounding to the nearest integer.
96 In order to determine a sufficient precision for the roots of $T$, this
97 ``Resolvent`` class imposes certain requirements on the form ``F``. It
98 could be possible to design a different ``Resolvent`` class, that made
99 different precision estimates, and different assumptions about ``F``.
101 ``F`` must be homogeneous, and all terms must have unit coefficient.
102 Furthermore, if $r$ is the number of terms in ``F``, and $t$ the total
103 degree, and if $m$ is the number of conjugates of ``F``, i.e. the number
104 of permutations in ``s``, then we require that $m < r 2^t$. Again, it is
105 not impossible to work with forms ``F`` that violate these assumptions, but
106 this ``Resolvent`` class requires them.
108 Since determining the integer coefficients of the resolvent for a given
109 polynomial $T$ is one of the main problems this class solves, we take some
110 time to explain the precision bounds it uses.
112 The general problem is:
113 Given a multivariate polynomial $P \in \mathbb{Z}[X_1, \ldots, X_n]$, and a
114 bound $M \in \mathbb{R}_+$, compute an $\varepsilon > 0$ such that for any
115 complex numbers $a_1, \ldots, a_n$ with $|a_i| < M$, if the $a_i$ are
116 approximated to within an accuracy of $\varepsilon$ by $b_i$, that is,
117 $|a_i - b_i| < \varepsilon$ for $i = 1, \ldots, n$, then
118 $|P(a_1, \ldots, a_n) - P(b_1, \ldots, b_n)| < 1/2$. In other words, if it
119 is known that $P(a_1, \ldots, a_n) = c$ for some $c \in \mathbb{Z}$, then
120 $P(b_1, \ldots, b_n)$ can be rounded to the nearest integer in order to
121 determine $c$.
123 To derive our error bound, consider the monomial $xyz$. Defining
124 $d_i = b_i - a_i$, our error is
125 $|(a_1 + d_1)(a_2 + d_2)(a_3 + d_3) - a_1 a_2 a_3|$, which is bounded
126 above by $|(M + \varepsilon)^3 - M^3|$. Passing to a general monomial of
127 total degree $t$, this expression is bounded by
128 $M^{t-1}\varepsilon(t + 2^t\varepsilon/M)$ provided $\varepsilon < M$,
129 and by $(t+1)M^{t-1}\varepsilon$ provided $\varepsilon < M/2^t$.
130 But since our goal is to make the error less than $1/2$, we will choose
131 $\varepsilon < 1/(2(t+1)M^{t-1})$, which implies the condition that
132 $\varepsilon < M/2^t$, as long as $M \geq 2$.
134 Passing from the general monomial to the general polynomial is easy, by
135 scaling and summing error bounds.
137 In our specific case, we are given a homogeneous polynomial $F$ of
138 $r$ terms and total degree $t$, all of whose coefficients are $\pm 1$. We
139 are given the $m$ permutations that make the conjugates of $F$, and
140 we want to bound the error in the coefficients of the monic polynomial
141 $R(Y)$ having $F$ and its conjugates as roots (i.e. the resolvent).
143 For $j$ from $1$ to $m$, the coefficient of $Y^{m-j}$ in $R(Y)$ is the
144 $j$th elementary symmetric polynomial in the conjugates of $F$. This sums
145 the products of these conjugates, taken $j$ at a time, in all possible
146 combinations. There are $\binom{m}{j}$ such combinations, and each product
147 of $j$ conjugates of $F$ expands to a sum of $r^j$ terms, each of unit
148 coefficient, and total degree $jt$. An error bound for the $j$th coeff of
149 $R$ is therefore
150 $$\binom{m}{j} r^j (jt + 1) M^{jt - 1} \varepsilon$$
151 When our goal is to evaluate all the coefficients of $R$, we will want to
152 use the maximum of these error bounds. It is clear that this bound is
153 strictly increasing for $j$ up to the ceiling of $m/2$. After that point,
154 the first factor $\binom{m}{j}$ begins to decrease, while the others
155 continue to increase. However, the binomial coefficient never falls by more
156 than a factor of $1/m$ at a time, so our assumptions that $M \geq 2$ and
157 $m < r 2^t$ are enough to tell us that the constant coefficient of $R$,
158 i.e. that where $j = m$, has the largest error bound. Therefore we can use
159 $$r^m (mt + 1) M^{mt - 1} \varepsilon$$
160 as our error bound for all the coefficients.
162 Note that this bound is also (more than) adequate to determine whether any
163 of the roots of $R$ is an integer. Each of these roots is a single
164 conjugate of $F$, which contains less error than the trace, i.e. the
165 coefficient of $Y^{m - 1}$. By rounding the roots of $R$ to the nearest
166 integers, we therefore get all the candidates for integer roots of $R$. By
167 plugging these candidates into $R$, we can check whether any of them
168 actually is a root.
170 Note: We take the definition of resolvent from Cohen, but the error bound
171 is ours.
173 References
174 ==========
176 .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
177 (Def 6.3.2)
179 """
181 def __init__(self, F, X, s):
182 r"""
183 Parameters
184 ==========
186 F : :py:class:`~.Expr`
187 polynomial in the symbols in *X*
188 X : list of :py:class:`~.Symbol`
189 s : list of :py:class:`~.Permutation`
190 representing the cosets of the stabilizer of *F* in
191 some subgroup $G$ of $S_n$, where $n$ is the length of *X*.
192 """
193 self.F = F
194 self.X = X
195 self.s = s
197 # Number of conjugates:
198 self.m = len(s)
199 # Total degree of F (computed below):
200 self.t = None
201 # Number of terms in F (computed below):
202 self.r = 0
204 for monom, coeff in Poly(F).terms():
205 if abs(coeff) != 1:
206 raise ResolventException('Resolvent class expects forms with unit coeffs')
207 t = sum(monom)
208 if t != self.t and self.t is not None:
209 raise ResolventException('Resolvent class expects homogeneous forms')
210 self.t = t
211 self.r += 1
213 m, t, r = self.m, self.t, self.r
214 if not m < r * 2**t:
215 raise ResolventException('Resolvent class expects m < r*2^t')
216 M = symbols('M')
217 # Precision sufficient for computing the coeffs of the resolvent:
218 self.coeff_prec_func = Poly(r**m*(m*t + 1)*M**(m*t - 1))
219 # Precision sufficient for checking whether any of the roots of the
220 # resolvent are integers:
221 self.root_prec_func = Poly(r*(t + 1)*M**(t - 1))
223 # The conjugates of F are the roots of the resolvent.
224 # For evaluating these to required numerical precisions, we need
225 # lambdified versions.
226 # Note: for a given permutation sigma, the conjugate (sigma F) is
227 # equivalent to lambda [sigma^(-1) X]: F.
228 self.root_lambdas = [
229 lambdify((~s[j])(X), F)
230 for j in range(self.m)
231 ]
233 # For evaluating the coeffs, we'll also need lambdified versions of
234 # the elementary symmetric functions for degree m.
235 Y = symbols('Y')
236 R = symbols(' '.join(f'R{i}' for i in range(m)))
237 f = 1
238 for r in R:
239 f *= (Y - r)
240 C = Poly(f, Y).coeffs()
241 self.esf_lambdas = [lambdify(R, c) for c in C]
243 def get_prec(self, M, target='coeffs'):
244 r"""
245 For a given upper bound *M* on the magnitude of the complex numbers to
246 be plugged in for this resolvent's symbols, compute a sufficient
247 precision for evaluating those complex numbers, such that the
248 coefficients, or the integer roots, of the resolvent can be determined.
250 Parameters
251 ==========
253 M : real number
254 Upper bound on magnitude of the complex numbers to be plugged in.
256 target : str, 'coeffs' or 'roots', default='coeffs'
257 Name the task for which a sufficient precision is desired.
258 This is either determining the coefficients of the resolvent
259 ('coeffs') or determining its possible integer roots ('roots').
260 The latter may require significantly lower precision.
262 Returns
263 =======
265 int $m$
266 such that $2^{-m}$ is a sufficient upper bound on the
267 error in approximating the complex numbers to be plugged in.
269 """
270 # As explained in the docstring for this class, our precision estimates
271 # require that M be at least 2.
272 M = max(M, 2)
273 f = self.coeff_prec_func if target == 'coeffs' else self.root_prec_func
274 r, _, _, _ = evalf(2*f(M), 1, {})
275 return fastlog(r) + 1
277 def approximate_roots_of_poly(self, T, target='coeffs'):
278 """
279 Approximate the roots of a given polynomial *T* to sufficient precision
280 in order to evaluate this resolvent's coefficients, or determine
281 whether the resolvent has an integer root.
283 Parameters
284 ==========
286 T : :py:class:`~.Poly`
288 target : str, 'coeffs' or 'roots', default='coeffs'
289 Set the approximation precision to be sufficient for the desired
290 task, which is either determining the coefficients of the resolvent
291 ('coeffs') or determining its possible integer roots ('roots').
292 The latter may require significantly lower precision.
294 Returns
295 =======
297 list of elements of :ref:`CC`
299 """
300 ctx = MPContext()
301 # Because sympy.polys.polyroots._integer_basis() is called when a CRootOf
302 # is formed, we proactively extract the integer basis now. This means that
303 # when we call T.all_roots(), every root will be a CRootOf, not a Mul
304 # of Integer*CRootOf.
305 coeff, T = preprocess_roots(T)
306 coeff = ctx.mpf(str(coeff))
308 scaled_roots = T.all_roots(radicals=False)
310 # Since we're going to be approximating the roots of T anyway, we can
311 # get a good upper bound on the magnitude of the roots by starting with
312 # a very low precision approx.
313 approx0 = [coeff * quad_to_mpmath(_evalf_with_bounded_error(r, m=0)) for r in scaled_roots]
314 # Here we add 1 to account for the possible error in our initial approximation.
315 M = max(abs(b) for b in approx0) + 1
316 m = self.get_prec(M, target=target)
317 n = fastlog(M._mpf_) + 1
318 p = m + n + 1
319 ctx.prec = p
320 d = prec_to_dps(p)
322 approx1 = [r.eval_approx(d, return_mpmath=True) for r in scaled_roots]
323 approx1 = [coeff*ctx.mpc(r) for r in approx1]
325 return approx1
327 @staticmethod
328 def round_mpf(a):
329 if isinstance(a, int):
330 return a
331 # If we use python's built-in `round()`, we lose precision.
332 # If we use `ZZ` directly, we may add or subtract 1.
333 return ZZ(a.context.nint(a))
335 def round_roots_to_integers_for_poly(self, T):
336 """
337 For a given polynomial *T*, round the roots of this resolvent to the
338 nearest integers.
340 Explanation
341 ===========
343 None of the integers returned by this method is guaranteed to be a
344 root of the resolvent; however, if the resolvent has any integer roots
345 (for the given polynomial *T*), then they must be among these.
347 If the coefficients of the resolvent are also desired, then this method
348 should not be used. Instead, use the ``eval_for_poly`` method. This
349 method may be significantly faster than ``eval_for_poly``.
351 Parameters
352 ==========
354 T : :py:class:`~.Poly`
356 Returns
357 =======
359 dict
360 Keys are the indices of those permutations in ``self.s`` such that
361 the corresponding root did round to a rational integer.
363 Values are :ref:`ZZ`.
366 """
367 approx_roots_of_T = self.approximate_roots_of_poly(T, target='roots')
368 approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas]
369 return {
370 i: self.round_mpf(r.real)
371 for i, r in enumerate(approx_roots_of_self)
372 if self.round_mpf(r.imag) == 0
373 }
375 def eval_for_poly(self, T, find_integer_root=False):
376 r"""
377 Compute the integer values of the coefficients of this resolvent, when
378 plugging in the roots of a given polynomial.
380 Parameters
381 ==========
383 T : :py:class:`~.Poly`
385 find_integer_root : ``bool``, default ``False``
386 If ``True``, then also determine whether the resolvent has an
387 integer root, and return the first one found, along with its
388 index, i.e. the index of the permutation ``self.s[i]`` it
389 corresponds to.
391 Returns
392 =======
394 Tuple ``(R, a, i)``
396 ``R`` is this resolvent as a dense univariate polynomial over
397 :ref:`ZZ`, i.e. a list of :ref:`ZZ`.
399 If *find_integer_root* was ``True``, then ``a`` and ``i`` are the
400 first integer root found, and its index, if one exists.
401 Otherwise ``a`` and ``i`` are both ``None``.
403 """
404 approx_roots_of_T = self.approximate_roots_of_poly(T, target='coeffs')
405 approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas]
406 approx_coeffs_of_self = [c(*approx_roots_of_self) for c in self.esf_lambdas]
408 R = []
409 for c in approx_coeffs_of_self:
410 if self.round_mpf(c.imag) != 0:
411 # If precision was enough, this should never happen.
412 raise ResolventException(f"Got non-integer coeff for resolvent: {c}")
413 R.append(self.round_mpf(c.real))
415 a0, i0 = None, None
417 if find_integer_root:
418 for i, r in enumerate(approx_roots_of_self):
419 if self.round_mpf(r.imag) != 0:
420 continue
421 if not dup_eval(R, (a := self.round_mpf(r.real)), ZZ):
422 a0, i0 = a, i
423 break
425 return R, a0, i0
428def wrap(text, width=80):
429 """Line wrap a polynomial expression. """
430 out = ''
431 col = 0
432 for c in text:
433 if c == ' ' and col > width:
434 c, col = '\n', 0
435 else:
436 col += 1
437 out += c
438 return out
441def s_vars(n):
442 """Form the symbols s1, s2, ..., sn to stand for elem. symm. polys. """
443 return symbols([f's{i + 1}' for i in range(n)])
446def sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=False):
447 """
448 Compute the coefficients of a resolvent as functions of the coefficients of
449 the associated polynomial.
451 F must be a sparse polynomial.
452 """
453 import time, sys
454 # Roots of resolvent as multivariate forms over vars X:
455 root_forms = [
456 F.compose(list(zip(X, sigma(X))))
457 for sigma in s
458 ]
460 # Coeffs of resolvent (besides lead coeff of 1) as symmetric forms over vars X:
461 Y = [Dummy(f'Y{i}') for i in range(len(s))]
462 coeff_forms = []
463 for i in range(1, len(s) + 1):
464 if verbose:
465 print('----')
466 print(f'Computing symmetric poly of degree {i}...')
467 sys.stdout.flush()
468 t0 = time.time()
469 G = symmetric_poly(i, *Y)
470 t1 = time.time()
471 if verbose:
472 print(f'took {t1 - t0} seconds')
473 print('lambdifying...')
474 sys.stdout.flush()
475 t0 = time.time()
476 C = lambdify(Y, (-1)**i*G)
477 t1 = time.time()
478 if verbose:
479 print(f'took {t1 - t0} seconds')
480 sys.stdout.flush()
481 coeff_forms.append(C)
483 coeffs = []
484 for i, f in enumerate(coeff_forms):
485 if verbose:
486 print('----')
487 print(f'Plugging root forms into elem symm poly {i+1}...')
488 sys.stdout.flush()
489 t0 = time.time()
490 g = f(*root_forms)
491 t1 = time.time()
492 coeffs.append(g)
493 if verbose:
494 print(f'took {t1 - t0} seconds')
495 sys.stdout.flush()
497 # Now symmetrize these coeffs. This means recasting them as polynomials in
498 # the elementary symmetric polys over X.
499 symmetrized = []
500 symmetrization_times = []
501 ss = s_vars(len(X))
502 for i, A in list(enumerate(coeffs)):
503 if verbose:
504 print('-----')
505 print(f'Coeff {i+1}...')
506 sys.stdout.flush()
507 t0 = time.time()
508 B, rem, _ = A.symmetrize()
509 t1 = time.time()
510 if rem != 0:
511 msg = f"Got nonzero remainder {rem} for resolvent (F, X, s) = ({F}, {X}, {s})"
512 raise ResolventException(msg)
513 B_str = str(B.as_expr(*ss))
514 symmetrized.append(B_str)
515 symmetrization_times.append(t1 - t0)
516 if verbose:
517 print(wrap(B_str))
518 print(f'took {t1 - t0} seconds')
519 sys.stdout.flush()
521 return symmetrized, symmetrization_times
524def define_resolvents():
525 """Define all the resolvents for polys T of degree 4 through 6. """
526 from sympy.combinatorics.galois import PGL2F5
527 from sympy.combinatorics.permutations import Permutation
529 R4, X4 = xring("X0,X1,X2,X3", ZZ, lex)
530 X = X4
532 # The one resolvent used in `_galois_group_degree_4_lookup()`:
533 F40 = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2
534 s40 = [
535 Permutation(3),
536 Permutation(3)(0, 1),
537 Permutation(3)(0, 2),
538 Permutation(3)(0, 3),
539 Permutation(3)(1, 2),
540 Permutation(3)(2, 3),
541 ]
543 # First resolvent used in `_galois_group_degree_4_root_approx()`:
544 F41 = X[0]*X[2] + X[1]*X[3]
545 s41 = [
546 Permutation(3),
547 Permutation(3)(0, 1),
548 Permutation(3)(0, 3)
549 ]
551 R5, X5 = xring("X0,X1,X2,X3,X4", ZZ, lex)
552 X = X5
554 # First resolvent used in `_galois_group_degree_5_hybrid()`,
555 # and only one used in `_galois_group_degree_5_lookup_ext_factor()`:
556 F51 = ( X[0]**2*(X[1]*X[4] + X[2]*X[3])
557 + X[1]**2*(X[2]*X[0] + X[3]*X[4])
558 + X[2]**2*(X[3]*X[1] + X[4]*X[0])
559 + X[3]**2*(X[4]*X[2] + X[0]*X[1])
560 + X[4]**2*(X[0]*X[3] + X[1]*X[2]))
561 s51 = [
562 Permutation(4),
563 Permutation(4)(0, 1),
564 Permutation(4)(0, 2),
565 Permutation(4)(0, 3),
566 Permutation(4)(0, 4),
567 Permutation(4)(1, 4)
568 ]
570 R6, X6 = xring("X0,X1,X2,X3,X4,X5", ZZ, lex)
571 X = X6
573 # First resolvent used in `_galois_group_degree_6_lookup()`:
574 H = PGL2F5()
575 term0 = X[0]**2*X[5]**2*(X[1]*X[4] + X[2]*X[3])
576 terms = {term0.compose(list(zip(X, s(X)))) for s in H.elements}
577 F61 = sum(terms)
578 s61 = [Permutation(5)] + [Permutation(5)(0, n) for n in range(1, 6)]
580 # Second resolvent used in `_galois_group_degree_6_lookup()`:
581 F62 = X[0]*X[1]*X[2] + X[3]*X[4]*X[5]
582 s62 = [Permutation(5)] + [
583 Permutation(5)(i, j + 3) for i in range(3) for j in range(3)
584 ]
586 return {
587 (4, 0): (F40, X4, s40),
588 (4, 1): (F41, X4, s41),
589 (5, 1): (F51, X5, s51),
590 (6, 1): (F61, X6, s61),
591 (6, 2): (F62, X6, s62),
592 }
595def generate_lambda_lookup(verbose=False, trial_run=False):
596 """
597 Generate the whole lookup table of coeff lambdas, for all resolvents.
598 """
599 jobs = define_resolvents()
600 lambda_lists = {}
601 total_time = 0
602 time_for_61 = 0
603 time_for_61_last = 0
604 for k, (F, X, s) in jobs.items():
605 symmetrized, times = sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=verbose)
607 total_time += sum(times)
608 if k == (6, 1):
609 time_for_61 = sum(times)
610 time_for_61_last = times[-1]
612 sv = s_vars(len(X))
613 head = f'lambda {", ".join(str(v) for v in sv)}:'
614 lambda_lists[k] = ',\n '.join([
615 f'{head} ({wrap(f)})'
616 for f in symmetrized
617 ])
619 if trial_run:
620 break
622 table = (
623 "# This table was generated by a call to\n"
624 "# `sympy.polys.numberfields.galois_resolvents.generate_lambda_lookup()`.\n"
625 f"# The entire job took {total_time:.2f}s.\n"
626 f"# Of this, Case (6, 1) took {time_for_61:.2f}s.\n"
627 f"# The final polynomial of Case (6, 1) alone took {time_for_61_last:.2f}s.\n"
628 "resolvent_coeff_lambdas = {\n")
630 for k, L in lambda_lists.items():
631 table += f" {k}: [\n"
632 table += " " + L + '\n'
633 table += " ],\n"
634 table += "}\n"
635 return table
638def get_resolvent_by_lookup(T, number):
639 """
640 Use the lookup table, to return a resolvent (as dup) for a given
641 polynomial *T*.
643 Parameters
644 ==========
646 T : Poly
647 The polynomial whose resolvent is needed
649 number : int
650 For some degrees, there are multiple resolvents.
651 Use this to indicate which one you want.
653 Returns
654 =======
656 dup
658 """
659 degree = T.degree()
660 L = resolvent_coeff_lambdas[(degree, number)]
661 T_coeffs = T.rep.rep[1:]
662 return [ZZ(1)] + [c(*T_coeffs) for c in L]
665# Use
666# (.venv) $ python -m sympy.polys.numberfields.galois_resolvents
667# to reproduce the table found in resolvent_lookup.py
668if __name__ == "__main__":
669 import sys
670 verbose = '-v' in sys.argv[1:]
671 trial_run = '-t' in sys.argv[1:]
672 table = generate_lambda_lookup(verbose=verbose, trial_run=trial_run)
673 print(table)