algebraicfield.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. """Implementation of :class:`AlgebraicField` class. """
  2. from sympy.core.add import Add
  3. from sympy.core.mul import Mul
  4. from sympy.core.singleton import S
  5. from sympy.core.symbol import Dummy, symbols
  6. from sympy.polys.domains.characteristiczero import CharacteristicZero
  7. from sympy.polys.domains.field import Field
  8. from sympy.polys.domains.simpledomain import SimpleDomain
  9. from sympy.polys.polyclasses import ANP
  10. from sympy.polys.polyerrors import CoercionFailed, DomainError, NotAlgebraic, IsomorphismFailed
  11. from sympy.utilities import public
  12. @public
  13. class AlgebraicField(Field, CharacteristicZero, SimpleDomain):
  14. r"""Algebraic number field :ref:`QQ(a)`
  15. A :ref:`QQ(a)` domain represents an `algebraic number field`_
  16. `\mathbb{Q}(a)` as a :py:class:`~.Domain` in the domain system (see
  17. :ref:`polys-domainsintro`).
  18. A :py:class:`~.Poly` created from an expression involving `algebraic
  19. numbers`_ will treat the algebraic numbers as generators if the generators
  20. argument is not specified.
  21. >>> from sympy import Poly, Symbol, sqrt
  22. >>> x = Symbol('x')
  23. >>> Poly(x**2 + sqrt(2))
  24. Poly(x**2 + (sqrt(2)), x, sqrt(2), domain='ZZ')
  25. That is a multivariate polynomial with ``sqrt(2)`` treated as one of the
  26. generators (variables). If the generators are explicitly specified then
  27. ``sqrt(2)`` will be considered to be a coefficient but by default the
  28. :ref:`EX` domain is used. To make a :py:class:`~.Poly` with a :ref:`QQ(a)`
  29. domain the argument ``extension=True`` can be given.
  30. >>> Poly(x**2 + sqrt(2), x)
  31. Poly(x**2 + sqrt(2), x, domain='EX')
  32. >>> Poly(x**2 + sqrt(2), x, extension=True)
  33. Poly(x**2 + sqrt(2), x, domain='QQ<sqrt(2)>')
  34. A generator of the algebraic field extension can also be specified
  35. explicitly which is particularly useful if the coefficients are all
  36. rational but an extension field is needed (e.g. to factor the
  37. polynomial).
  38. >>> Poly(x**2 + 1)
  39. Poly(x**2 + 1, x, domain='ZZ')
  40. >>> Poly(x**2 + 1, extension=sqrt(2))
  41. Poly(x**2 + 1, x, domain='QQ<sqrt(2)>')
  42. It is possible to factorise a polynomial over a :ref:`QQ(a)` domain using
  43. the ``extension`` argument to :py:func:`~.factor` or by specifying the domain
  44. explicitly.
  45. >>> from sympy import factor, QQ
  46. >>> factor(x**2 - 2)
  47. x**2 - 2
  48. >>> factor(x**2 - 2, extension=sqrt(2))
  49. (x - sqrt(2))*(x + sqrt(2))
  50. >>> factor(x**2 - 2, domain='QQ<sqrt(2)>')
  51. (x - sqrt(2))*(x + sqrt(2))
  52. >>> factor(x**2 - 2, domain=QQ.algebraic_field(sqrt(2)))
  53. (x - sqrt(2))*(x + sqrt(2))
  54. The ``extension=True`` argument can be used but will only create an
  55. extension that contains the coefficients which is usually not enough to
  56. factorise the polynomial.
  57. >>> p = x**3 + sqrt(2)*x**2 - 2*x - 2*sqrt(2)
  58. >>> factor(p) # treats sqrt(2) as a symbol
  59. (x + sqrt(2))*(x**2 - 2)
  60. >>> factor(p, extension=True)
  61. (x - sqrt(2))*(x + sqrt(2))**2
  62. >>> factor(x**2 - 2, extension=True) # all rational coefficients
  63. x**2 - 2
  64. It is also possible to use :ref:`QQ(a)` with the :py:func:`~.cancel`
  65. and :py:func:`~.gcd` functions.
  66. >>> from sympy import cancel, gcd
  67. >>> cancel((x**2 - 2)/(x - sqrt(2)))
  68. (x**2 - 2)/(x - sqrt(2))
  69. >>> cancel((x**2 - 2)/(x - sqrt(2)), extension=sqrt(2))
  70. x + sqrt(2)
  71. >>> gcd(x**2 - 2, x - sqrt(2))
  72. 1
  73. >>> gcd(x**2 - 2, x - sqrt(2), extension=sqrt(2))
  74. x - sqrt(2)
  75. When using the domain directly :ref:`QQ(a)` can be used as a constructor
  76. to create instances which then support the operations ``+,-,*,**,/``. The
  77. :py:meth:`~.Domain.algebraic_field` method is used to construct a
  78. particular :ref:`QQ(a)` domain. The :py:meth:`~.Domain.from_sympy` method
  79. can be used to create domain elements from normal SymPy expressions.
  80. >>> K = QQ.algebraic_field(sqrt(2))
  81. >>> K
  82. QQ<sqrt(2)>
  83. >>> xk = K.from_sympy(3 + 4*sqrt(2))
  84. >>> xk # doctest: +SKIP
  85. ANP([4, 3], [1, 0, -2], QQ)
  86. Elements of :ref:`QQ(a)` are instances of :py:class:`~.ANP` which have
  87. limited printing support. The raw display shows the internal
  88. representation of the element as the list ``[4, 3]`` representing the
  89. coefficients of ``1`` and ``sqrt(2)`` for this element in the form
  90. ``a * sqrt(2) + b * 1`` where ``a`` and ``b`` are elements of :ref:`QQ`.
  91. The minimal polynomial for the generator ``(x**2 - 2)`` is also shown in
  92. the :ref:`dup-representation` as the list ``[1, 0, -2]``. We can use
  93. :py:meth:`~.Domain.to_sympy` to get a better printed form for the
  94. elements and to see the results of operations.
  95. >>> xk = K.from_sympy(3 + 4*sqrt(2))
  96. >>> yk = K.from_sympy(2 + 3*sqrt(2))
  97. >>> xk * yk # doctest: +SKIP
  98. ANP([17, 30], [1, 0, -2], QQ)
  99. >>> K.to_sympy(xk * yk)
  100. 17*sqrt(2) + 30
  101. >>> K.to_sympy(xk + yk)
  102. 5 + 7*sqrt(2)
  103. >>> K.to_sympy(xk ** 2)
  104. 24*sqrt(2) + 41
  105. >>> K.to_sympy(xk / yk)
  106. sqrt(2)/14 + 9/7
  107. Any expression representing an algebraic number can be used to generate
  108. a :ref:`QQ(a)` domain provided its `minimal polynomial`_ can be computed.
  109. The function :py:func:`~.minpoly` function is used for this.
  110. >>> from sympy import exp, I, pi, minpoly
  111. >>> g = exp(2*I*pi/3)
  112. >>> g
  113. exp(2*I*pi/3)
  114. >>> g.is_algebraic
  115. True
  116. >>> minpoly(g, x)
  117. x**2 + x + 1
  118. >>> factor(x**3 - 1, extension=g)
  119. (x - 1)*(x - exp(2*I*pi/3))*(x + 1 + exp(2*I*pi/3))
  120. It is also possible to make an algebraic field from multiple extension
  121. elements.
  122. >>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
  123. >>> K
  124. QQ<sqrt(2) + sqrt(3)>
  125. >>> p = x**4 - 5*x**2 + 6
  126. >>> factor(p)
  127. (x**2 - 3)*(x**2 - 2)
  128. >>> factor(p, domain=K)
  129. (x - sqrt(2))*(x + sqrt(2))*(x - sqrt(3))*(x + sqrt(3))
  130. >>> factor(p, extension=[sqrt(2), sqrt(3)])
  131. (x - sqrt(2))*(x + sqrt(2))*(x - sqrt(3))*(x + sqrt(3))
  132. Multiple extension elements are always combined together to make a single
  133. `primitive element`_. In the case of ``[sqrt(2), sqrt(3)]`` the primitive
  134. element chosen is ``sqrt(2) + sqrt(3)`` which is why the domain displays
  135. as ``QQ<sqrt(2) + sqrt(3)>``. The minimal polynomial for the primitive
  136. element is computed using the :py:func:`~.primitive_element` function.
  137. >>> from sympy import primitive_element
  138. >>> primitive_element([sqrt(2), sqrt(3)], x)
  139. (x**4 - 10*x**2 + 1, [1, 1])
  140. >>> minpoly(sqrt(2) + sqrt(3), x)
  141. x**4 - 10*x**2 + 1
  142. The extension elements that generate the domain can be accessed from the
  143. domain using the :py:attr:`~.ext` and :py:attr:`~.orig_ext` attributes as
  144. instances of :py:class:`~.AlgebraicNumber`. The minimal polynomial for
  145. the primitive element as a :py:class:`~.DMP` instance is available as
  146. :py:attr:`~.mod`.
  147. >>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
  148. >>> K
  149. QQ<sqrt(2) + sqrt(3)>
  150. >>> K.ext
  151. sqrt(2) + sqrt(3)
  152. >>> K.orig_ext
  153. (sqrt(2), sqrt(3))
  154. >>> K.mod # doctest: +SKIP
  155. DMP_Python([1, 0, -10, 0, 1], QQ)
  156. The `discriminant`_ of the field can be obtained from the
  157. :py:meth:`~.discriminant` method, and an `integral basis`_ from the
  158. :py:meth:`~.integral_basis` method. The latter returns a list of
  159. :py:class:`~.ANP` instances by default, but can be made to return instances
  160. of :py:class:`~.Expr` or :py:class:`~.AlgebraicNumber` by passing a ``fmt``
  161. argument. The maximal order, or ring of integers, of the field can also be
  162. obtained from the :py:meth:`~.maximal_order` method, as a
  163. :py:class:`~sympy.polys.numberfields.modules.Submodule`.
  164. >>> zeta5 = exp(2*I*pi/5)
  165. >>> K = QQ.algebraic_field(zeta5)
  166. >>> K
  167. QQ<exp(2*I*pi/5)>
  168. >>> K.discriminant()
  169. 125
  170. >>> K = QQ.algebraic_field(sqrt(5))
  171. >>> K
  172. QQ<sqrt(5)>
  173. >>> K.integral_basis(fmt='sympy')
  174. [1, 1/2 + sqrt(5)/2]
  175. >>> K.maximal_order()
  176. Submodule[[2, 0], [1, 1]]/2
  177. The factorization of a rational prime into prime ideals of the field is
  178. computed by the :py:meth:`~.primes_above` method, which returns a list
  179. of :py:class:`~sympy.polys.numberfields.primes.PrimeIdeal` instances.
  180. >>> zeta7 = exp(2*I*pi/7)
  181. >>> K = QQ.algebraic_field(zeta7)
  182. >>> K
  183. QQ<exp(2*I*pi/7)>
  184. >>> K.primes_above(11)
  185. [(11, _x**3 + 5*_x**2 + 4*_x - 1), (11, _x**3 - 4*_x**2 - 5*_x - 1)]
  186. The Galois group of the Galois closure of the field can be computed (when
  187. the minimal polynomial of the field is of sufficiently small degree).
  188. >>> K.galois_group(by_name=True)[0]
  189. S6TransitiveSubgroups.C6
  190. Notes
  191. =====
  192. It is not currently possible to generate an algebraic extension over any
  193. domain other than :ref:`QQ`. Ideally it would be possible to generate
  194. extensions like ``QQ(x)(sqrt(x**2 - 2))``. This is equivalent to the
  195. quotient ring ``QQ(x)[y]/(y**2 - x**2 + 2)`` and there are two
  196. implementations of this kind of quotient ring/extension in the
  197. :py:class:`~.QuotientRing` and :py:class:`~.MonogenicFiniteExtension`
  198. classes. Each of those implementations needs some work to make them fully
  199. usable though.
  200. .. _algebraic number field: https://en.wikipedia.org/wiki/Algebraic_number_field
  201. .. _algebraic numbers: https://en.wikipedia.org/wiki/Algebraic_number
  202. .. _discriminant: https://en.wikipedia.org/wiki/Discriminant_of_an_algebraic_number_field
  203. .. _integral basis: https://en.wikipedia.org/wiki/Algebraic_number_field#Integral_basis
  204. .. _minimal polynomial: https://en.wikipedia.org/wiki/Minimal_polynomial_(field_theory)
  205. .. _primitive element: https://en.wikipedia.org/wiki/Primitive_element_theorem
  206. """
  207. dtype = ANP
  208. is_AlgebraicField = is_Algebraic = True
  209. is_Numerical = True
  210. has_assoc_Ring = False
  211. has_assoc_Field = True
  212. def __init__(self, dom, *ext, alias=None):
  213. r"""
  214. Parameters
  215. ==========
  216. dom : :py:class:`~.Domain`
  217. The base field over which this is an extension field.
  218. Currently only :ref:`QQ` is accepted.
  219. *ext : One or more :py:class:`~.Expr`
  220. Generators of the extension. These should be expressions that are
  221. algebraic over `\mathbb{Q}`.
  222. alias : str, :py:class:`~.Symbol`, None, optional (default=None)
  223. If provided, this will be used as the alias symbol for the
  224. primitive element of the :py:class:`~.AlgebraicField`.
  225. If ``None``, while ``ext`` consists of exactly one
  226. :py:class:`~.AlgebraicNumber`, its alias (if any) will be used.
  227. """
  228. if not dom.is_QQ:
  229. raise DomainError("ground domain must be a rational field")
  230. from sympy.polys.numberfields import to_number_field
  231. if len(ext) == 1 and isinstance(ext[0], tuple):
  232. orig_ext = ext[0][1:]
  233. else:
  234. orig_ext = ext
  235. if alias is None and len(ext) == 1:
  236. alias = getattr(ext[0], 'alias', None)
  237. self.orig_ext = orig_ext
  238. """
  239. Original elements given to generate the extension.
  240. >>> from sympy import QQ, sqrt
  241. >>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
  242. >>> K.orig_ext
  243. (sqrt(2), sqrt(3))
  244. """
  245. self.ext = to_number_field(ext, alias=alias)
  246. """
  247. Primitive element used for the extension.
  248. >>> from sympy import QQ, sqrt
  249. >>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
  250. >>> K.ext
  251. sqrt(2) + sqrt(3)
  252. """
  253. self.mod = self.ext.minpoly.rep
  254. """
  255. Minimal polynomial for the primitive element of the extension.
  256. >>> from sympy import QQ, sqrt
  257. >>> K = QQ.algebraic_field(sqrt(2))
  258. >>> K.mod
  259. DMP([1, 0, -2], QQ)
  260. """
  261. self.domain = self.dom = dom
  262. self.ngens = 1
  263. self.symbols = self.gens = (self.ext,)
  264. self.unit = self([dom(1), dom(0)])
  265. self.zero = self.dtype.zero(self.mod.to_list(), dom)
  266. self.one = self.dtype.one(self.mod.to_list(), dom)
  267. self._maximal_order = None
  268. self._discriminant = None
  269. self._nilradicals_mod_p = {}
  270. def new(self, element):
  271. return self.dtype(element, self.mod.to_list(), self.dom)
  272. def __str__(self):
  273. return str(self.dom) + '<' + str(self.ext) + '>'
  274. def __hash__(self):
  275. return hash((self.__class__.__name__, self.dtype, self.dom, self.ext))
  276. def __eq__(self, other):
  277. """Returns ``True`` if two domains are equivalent. """
  278. if isinstance(other, AlgebraicField):
  279. return self.dtype == other.dtype and self.ext == other.ext
  280. else:
  281. return NotImplemented
  282. def algebraic_field(self, *extension, alias=None):
  283. r"""Returns an algebraic field, i.e. `\mathbb{Q}(\alpha, \ldots)`. """
  284. return AlgebraicField(self.dom, *((self.ext,) + extension), alias=alias)
  285. def to_alg_num(self, a):
  286. """Convert ``a`` of ``dtype`` to an :py:class:`~.AlgebraicNumber`. """
  287. return self.ext.field_element(a)
  288. def to_sympy(self, a):
  289. """Convert ``a`` of ``dtype`` to a SymPy object. """
  290. # Precompute a converter to be reused:
  291. if not hasattr(self, '_converter'):
  292. self._converter = _make_converter(self)
  293. return self._converter(a)
  294. def from_sympy(self, a):
  295. """Convert SymPy's expression to ``dtype``. """
  296. try:
  297. return self([self.dom.from_sympy(a)])
  298. except CoercionFailed:
  299. pass
  300. from sympy.polys.numberfields import to_number_field
  301. try:
  302. return self(to_number_field(a, self.ext).native_coeffs())
  303. except (NotAlgebraic, IsomorphismFailed):
  304. raise CoercionFailed(
  305. "%s is not a valid algebraic number in %s" % (a, self))
  306. def from_ZZ(K1, a, K0):
  307. """Convert a Python ``int`` object to ``dtype``. """
  308. return K1(K1.dom.convert(a, K0))
  309. def from_ZZ_python(K1, a, K0):
  310. """Convert a Python ``int`` object to ``dtype``. """
  311. return K1(K1.dom.convert(a, K0))
  312. def from_QQ(K1, a, K0):
  313. """Convert a Python ``Fraction`` object to ``dtype``. """
  314. return K1(K1.dom.convert(a, K0))
  315. def from_QQ_python(K1, a, K0):
  316. """Convert a Python ``Fraction`` object to ``dtype``. """
  317. return K1(K1.dom.convert(a, K0))
  318. def from_ZZ_gmpy(K1, a, K0):
  319. """Convert a GMPY ``mpz`` object to ``dtype``. """
  320. return K1(K1.dom.convert(a, K0))
  321. def from_QQ_gmpy(K1, a, K0):
  322. """Convert a GMPY ``mpq`` object to ``dtype``. """
  323. return K1(K1.dom.convert(a, K0))
  324. def from_RealField(K1, a, K0):
  325. """Convert a mpmath ``mpf`` object to ``dtype``. """
  326. return K1(K1.dom.convert(a, K0))
  327. def get_ring(self):
  328. """Returns a ring associated with ``self``. """
  329. raise DomainError('there is no ring associated with %s' % self)
  330. def is_positive(self, a):
  331. """Returns True if ``a`` is positive. """
  332. return self.dom.is_positive(a.LC())
  333. def is_negative(self, a):
  334. """Returns True if ``a`` is negative. """
  335. return self.dom.is_negative(a.LC())
  336. def is_nonpositive(self, a):
  337. """Returns True if ``a`` is non-positive. """
  338. return self.dom.is_nonpositive(a.LC())
  339. def is_nonnegative(self, a):
  340. """Returns True if ``a`` is non-negative. """
  341. return self.dom.is_nonnegative(a.LC())
  342. def numer(self, a):
  343. """Returns numerator of ``a``. """
  344. return a
  345. def denom(self, a):
  346. """Returns denominator of ``a``. """
  347. return self.one
  348. def from_AlgebraicField(K1, a, K0):
  349. """Convert AlgebraicField element 'a' to another AlgebraicField """
  350. return K1.from_sympy(K0.to_sympy(a))
  351. def from_GaussianIntegerRing(K1, a, K0):
  352. """Convert a GaussianInteger element 'a' to ``dtype``. """
  353. return K1.from_sympy(K0.to_sympy(a))
  354. def from_GaussianRationalField(K1, a, K0):
  355. """Convert a GaussianRational element 'a' to ``dtype``. """
  356. return K1.from_sympy(K0.to_sympy(a))
  357. def _do_round_two(self):
  358. from sympy.polys.numberfields.basis import round_two
  359. ZK, dK = round_two(self, radicals=self._nilradicals_mod_p)
  360. self._maximal_order = ZK
  361. self._discriminant = dK
  362. def maximal_order(self):
  363. """
  364. Compute the maximal order, or ring of integers, of the field.
  365. Returns
  366. =======
  367. :py:class:`~sympy.polys.numberfields.modules.Submodule`.
  368. See Also
  369. ========
  370. integral_basis
  371. """
  372. if self._maximal_order is None:
  373. self._do_round_two()
  374. return self._maximal_order
  375. def integral_basis(self, fmt=None):
  376. r"""
  377. Get an integral basis for the field.
  378. Parameters
  379. ==========
  380. fmt : str, None, optional (default=None)
  381. If ``None``, return a list of :py:class:`~.ANP` instances.
  382. If ``"sympy"``, convert each element of the list to an
  383. :py:class:`~.Expr`, using ``self.to_sympy()``.
  384. If ``"alg"``, convert each element of the list to an
  385. :py:class:`~.AlgebraicNumber`, using ``self.to_alg_num()``.
  386. Examples
  387. ========
  388. >>> from sympy import QQ, AlgebraicNumber, sqrt
  389. >>> alpha = AlgebraicNumber(sqrt(5), alias='alpha')
  390. >>> k = QQ.algebraic_field(alpha)
  391. >>> B0 = k.integral_basis()
  392. >>> B1 = k.integral_basis(fmt='sympy')
  393. >>> B2 = k.integral_basis(fmt='alg')
  394. >>> print(B0[1]) # doctest: +SKIP
  395. ANP([mpq(1,2), mpq(1,2)], [mpq(1,1), mpq(0,1), mpq(-5,1)], QQ)
  396. >>> print(B1[1])
  397. 1/2 + alpha/2
  398. >>> print(B2[1])
  399. alpha/2 + 1/2
  400. In the last two cases we get legible expressions, which print somewhat
  401. differently because of the different types involved:
  402. >>> print(type(B1[1]))
  403. <class 'sympy.core.add.Add'>
  404. >>> print(type(B2[1]))
  405. <class 'sympy.core.numbers.AlgebraicNumber'>
  406. See Also
  407. ========
  408. to_sympy
  409. to_alg_num
  410. maximal_order
  411. """
  412. ZK = self.maximal_order()
  413. M = ZK.QQ_matrix
  414. n = M.shape[1]
  415. B = [self.new(list(reversed(M[:, j].flat()))) for j in range(n)]
  416. if fmt == 'sympy':
  417. return [self.to_sympy(b) for b in B]
  418. elif fmt == 'alg':
  419. return [self.to_alg_num(b) for b in B]
  420. return B
  421. def discriminant(self):
  422. """Get the discriminant of the field."""
  423. if self._discriminant is None:
  424. self._do_round_two()
  425. return self._discriminant
  426. def primes_above(self, p):
  427. """Compute the prime ideals lying above a given rational prime *p*."""
  428. from sympy.polys.numberfields.primes import prime_decomp
  429. ZK = self.maximal_order()
  430. dK = self.discriminant()
  431. rad = self._nilradicals_mod_p.get(p)
  432. return prime_decomp(p, ZK=ZK, dK=dK, radical=rad)
  433. def galois_group(self, by_name=False, max_tries=30, randomize=False):
  434. """
  435. Compute the Galois group of the Galois closure of this field.
  436. Examples
  437. ========
  438. If the field is Galois, the order of the group will equal the degree
  439. of the field:
  440. >>> from sympy import QQ
  441. >>> from sympy.abc import x
  442. >>> k = QQ.alg_field_from_poly(x**4 + 1)
  443. >>> G, _ = k.galois_group()
  444. >>> G.order()
  445. 4
  446. If the field is not Galois, then its Galois closure is a proper
  447. extension, and the order of the Galois group will be greater than the
  448. degree of the field:
  449. >>> k = QQ.alg_field_from_poly(x**4 - 2)
  450. >>> G, _ = k.galois_group()
  451. >>> G.order()
  452. 8
  453. See Also
  454. ========
  455. sympy.polys.numberfields.galoisgroups.galois_group
  456. """
  457. return self.ext.minpoly_of_element().galois_group(
  458. by_name=by_name, max_tries=max_tries, randomize=randomize)
  459. def _make_converter(K):
  460. """Construct the converter to convert back to Expr"""
  461. # Precompute the effect of converting to SymPy and expanding expressions
  462. # like (sqrt(2) + sqrt(3))**2. Asking Expr to do the expansion on every
  463. # conversion from K to Expr is slow. Here we compute the expansions for
  464. # each power of the generator and collect together the resulting algebraic
  465. # terms and the rational coefficients into a matrix.
  466. ext = K.ext.as_expr()
  467. todom = K.dom.from_sympy
  468. toexpr = K.dom.to_sympy
  469. if not ext.is_Add:
  470. powers = [ext**n for n in range(K.mod.degree())]
  471. else:
  472. # primitive_element generates a QQ-linear combination of lower degree
  473. # algebraic numbers to generate the higher degree extension e.g.
  474. # QQ<sqrt(2)+sqrt(3)> That means that we end up having high powers of low
  475. # degree algebraic numbers that can be reduced. Here we will use the
  476. # minimal polynomials of the algebraic numbers to reduce those powers
  477. # before converting to Expr.
  478. from sympy.polys.numberfields.minpoly import minpoly
  479. # Decompose ext as a linear combination of gens and make a symbol for
  480. # each gen.
  481. gens, coeffs = zip(*ext.as_coefficients_dict().items())
  482. syms = symbols(f'a:{len(gens)}', cls=Dummy)
  483. sym2gen = dict(zip(syms, gens))
  484. # Make a polynomial ring that can express ext and minpolys of all gens
  485. # in terms of syms.
  486. R = K.dom[syms]
  487. monoms = [R.ring.monomial_basis(i) for i in range(R.ngens)]
  488. ext_dict = {m: todom(c) for m, c in zip(monoms, coeffs)}
  489. ext_poly = R.ring.from_dict(ext_dict)
  490. minpolys = [R.from_sympy(minpoly(g, s)) for s, g in sym2gen.items()]
  491. # Compute all powers of ext_poly reduced modulo minpolys
  492. powers = [R.one, ext_poly]
  493. for n in range(2, K.mod.degree()):
  494. ext_poly_n = (powers[-1] * ext_poly).rem(minpolys)
  495. powers.append(ext_poly_n)
  496. # Convert the powers back to Expr. This will recombine some things like
  497. # sqrt(2)*sqrt(3) -> sqrt(6).
  498. powers = [p.as_expr().xreplace(sym2gen) for p in powers]
  499. # This also expands some rational powers
  500. powers = [p.expand() for p in powers]
  501. # Collect the rational coefficients and algebraic Expr that can
  502. # map the ANP coefficients into an expanded SymPy expression
  503. terms = [dict(t.as_coeff_Mul()[::-1] for t in Add.make_args(p)) for p in powers]
  504. algebraics = set().union(*terms)
  505. matrix = [[todom(t.get(a, S.Zero)) for t in terms] for a in algebraics]
  506. # Create a function to do the conversion efficiently:
  507. def converter(a):
  508. """Convert a to Expr using converter"""
  509. ai = a.to_list()[::-1]
  510. coeffs_dom = [sum(mij*aj for mij, aj in zip(mi, ai)) for mi in matrix]
  511. coeffs_sympy = [toexpr(c) for c in coeffs_dom]
  512. res = Add(*(Mul(c, a) for c, a in zip(coeffs_sympy, algebraics)))
  513. return res
  514. return converter