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

1r""" 

2Galois resolvents 

3 

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$. 

8 

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$. 

12 

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. 

19 

20""" 

21 

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 

35 

36from mpmath import MPContext 

37from mpmath.libmp.libmpf import prec_to_dps 

38 

39 

40class GaloisGroupException(Exception): 

41 ... 

42 

43 

44class ResolventException(GaloisGroupException): 

45 ... 

46 

47 

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)})$. 

57 

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: 

63 

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) 

72 

73 This resolvent has three roots, which are the conjugates of ``F`` under the 

74 three permutations in ``s``: 

75 

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 

82 

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}$. 

88 

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. 

95 

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``. 

100 

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. 

107 

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. 

111 

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$. 

122 

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$. 

133 

134 Passing from the general monomial to the general polynomial is easy, by 

135 scaling and summing error bounds. 

136 

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). 

142 

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. 

161 

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. 

169 

170 Note: We take the definition of resolvent from Cohen, but the error bound 

171 is ours. 

172 

173 References 

174 ========== 

175 

176 .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*. 

177 (Def 6.3.2) 

178 

179 """ 

180 

181 def __init__(self, F, X, s): 

182 r""" 

183 Parameters 

184 ========== 

185 

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 

196 

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 

203 

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 

212 

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)) 

222 

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 ] 

232 

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] 

242 

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. 

249 

250 Parameters 

251 ========== 

252 

253 M : real number 

254 Upper bound on magnitude of the complex numbers to be plugged in. 

255 

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. 

261 

262 Returns 

263 ======= 

264 

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. 

268 

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 

276 

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. 

282 

283 Parameters 

284 ========== 

285 

286 T : :py:class:`~.Poly` 

287 

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. 

293 

294 Returns 

295 ======= 

296 

297 list of elements of :ref:`CC` 

298 

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)) 

307 

308 scaled_roots = T.all_roots(radicals=False) 

309 

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) 

321 

322 approx1 = [r.eval_approx(d, return_mpmath=True) for r in scaled_roots] 

323 approx1 = [coeff*ctx.mpc(r) for r in approx1] 

324 

325 return approx1 

326 

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)) 

334 

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. 

339 

340 Explanation 

341 =========== 

342 

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. 

346 

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``. 

350 

351 Parameters 

352 ========== 

353 

354 T : :py:class:`~.Poly` 

355 

356 Returns 

357 ======= 

358 

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. 

362 

363 Values are :ref:`ZZ`. 

364 

365 

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 } 

374 

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. 

379 

380 Parameters 

381 ========== 

382 

383 T : :py:class:`~.Poly` 

384 

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. 

390 

391 Returns 

392 ======= 

393 

394 Tuple ``(R, a, i)`` 

395 

396 ``R`` is this resolvent as a dense univariate polynomial over 

397 :ref:`ZZ`, i.e. a list of :ref:`ZZ`. 

398 

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``. 

402 

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] 

407 

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)) 

414 

415 a0, i0 = None, None 

416 

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 

424 

425 return R, a0, i0 

426 

427 

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 

439 

440 

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)]) 

444 

445 

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. 

450 

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 ] 

459 

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) 

482 

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() 

496 

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() 

520 

521 return symmetrized, symmetrization_times 

522 

523 

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 

528 

529 R4, X4 = xring("X0,X1,X2,X3", ZZ, lex) 

530 X = X4 

531 

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 ] 

542 

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 ] 

550 

551 R5, X5 = xring("X0,X1,X2,X3,X4", ZZ, lex) 

552 X = X5 

553 

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 ] 

569 

570 R6, X6 = xring("X0,X1,X2,X3,X4,X5", ZZ, lex) 

571 X = X6 

572 

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)] 

579 

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 ] 

585 

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 } 

593 

594 

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) 

606 

607 total_time += sum(times) 

608 if k == (6, 1): 

609 time_for_61 = sum(times) 

610 time_for_61_last = times[-1] 

611 

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 ]) 

618 

619 if trial_run: 

620 break 

621 

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") 

629 

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 

636 

637 

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*. 

642 

643 Parameters 

644 ========== 

645 

646 T : Poly 

647 The polynomial whose resolvent is needed 

648 

649 number : int 

650 For some degrees, there are multiple resolvents. 

651 Use this to indicate which one you want. 

652 

653 Returns 

654 ======= 

655 

656 dup 

657 

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] 

663 

664 

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)