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
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Implementation of :class:`AlgebraicField` class. """
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
14@public
15class AlgebraicField(Field, CharacteristicZero, SimpleDomain):
16 r"""Algebraic number field :ref:`QQ(a)`
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`).
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.
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')
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.
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)>')
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).
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)>')
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.
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))
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.
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
78 It is also possible to use :ref:`QQ(a)` with the :py:func:`~.cancel`
79 and :py:func:`~.gcd` functions.
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)
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.
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)
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.
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
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.
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))
142 It is also possible to make an algebraic field from multiple extension
143 elements.
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))
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.
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
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`.
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)
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`.
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
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.
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)]
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).
221 >>> K.galois_group(by_name=True)[0]
222 S6TransitiveSubgroups.C6
224 Notes
225 =====
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.
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 """
244 dtype = ANP
246 is_AlgebraicField = is_Algebraic = True
247 is_Numerical = True
249 has_assoc_Ring = False
250 has_assoc_Field = True
252 def __init__(self, dom, *ext, alias=None):
253 r"""
254 Parameters
255 ==========
257 dom : :py:class:`~.Domain`
258 The base field over which this is an extension field.
259 Currently only :ref:`QQ` is accepted.
261 *ext : One or more :py:class:`~.Expr`
262 Generators of the extension. These should be expressions that are
263 algebraic over `\mathbb{Q}`.
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")
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
280 if alias is None and len(ext) == 1:
281 alias = getattr(ext[0], 'alias', None)
283 self.orig_ext = orig_ext
284 """
285 Original elements given to generate the extension.
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 """
293 self.ext = to_number_field(ext, alias=alias)
294 """
295 Primitive element used for the extension.
297 >>> from sympy import QQ, sqrt
298 >>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
299 >>> K.ext
300 sqrt(2) + sqrt(3)
301 """
303 self.mod = self.ext.minpoly.rep
304 """
305 Minimal polynomial for the primitive element of the extension.
307 >>> from sympy import QQ, sqrt
308 >>> K = QQ.algebraic_field(sqrt(2))
309 >>> K.mod
310 DMP([1, 0, -2], QQ, None)
311 """
313 self.domain = self.dom = dom
315 self.ngens = 1
316 self.symbols = self.gens = (self.ext,)
317 self.unit = self([dom(1), dom(0)])
319 self.zero = self.dtype.zero(self.mod.rep, dom)
320 self.one = self.dtype.one(self.mod.rep, dom)
322 self._maximal_order = None
323 self._discriminant = None
324 self._nilradicals_mod_p = {}
326 def new(self, element):
327 return self.dtype(element, self.mod.rep, self.dom)
329 def __str__(self):
330 return str(self.dom) + '<' + str(self.ext) + '>'
332 def __hash__(self):
333 return hash((self.__class__.__name__, self.dtype, self.dom, self.ext))
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
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)
344 def to_alg_num(self, a):
345 """Convert ``a`` of ``dtype`` to an :py:class:`~.AlgebraicNumber`. """
346 return self.ext.field_element(a)
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)
354 return self._converter(a)
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
363 from sympy.polys.numberfields import to_number_field
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))
371 def from_ZZ(K1, a, K0):
372 """Convert a Python ``int`` object to ``dtype``. """
373 return K1(K1.dom.convert(a, K0))
375 def from_ZZ_python(K1, a, K0):
376 """Convert a Python ``int`` object to ``dtype``. """
377 return K1(K1.dom.convert(a, K0))
379 def from_QQ(K1, a, K0):
380 """Convert a Python ``Fraction`` object to ``dtype``. """
381 return K1(K1.dom.convert(a, K0))
383 def from_QQ_python(K1, a, K0):
384 """Convert a Python ``Fraction`` object to ``dtype``. """
385 return K1(K1.dom.convert(a, K0))
387 def from_ZZ_gmpy(K1, a, K0):
388 """Convert a GMPY ``mpz`` object to ``dtype``. """
389 return K1(K1.dom.convert(a, K0))
391 def from_QQ_gmpy(K1, a, K0):
392 """Convert a GMPY ``mpq`` object to ``dtype``. """
393 return K1(K1.dom.convert(a, K0))
395 def from_RealField(K1, a, K0):
396 """Convert a mpmath ``mpf`` object to ``dtype``. """
397 return K1(K1.dom.convert(a, K0))
399 def get_ring(self):
400 """Returns a ring associated with ``self``. """
401 raise DomainError('there is no ring associated with %s' % self)
403 def is_positive(self, a):
404 """Returns True if ``a`` is positive. """
405 return self.dom.is_positive(a.LC())
407 def is_negative(self, a):
408 """Returns True if ``a`` is negative. """
409 return self.dom.is_negative(a.LC())
411 def is_nonpositive(self, a):
412 """Returns True if ``a`` is non-positive. """
413 return self.dom.is_nonpositive(a.LC())
415 def is_nonnegative(self, a):
416 """Returns True if ``a`` is non-negative. """
417 return self.dom.is_nonnegative(a.LC())
419 def numer(self, a):
420 """Returns numerator of ``a``. """
421 return a
423 def denom(self, a):
424 """Returns denominator of ``a``. """
425 return self.one
427 def from_AlgebraicField(K1, a, K0):
428 """Convert AlgebraicField element 'a' to another AlgebraicField """
429 return K1.from_sympy(K0.to_sympy(a))
431 def from_GaussianIntegerRing(K1, a, K0):
432 """Convert a GaussianInteger element 'a' to ``dtype``. """
433 return K1.from_sympy(K0.to_sympy(a))
435 def from_GaussianRationalField(K1, a, K0):
436 """Convert a GaussianRational element 'a' to ``dtype``. """
437 return K1.from_sympy(K0.to_sympy(a))
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
445 def maximal_order(self):
446 """
447 Compute the maximal order, or ring of integers, of the field.
449 Returns
450 =======
452 :py:class:`~sympy.polys.numberfields.modules.Submodule`.
454 See Also
455 ========
457 integral_basis
459 """
460 if self._maximal_order is None:
461 self._do_round_two()
462 return self._maximal_order
464 def integral_basis(self, fmt=None):
465 r"""
466 Get an integral basis for the field.
468 Parameters
469 ==========
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()``.
478 Examples
479 ========
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
494 In the last two cases we get legible expressions, which print somewhat
495 differently because of the different types involved:
497 >>> print(type(B1[1]))
498 <class 'sympy.core.add.Add'>
499 >>> print(type(B2[1]))
500 <class 'sympy.core.numbers.AlgebraicNumber'>
502 See Also
503 ========
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
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
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)
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.
537 Examples
538 ========
540 If the field is Galois, the order of the group will equal the degree
541 of the field:
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
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:
554 >>> k = QQ.alg_field_from_poly(x**4 - 2)
555 >>> G, _ = k.galois_group()
556 >>> G.order()
557 8
559 See Also
560 ========
562 sympy.polys.numberfields.galoisgroups.galois_group
564 """
565 return self.ext.minpoly_of_element().galois_group(
566 by_name=by_name, max_tries=max_tries, randomize=randomize)
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.
577 gen = K.ext.as_expr()
578 todom = K.dom.from_sympy
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())
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]
594 # Create a function to do the conversion efficiently:
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
605 return converter