Coverage for /usr/lib/python3/dist-packages/sympy/polys/domains/algebraicfield.py: 33%

148 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Implementation of :class:`AlgebraicField` class. """ 

2 

3 

4from sympy.core.add import Add 

5from sympy.core.mul import Mul 

6from sympy.core.singleton import S 

7from sympy.polys.domains.characteristiczero import CharacteristicZero 

8from sympy.polys.domains.field import Field 

9from sympy.polys.domains.simpledomain import SimpleDomain 

10from sympy.polys.polyclasses import ANP 

11from sympy.polys.polyerrors import CoercionFailed, DomainError, NotAlgebraic, IsomorphismFailed 

12from sympy.utilities import public 

13 

14@public 

15class AlgebraicField(Field, CharacteristicZero, SimpleDomain): 

16 r"""Algebraic number field :ref:`QQ(a)` 

17 

18 A :ref:`QQ(a)` domain represents an `algebraic number field`_ 

19 `\mathbb{Q}(a)` as a :py:class:`~.Domain` in the domain system (see 

20 :ref:`polys-domainsintro`). 

21 

22 A :py:class:`~.Poly` created from an expression involving `algebraic 

23 numbers`_ will treat the algebraic numbers as generators if the generators 

24 argument is not specified. 

25 

26 >>> from sympy import Poly, Symbol, sqrt 

27 >>> x = Symbol('x') 

28 >>> Poly(x**2 + sqrt(2)) 

29 Poly(x**2 + (sqrt(2)), x, sqrt(2), domain='ZZ') 

30 

31 That is a multivariate polynomial with ``sqrt(2)`` treated as one of the 

32 generators (variables). If the generators are explicitly specified then 

33 ``sqrt(2)`` will be considered to be a coefficient but by default the 

34 :ref:`EX` domain is used. To make a :py:class:`~.Poly` with a :ref:`QQ(a)` 

35 domain the argument ``extension=True`` can be given. 

36 

37 >>> Poly(x**2 + sqrt(2), x) 

38 Poly(x**2 + sqrt(2), x, domain='EX') 

39 >>> Poly(x**2 + sqrt(2), x, extension=True) 

40 Poly(x**2 + sqrt(2), x, domain='QQ<sqrt(2)>') 

41 

42 A generator of the algebraic field extension can also be specified 

43 explicitly which is particularly useful if the coefficients are all 

44 rational but an extension field is needed (e.g. to factor the 

45 polynomial). 

46 

47 >>> Poly(x**2 + 1) 

48 Poly(x**2 + 1, x, domain='ZZ') 

49 >>> Poly(x**2 + 1, extension=sqrt(2)) 

50 Poly(x**2 + 1, x, domain='QQ<sqrt(2)>') 

51 

52 It is possible to factorise a polynomial over a :ref:`QQ(a)` domain using 

53 the ``extension`` argument to :py:func:`~.factor` or by specifying the domain 

54 explicitly. 

55 

56 >>> from sympy import factor, QQ 

57 >>> factor(x**2 - 2) 

58 x**2 - 2 

59 >>> factor(x**2 - 2, extension=sqrt(2)) 

60 (x - sqrt(2))*(x + sqrt(2)) 

61 >>> factor(x**2 - 2, domain='QQ<sqrt(2)>') 

62 (x - sqrt(2))*(x + sqrt(2)) 

63 >>> factor(x**2 - 2, domain=QQ.algebraic_field(sqrt(2))) 

64 (x - sqrt(2))*(x + sqrt(2)) 

65 

66 The ``extension=True`` argument can be used but will only create an 

67 extension that contains the coefficients which is usually not enough to 

68 factorise the polynomial. 

69 

70 >>> p = x**3 + sqrt(2)*x**2 - 2*x - 2*sqrt(2) 

71 >>> factor(p) # treats sqrt(2) as a symbol 

72 (x + sqrt(2))*(x**2 - 2) 

73 >>> factor(p, extension=True) 

74 (x - sqrt(2))*(x + sqrt(2))**2 

75 >>> factor(x**2 - 2, extension=True) # all rational coefficients 

76 x**2 - 2 

77 

78 It is also possible to use :ref:`QQ(a)` with the :py:func:`~.cancel` 

79 and :py:func:`~.gcd` functions. 

80 

81 >>> from sympy import cancel, gcd 

82 >>> cancel((x**2 - 2)/(x - sqrt(2))) 

83 (x**2 - 2)/(x - sqrt(2)) 

84 >>> cancel((x**2 - 2)/(x - sqrt(2)), extension=sqrt(2)) 

85 x + sqrt(2) 

86 >>> gcd(x**2 - 2, x - sqrt(2)) 

87 1 

88 >>> gcd(x**2 - 2, x - sqrt(2), extension=sqrt(2)) 

89 x - sqrt(2) 

90 

91 When using the domain directly :ref:`QQ(a)` can be used as a constructor 

92 to create instances which then support the operations ``+,-,*,**,/``. The 

93 :py:meth:`~.Domain.algebraic_field` method is used to construct a 

94 particular :ref:`QQ(a)` domain. The :py:meth:`~.Domain.from_sympy` method 

95 can be used to create domain elements from normal SymPy expressions. 

96 

97 >>> K = QQ.algebraic_field(sqrt(2)) 

98 >>> K 

99 QQ<sqrt(2)> 

100 >>> xk = K.from_sympy(3 + 4*sqrt(2)) 

101 >>> xk # doctest: +SKIP 

102 ANP([4, 3], [1, 0, -2], QQ) 

103 

104 Elements of :ref:`QQ(a)` are instances of :py:class:`~.ANP` which have 

105 limited printing support. The raw display shows the internal 

106 representation of the element as the list ``[4, 3]`` representing the 

107 coefficients of ``1`` and ``sqrt(2)`` for this element in the form 

108 ``a * sqrt(2) + b * 1`` where ``a`` and ``b`` are elements of :ref:`QQ`. 

109 The minimal polynomial for the generator ``(x**2 - 2)`` is also shown in 

110 the :ref:`dup-representation` as the list ``[1, 0, -2]``. We can use 

111 :py:meth:`~.Domain.to_sympy` to get a better printed form for the 

112 elements and to see the results of operations. 

113 

114 >>> xk = K.from_sympy(3 + 4*sqrt(2)) 

115 >>> yk = K.from_sympy(2 + 3*sqrt(2)) 

116 >>> xk * yk # doctest: +SKIP 

117 ANP([17, 30], [1, 0, -2], QQ) 

118 >>> K.to_sympy(xk * yk) 

119 17*sqrt(2) + 30 

120 >>> K.to_sympy(xk + yk) 

121 5 + 7*sqrt(2) 

122 >>> K.to_sympy(xk ** 2) 

123 24*sqrt(2) + 41 

124 >>> K.to_sympy(xk / yk) 

125 sqrt(2)/14 + 9/7 

126 

127 Any expression representing an algebraic number can be used to generate 

128 a :ref:`QQ(a)` domain provided its `minimal polynomial`_ can be computed. 

129 The function :py:func:`~.minpoly` function is used for this. 

130 

131 >>> from sympy import exp, I, pi, minpoly 

132 >>> g = exp(2*I*pi/3) 

133 >>> g 

134 exp(2*I*pi/3) 

135 >>> g.is_algebraic 

136 True 

137 >>> minpoly(g, x) 

138 x**2 + x + 1 

139 >>> factor(x**3 - 1, extension=g) 

140 (x - 1)*(x - exp(2*I*pi/3))*(x + 1 + exp(2*I*pi/3)) 

141 

142 It is also possible to make an algebraic field from multiple extension 

143 elements. 

144 

145 >>> K = QQ.algebraic_field(sqrt(2), sqrt(3)) 

146 >>> K 

147 QQ<sqrt(2) + sqrt(3)> 

148 >>> p = x**4 - 5*x**2 + 6 

149 >>> factor(p) 

150 (x**2 - 3)*(x**2 - 2) 

151 >>> factor(p, domain=K) 

152 (x - sqrt(2))*(x + sqrt(2))*(x - sqrt(3))*(x + sqrt(3)) 

153 >>> factor(p, extension=[sqrt(2), sqrt(3)]) 

154 (x - sqrt(2))*(x + sqrt(2))*(x - sqrt(3))*(x + sqrt(3)) 

155 

156 Multiple extension elements are always combined together to make a single 

157 `primitive element`_. In the case of ``[sqrt(2), sqrt(3)]`` the primitive 

158 element chosen is ``sqrt(2) + sqrt(3)`` which is why the domain displays 

159 as ``QQ<sqrt(2) + sqrt(3)>``. The minimal polynomial for the primitive 

160 element is computed using the :py:func:`~.primitive_element` function. 

161 

162 >>> from sympy import primitive_element 

163 >>> primitive_element([sqrt(2), sqrt(3)], x) 

164 (x**4 - 10*x**2 + 1, [1, 1]) 

165 >>> minpoly(sqrt(2) + sqrt(3), x) 

166 x**4 - 10*x**2 + 1 

167 

168 The extension elements that generate the domain can be accessed from the 

169 domain using the :py:attr:`~.ext` and :py:attr:`~.orig_ext` attributes as 

170 instances of :py:class:`~.AlgebraicNumber`. The minimal polynomial for 

171 the primitive element as a :py:class:`~.DMP` instance is available as 

172 :py:attr:`~.mod`. 

173 

174 >>> K = QQ.algebraic_field(sqrt(2), sqrt(3)) 

175 >>> K 

176 QQ<sqrt(2) + sqrt(3)> 

177 >>> K.ext 

178 sqrt(2) + sqrt(3) 

179 >>> K.orig_ext 

180 (sqrt(2), sqrt(3)) 

181 >>> K.mod 

182 DMP([1, 0, -10, 0, 1], QQ, None) 

183 

184 The `discriminant`_ of the field can be obtained from the 

185 :py:meth:`~.discriminant` method, and an `integral basis`_ from the 

186 :py:meth:`~.integral_basis` method. The latter returns a list of 

187 :py:class:`~.ANP` instances by default, but can be made to return instances 

188 of :py:class:`~.Expr` or :py:class:`~.AlgebraicNumber` by passing a ``fmt`` 

189 argument. The maximal order, or ring of integers, of the field can also be 

190 obtained from the :py:meth:`~.maximal_order` method, as a 

191 :py:class:`~sympy.polys.numberfields.modules.Submodule`. 

192 

193 >>> zeta5 = exp(2*I*pi/5) 

194 >>> K = QQ.algebraic_field(zeta5) 

195 >>> K 

196 QQ<exp(2*I*pi/5)> 

197 >>> K.discriminant() 

198 125 

199 >>> K = QQ.algebraic_field(sqrt(5)) 

200 >>> K 

201 QQ<sqrt(5)> 

202 >>> K.integral_basis(fmt='sympy') 

203 [1, 1/2 + sqrt(5)/2] 

204 >>> K.maximal_order() 

205 Submodule[[2, 0], [1, 1]]/2 

206 

207 The factorization of a rational prime into prime ideals of the field is 

208 computed by the :py:meth:`~.primes_above` method, which returns a list 

209 of :py:class:`~sympy.polys.numberfields.primes.PrimeIdeal` instances. 

210 

211 >>> zeta7 = exp(2*I*pi/7) 

212 >>> K = QQ.algebraic_field(zeta7) 

213 >>> K 

214 QQ<exp(2*I*pi/7)> 

215 >>> K.primes_above(11) 

216 [(11, _x**3 + 5*_x**2 + 4*_x - 1), (11, _x**3 - 4*_x**2 - 5*_x - 1)] 

217 

218 The Galois group of the Galois closure of the field can be computed (when 

219 the minimal polynomial of the field is of sufficiently small degree). 

220 

221 >>> K.galois_group(by_name=True)[0] 

222 S6TransitiveSubgroups.C6 

223 

224 Notes 

225 ===== 

226 

227 It is not currently possible to generate an algebraic extension over any 

228 domain other than :ref:`QQ`. Ideally it would be possible to generate 

229 extensions like ``QQ(x)(sqrt(x**2 - 2))``. This is equivalent to the 

230 quotient ring ``QQ(x)[y]/(y**2 - x**2 + 2)`` and there are two 

231 implementations of this kind of quotient ring/extension in the 

232 :py:class:`~.QuotientRing` and :py:class:`~.MonogenicFiniteExtension` 

233 classes. Each of those implementations needs some work to make them fully 

234 usable though. 

235 

236 .. _algebraic number field: https://en.wikipedia.org/wiki/Algebraic_number_field 

237 .. _algebraic numbers: https://en.wikipedia.org/wiki/Algebraic_number 

238 .. _discriminant: https://en.wikipedia.org/wiki/Discriminant_of_an_algebraic_number_field 

239 .. _integral basis: https://en.wikipedia.org/wiki/Algebraic_number_field#Integral_basis 

240 .. _minimal polynomial: https://en.wikipedia.org/wiki/Minimal_polynomial_(field_theory) 

241 .. _primitive element: https://en.wikipedia.org/wiki/Primitive_element_theorem 

242 """ 

243 

244 dtype = ANP 

245 

246 is_AlgebraicField = is_Algebraic = True 

247 is_Numerical = True 

248 

249 has_assoc_Ring = False 

250 has_assoc_Field = True 

251 

252 def __init__(self, dom, *ext, alias=None): 

253 r""" 

254 Parameters 

255 ========== 

256 

257 dom : :py:class:`~.Domain` 

258 The base field over which this is an extension field. 

259 Currently only :ref:`QQ` is accepted. 

260 

261 *ext : One or more :py:class:`~.Expr` 

262 Generators of the extension. These should be expressions that are 

263 algebraic over `\mathbb{Q}`. 

264 

265 alias : str, :py:class:`~.Symbol`, None, optional (default=None) 

266 If provided, this will be used as the alias symbol for the 

267 primitive element of the :py:class:`~.AlgebraicField`. 

268 If ``None``, while ``ext`` consists of exactly one 

269 :py:class:`~.AlgebraicNumber`, its alias (if any) will be used. 

270 """ 

271 if not dom.is_QQ: 

272 raise DomainError("ground domain must be a rational field") 

273 

274 from sympy.polys.numberfields import to_number_field 

275 if len(ext) == 1 and isinstance(ext[0], tuple): 

276 orig_ext = ext[0][1:] 

277 else: 

278 orig_ext = ext 

279 

280 if alias is None and len(ext) == 1: 

281 alias = getattr(ext[0], 'alias', None) 

282 

283 self.orig_ext = orig_ext 

284 """ 

285 Original elements given to generate the extension. 

286 

287 >>> from sympy import QQ, sqrt 

288 >>> K = QQ.algebraic_field(sqrt(2), sqrt(3)) 

289 >>> K.orig_ext 

290 (sqrt(2), sqrt(3)) 

291 """ 

292 

293 self.ext = to_number_field(ext, alias=alias) 

294 """ 

295 Primitive element used for the extension. 

296 

297 >>> from sympy import QQ, sqrt 

298 >>> K = QQ.algebraic_field(sqrt(2), sqrt(3)) 

299 >>> K.ext 

300 sqrt(2) + sqrt(3) 

301 """ 

302 

303 self.mod = self.ext.minpoly.rep 

304 """ 

305 Minimal polynomial for the primitive element of the extension. 

306 

307 >>> from sympy import QQ, sqrt 

308 >>> K = QQ.algebraic_field(sqrt(2)) 

309 >>> K.mod 

310 DMP([1, 0, -2], QQ, None) 

311 """ 

312 

313 self.domain = self.dom = dom 

314 

315 self.ngens = 1 

316 self.symbols = self.gens = (self.ext,) 

317 self.unit = self([dom(1), dom(0)]) 

318 

319 self.zero = self.dtype.zero(self.mod.rep, dom) 

320 self.one = self.dtype.one(self.mod.rep, dom) 

321 

322 self._maximal_order = None 

323 self._discriminant = None 

324 self._nilradicals_mod_p = {} 

325 

326 def new(self, element): 

327 return self.dtype(element, self.mod.rep, self.dom) 

328 

329 def __str__(self): 

330 return str(self.dom) + '<' + str(self.ext) + '>' 

331 

332 def __hash__(self): 

333 return hash((self.__class__.__name__, self.dtype, self.dom, self.ext)) 

334 

335 def __eq__(self, other): 

336 """Returns ``True`` if two domains are equivalent. """ 

337 return isinstance(other, AlgebraicField) and \ 

338 self.dtype == other.dtype and self.ext == other.ext 

339 

340 def algebraic_field(self, *extension, alias=None): 

341 r"""Returns an algebraic field, i.e. `\mathbb{Q}(\alpha, \ldots)`. """ 

342 return AlgebraicField(self.dom, *((self.ext,) + extension), alias=alias) 

343 

344 def to_alg_num(self, a): 

345 """Convert ``a`` of ``dtype`` to an :py:class:`~.AlgebraicNumber`. """ 

346 return self.ext.field_element(a) 

347 

348 def to_sympy(self, a): 

349 """Convert ``a`` of ``dtype`` to a SymPy object. """ 

350 # Precompute a converter to be reused: 

351 if not hasattr(self, '_converter'): 

352 self._converter = _make_converter(self) 

353 

354 return self._converter(a) 

355 

356 def from_sympy(self, a): 

357 """Convert SymPy's expression to ``dtype``. """ 

358 try: 

359 return self([self.dom.from_sympy(a)]) 

360 except CoercionFailed: 

361 pass 

362 

363 from sympy.polys.numberfields import to_number_field 

364 

365 try: 

366 return self(to_number_field(a, self.ext).native_coeffs()) 

367 except (NotAlgebraic, IsomorphismFailed): 

368 raise CoercionFailed( 

369 "%s is not a valid algebraic number in %s" % (a, self)) 

370 

371 def from_ZZ(K1, a, K0): 

372 """Convert a Python ``int`` object to ``dtype``. """ 

373 return K1(K1.dom.convert(a, K0)) 

374 

375 def from_ZZ_python(K1, a, K0): 

376 """Convert a Python ``int`` object to ``dtype``. """ 

377 return K1(K1.dom.convert(a, K0)) 

378 

379 def from_QQ(K1, a, K0): 

380 """Convert a Python ``Fraction`` object to ``dtype``. """ 

381 return K1(K1.dom.convert(a, K0)) 

382 

383 def from_QQ_python(K1, a, K0): 

384 """Convert a Python ``Fraction`` object to ``dtype``. """ 

385 return K1(K1.dom.convert(a, K0)) 

386 

387 def from_ZZ_gmpy(K1, a, K0): 

388 """Convert a GMPY ``mpz`` object to ``dtype``. """ 

389 return K1(K1.dom.convert(a, K0)) 

390 

391 def from_QQ_gmpy(K1, a, K0): 

392 """Convert a GMPY ``mpq`` object to ``dtype``. """ 

393 return K1(K1.dom.convert(a, K0)) 

394 

395 def from_RealField(K1, a, K0): 

396 """Convert a mpmath ``mpf`` object to ``dtype``. """ 

397 return K1(K1.dom.convert(a, K0)) 

398 

399 def get_ring(self): 

400 """Returns a ring associated with ``self``. """ 

401 raise DomainError('there is no ring associated with %s' % self) 

402 

403 def is_positive(self, a): 

404 """Returns True if ``a`` is positive. """ 

405 return self.dom.is_positive(a.LC()) 

406 

407 def is_negative(self, a): 

408 """Returns True if ``a`` is negative. """ 

409 return self.dom.is_negative(a.LC()) 

410 

411 def is_nonpositive(self, a): 

412 """Returns True if ``a`` is non-positive. """ 

413 return self.dom.is_nonpositive(a.LC()) 

414 

415 def is_nonnegative(self, a): 

416 """Returns True if ``a`` is non-negative. """ 

417 return self.dom.is_nonnegative(a.LC()) 

418 

419 def numer(self, a): 

420 """Returns numerator of ``a``. """ 

421 return a 

422 

423 def denom(self, a): 

424 """Returns denominator of ``a``. """ 

425 return self.one 

426 

427 def from_AlgebraicField(K1, a, K0): 

428 """Convert AlgebraicField element 'a' to another AlgebraicField """ 

429 return K1.from_sympy(K0.to_sympy(a)) 

430 

431 def from_GaussianIntegerRing(K1, a, K0): 

432 """Convert a GaussianInteger element 'a' to ``dtype``. """ 

433 return K1.from_sympy(K0.to_sympy(a)) 

434 

435 def from_GaussianRationalField(K1, a, K0): 

436 """Convert a GaussianRational element 'a' to ``dtype``. """ 

437 return K1.from_sympy(K0.to_sympy(a)) 

438 

439 def _do_round_two(self): 

440 from sympy.polys.numberfields.basis import round_two 

441 ZK, dK = round_two(self, radicals=self._nilradicals_mod_p) 

442 self._maximal_order = ZK 

443 self._discriminant = dK 

444 

445 def maximal_order(self): 

446 """ 

447 Compute the maximal order, or ring of integers, of the field. 

448 

449 Returns 

450 ======= 

451 

452 :py:class:`~sympy.polys.numberfields.modules.Submodule`. 

453 

454 See Also 

455 ======== 

456 

457 integral_basis 

458 

459 """ 

460 if self._maximal_order is None: 

461 self._do_round_two() 

462 return self._maximal_order 

463 

464 def integral_basis(self, fmt=None): 

465 r""" 

466 Get an integral basis for the field. 

467 

468 Parameters 

469 ========== 

470 

471 fmt : str, None, optional (default=None) 

472 If ``None``, return a list of :py:class:`~.ANP` instances. 

473 If ``"sympy"``, convert each element of the list to an 

474 :py:class:`~.Expr`, using ``self.to_sympy()``. 

475 If ``"alg"``, convert each element of the list to an 

476 :py:class:`~.AlgebraicNumber`, using ``self.to_alg_num()``. 

477 

478 Examples 

479 ======== 

480 

481 >>> from sympy import QQ, AlgebraicNumber, sqrt 

482 >>> alpha = AlgebraicNumber(sqrt(5), alias='alpha') 

483 >>> k = QQ.algebraic_field(alpha) 

484 >>> B0 = k.integral_basis() 

485 >>> B1 = k.integral_basis(fmt='sympy') 

486 >>> B2 = k.integral_basis(fmt='alg') 

487 >>> print(B0[1]) # doctest: +SKIP 

488 ANP([mpq(1,2), mpq(1,2)], [mpq(1,1), mpq(0,1), mpq(-5,1)], QQ) 

489 >>> print(B1[1]) 

490 1/2 + alpha/2 

491 >>> print(B2[1]) 

492 alpha/2 + 1/2 

493 

494 In the last two cases we get legible expressions, which print somewhat 

495 differently because of the different types involved: 

496 

497 >>> print(type(B1[1])) 

498 <class 'sympy.core.add.Add'> 

499 >>> print(type(B2[1])) 

500 <class 'sympy.core.numbers.AlgebraicNumber'> 

501 

502 See Also 

503 ======== 

504 

505 to_sympy 

506 to_alg_num 

507 maximal_order 

508 """ 

509 ZK = self.maximal_order() 

510 M = ZK.QQ_matrix 

511 n = M.shape[1] 

512 B = [self.new(list(reversed(M[:, j].flat()))) for j in range(n)] 

513 if fmt == 'sympy': 

514 return [self.to_sympy(b) for b in B] 

515 elif fmt == 'alg': 

516 return [self.to_alg_num(b) for b in B] 

517 return B 

518 

519 def discriminant(self): 

520 """Get the discriminant of the field.""" 

521 if self._discriminant is None: 

522 self._do_round_two() 

523 return self._discriminant 

524 

525 def primes_above(self, p): 

526 """Compute the prime ideals lying above a given rational prime *p*.""" 

527 from sympy.polys.numberfields.primes import prime_decomp 

528 ZK = self.maximal_order() 

529 dK = self.discriminant() 

530 rad = self._nilradicals_mod_p.get(p) 

531 return prime_decomp(p, ZK=ZK, dK=dK, radical=rad) 

532 

533 def galois_group(self, by_name=False, max_tries=30, randomize=False): 

534 """ 

535 Compute the Galois group of the Galois closure of this field. 

536 

537 Examples 

538 ======== 

539 

540 If the field is Galois, the order of the group will equal the degree 

541 of the field: 

542 

543 >>> from sympy import QQ 

544 >>> from sympy.abc import x 

545 >>> k = QQ.alg_field_from_poly(x**4 + 1) 

546 >>> G, _ = k.galois_group() 

547 >>> G.order() 

548 4 

549 

550 If the field is not Galois, then its Galois closure is a proper 

551 extension, and the order of the Galois group will be greater than the 

552 degree of the field: 

553 

554 >>> k = QQ.alg_field_from_poly(x**4 - 2) 

555 >>> G, _ = k.galois_group() 

556 >>> G.order() 

557 8 

558 

559 See Also 

560 ======== 

561 

562 sympy.polys.numberfields.galoisgroups.galois_group 

563 

564 """ 

565 return self.ext.minpoly_of_element().galois_group( 

566 by_name=by_name, max_tries=max_tries, randomize=randomize) 

567 

568 

569def _make_converter(K): 

570 """Construct the converter to convert back to Expr""" 

571 # Precompute the effect of converting to SymPy and expanding expressions 

572 # like (sqrt(2) + sqrt(3))**2. Asking Expr to do the expansion on every 

573 # conversion from K to Expr is slow. Here we compute the expansions for 

574 # each power of the generator and collect together the resulting algebraic 

575 # terms and the rational coefficients into a matrix. 

576 

577 gen = K.ext.as_expr() 

578 todom = K.dom.from_sympy 

579 

580 # We'll let Expr compute the expansions. We won't make any presumptions 

581 # about what this results in except that it is QQ-linear in some terms 

582 # that we will call algebraics. The final result will be expressed in 

583 # terms of those. 

584 powers = [S.One, gen] 

585 for n in range(2, K.mod.degree()): 

586 powers.append((gen * powers[-1]).expand()) 

587 

588 # Collect the rational coefficients and algebraic Expr that can 

589 # map the ANP coefficients into an expanded SymPy expression 

590 terms = [dict(t.as_coeff_Mul()[::-1] for t in Add.make_args(p)) for p in powers] 

591 algebraics = set().union(*terms) 

592 matrix = [[todom(t.get(a, S.Zero)) for t in terms] for a in algebraics] 

593 

594 # Create a function to do the conversion efficiently: 

595 

596 def converter(a): 

597 """Convert a to Expr using converter""" 

598 ai = a.rep[::-1] 

599 tosympy = K.dom.to_sympy 

600 coeffs_dom = [sum(mij*aj for mij, aj in zip(mi, ai)) for mi in matrix] 

601 coeffs_sympy = [tosympy(c) for c in coeffs_dom] 

602 res = Add(*(Mul(c, a) for c, a in zip(coeffs_sympy, algebraics))) 

603 return res 

604 

605 return converter