606 lines
21 KiB
Python
606 lines
21 KiB
Python
|
"""Implementation of :class:`AlgebraicField` class. """
|
||
|
|
||
|
|
||
|
from sympy.core.add import Add
|
||
|
from sympy.core.mul import Mul
|
||
|
from sympy.core.singleton import S
|
||
|
from sympy.polys.domains.characteristiczero import CharacteristicZero
|
||
|
from sympy.polys.domains.field import Field
|
||
|
from sympy.polys.domains.simpledomain import SimpleDomain
|
||
|
from sympy.polys.polyclasses import ANP
|
||
|
from sympy.polys.polyerrors import CoercionFailed, DomainError, NotAlgebraic, IsomorphismFailed
|
||
|
from sympy.utilities import public
|
||
|
|
||
|
@public
|
||
|
class AlgebraicField(Field, CharacteristicZero, SimpleDomain):
|
||
|
r"""Algebraic number field :ref:`QQ(a)`
|
||
|
|
||
|
A :ref:`QQ(a)` domain represents an `algebraic number field`_
|
||
|
`\mathbb{Q}(a)` as a :py:class:`~.Domain` in the domain system (see
|
||
|
:ref:`polys-domainsintro`).
|
||
|
|
||
|
A :py:class:`~.Poly` created from an expression involving `algebraic
|
||
|
numbers`_ will treat the algebraic numbers as generators if the generators
|
||
|
argument is not specified.
|
||
|
|
||
|
>>> from sympy import Poly, Symbol, sqrt
|
||
|
>>> x = Symbol('x')
|
||
|
>>> Poly(x**2 + sqrt(2))
|
||
|
Poly(x**2 + (sqrt(2)), x, sqrt(2), domain='ZZ')
|
||
|
|
||
|
That is a multivariate polynomial with ``sqrt(2)`` treated as one of the
|
||
|
generators (variables). If the generators are explicitly specified then
|
||
|
``sqrt(2)`` will be considered to be a coefficient but by default the
|
||
|
:ref:`EX` domain is used. To make a :py:class:`~.Poly` with a :ref:`QQ(a)`
|
||
|
domain the argument ``extension=True`` can be given.
|
||
|
|
||
|
>>> Poly(x**2 + sqrt(2), x)
|
||
|
Poly(x**2 + sqrt(2), x, domain='EX')
|
||
|
>>> Poly(x**2 + sqrt(2), x, extension=True)
|
||
|
Poly(x**2 + sqrt(2), x, domain='QQ<sqrt(2)>')
|
||
|
|
||
|
A generator of the algebraic field extension can also be specified
|
||
|
explicitly which is particularly useful if the coefficients are all
|
||
|
rational but an extension field is needed (e.g. to factor the
|
||
|
polynomial).
|
||
|
|
||
|
>>> Poly(x**2 + 1)
|
||
|
Poly(x**2 + 1, x, domain='ZZ')
|
||
|
>>> Poly(x**2 + 1, extension=sqrt(2))
|
||
|
Poly(x**2 + 1, x, domain='QQ<sqrt(2)>')
|
||
|
|
||
|
It is possible to factorise a polynomial over a :ref:`QQ(a)` domain using
|
||
|
the ``extension`` argument to :py:func:`~.factor` or by specifying the domain
|
||
|
explicitly.
|
||
|
|
||
|
>>> from sympy import factor, QQ
|
||
|
>>> factor(x**2 - 2)
|
||
|
x**2 - 2
|
||
|
>>> factor(x**2 - 2, extension=sqrt(2))
|
||
|
(x - sqrt(2))*(x + sqrt(2))
|
||
|
>>> factor(x**2 - 2, domain='QQ<sqrt(2)>')
|
||
|
(x - sqrt(2))*(x + sqrt(2))
|
||
|
>>> factor(x**2 - 2, domain=QQ.algebraic_field(sqrt(2)))
|
||
|
(x - sqrt(2))*(x + sqrt(2))
|
||
|
|
||
|
The ``extension=True`` argument can be used but will only create an
|
||
|
extension that contains the coefficients which is usually not enough to
|
||
|
factorise the polynomial.
|
||
|
|
||
|
>>> p = x**3 + sqrt(2)*x**2 - 2*x - 2*sqrt(2)
|
||
|
>>> factor(p) # treats sqrt(2) as a symbol
|
||
|
(x + sqrt(2))*(x**2 - 2)
|
||
|
>>> factor(p, extension=True)
|
||
|
(x - sqrt(2))*(x + sqrt(2))**2
|
||
|
>>> factor(x**2 - 2, extension=True) # all rational coefficients
|
||
|
x**2 - 2
|
||
|
|
||
|
It is also possible to use :ref:`QQ(a)` with the :py:func:`~.cancel`
|
||
|
and :py:func:`~.gcd` functions.
|
||
|
|
||
|
>>> from sympy import cancel, gcd
|
||
|
>>> cancel((x**2 - 2)/(x - sqrt(2)))
|
||
|
(x**2 - 2)/(x - sqrt(2))
|
||
|
>>> cancel((x**2 - 2)/(x - sqrt(2)), extension=sqrt(2))
|
||
|
x + sqrt(2)
|
||
|
>>> gcd(x**2 - 2, x - sqrt(2))
|
||
|
1
|
||
|
>>> gcd(x**2 - 2, x - sqrt(2), extension=sqrt(2))
|
||
|
x - sqrt(2)
|
||
|
|
||
|
When using the domain directly :ref:`QQ(a)` can be used as a constructor
|
||
|
to create instances which then support the operations ``+,-,*,**,/``. The
|
||
|
:py:meth:`~.Domain.algebraic_field` method is used to construct a
|
||
|
particular :ref:`QQ(a)` domain. The :py:meth:`~.Domain.from_sympy` method
|
||
|
can be used to create domain elements from normal SymPy expressions.
|
||
|
|
||
|
>>> K = QQ.algebraic_field(sqrt(2))
|
||
|
>>> K
|
||
|
QQ<sqrt(2)>
|
||
|
>>> xk = K.from_sympy(3 + 4*sqrt(2))
|
||
|
>>> xk # doctest: +SKIP
|
||
|
ANP([4, 3], [1, 0, -2], QQ)
|
||
|
|
||
|
Elements of :ref:`QQ(a)` are instances of :py:class:`~.ANP` which have
|
||
|
limited printing support. The raw display shows the internal
|
||
|
representation of the element as the list ``[4, 3]`` representing the
|
||
|
coefficients of ``1`` and ``sqrt(2)`` for this element in the form
|
||
|
``a * sqrt(2) + b * 1`` where ``a`` and ``b`` are elements of :ref:`QQ`.
|
||
|
The minimal polynomial for the generator ``(x**2 - 2)`` is also shown in
|
||
|
the :ref:`dup-representation` as the list ``[1, 0, -2]``. We can use
|
||
|
:py:meth:`~.Domain.to_sympy` to get a better printed form for the
|
||
|
elements and to see the results of operations.
|
||
|
|
||
|
>>> xk = K.from_sympy(3 + 4*sqrt(2))
|
||
|
>>> yk = K.from_sympy(2 + 3*sqrt(2))
|
||
|
>>> xk * yk # doctest: +SKIP
|
||
|
ANP([17, 30], [1, 0, -2], QQ)
|
||
|
>>> K.to_sympy(xk * yk)
|
||
|
17*sqrt(2) + 30
|
||
|
>>> K.to_sympy(xk + yk)
|
||
|
5 + 7*sqrt(2)
|
||
|
>>> K.to_sympy(xk ** 2)
|
||
|
24*sqrt(2) + 41
|
||
|
>>> K.to_sympy(xk / yk)
|
||
|
sqrt(2)/14 + 9/7
|
||
|
|
||
|
Any expression representing an algebraic number can be used to generate
|
||
|
a :ref:`QQ(a)` domain provided its `minimal polynomial`_ can be computed.
|
||
|
The function :py:func:`~.minpoly` function is used for this.
|
||
|
|
||
|
>>> from sympy import exp, I, pi, minpoly
|
||
|
>>> g = exp(2*I*pi/3)
|
||
|
>>> g
|
||
|
exp(2*I*pi/3)
|
||
|
>>> g.is_algebraic
|
||
|
True
|
||
|
>>> minpoly(g, x)
|
||
|
x**2 + x + 1
|
||
|
>>> factor(x**3 - 1, extension=g)
|
||
|
(x - 1)*(x - exp(2*I*pi/3))*(x + 1 + exp(2*I*pi/3))
|
||
|
|
||
|
It is also possible to make an algebraic field from multiple extension
|
||
|
elements.
|
||
|
|
||
|
>>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
|
||
|
>>> K
|
||
|
QQ<sqrt(2) + sqrt(3)>
|
||
|
>>> p = x**4 - 5*x**2 + 6
|
||
|
>>> factor(p)
|
||
|
(x**2 - 3)*(x**2 - 2)
|
||
|
>>> factor(p, domain=K)
|
||
|
(x - sqrt(2))*(x + sqrt(2))*(x - sqrt(3))*(x + sqrt(3))
|
||
|
>>> factor(p, extension=[sqrt(2), sqrt(3)])
|
||
|
(x - sqrt(2))*(x + sqrt(2))*(x - sqrt(3))*(x + sqrt(3))
|
||
|
|
||
|
Multiple extension elements are always combined together to make a single
|
||
|
`primitive element`_. In the case of ``[sqrt(2), sqrt(3)]`` the primitive
|
||
|
element chosen is ``sqrt(2) + sqrt(3)`` which is why the domain displays
|
||
|
as ``QQ<sqrt(2) + sqrt(3)>``. The minimal polynomial for the primitive
|
||
|
element is computed using the :py:func:`~.primitive_element` function.
|
||
|
|
||
|
>>> from sympy import primitive_element
|
||
|
>>> primitive_element([sqrt(2), sqrt(3)], x)
|
||
|
(x**4 - 10*x**2 + 1, [1, 1])
|
||
|
>>> minpoly(sqrt(2) + sqrt(3), x)
|
||
|
x**4 - 10*x**2 + 1
|
||
|
|
||
|
The extension elements that generate the domain can be accessed from the
|
||
|
domain using the :py:attr:`~.ext` and :py:attr:`~.orig_ext` attributes as
|
||
|
instances of :py:class:`~.AlgebraicNumber`. The minimal polynomial for
|
||
|
the primitive element as a :py:class:`~.DMP` instance is available as
|
||
|
:py:attr:`~.mod`.
|
||
|
|
||
|
>>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
|
||
|
>>> K
|
||
|
QQ<sqrt(2) + sqrt(3)>
|
||
|
>>> K.ext
|
||
|
sqrt(2) + sqrt(3)
|
||
|
>>> K.orig_ext
|
||
|
(sqrt(2), sqrt(3))
|
||
|
>>> K.mod
|
||
|
DMP([1, 0, -10, 0, 1], QQ, None)
|
||
|
|
||
|
The `discriminant`_ of the field can be obtained from the
|
||
|
:py:meth:`~.discriminant` method, and an `integral basis`_ from the
|
||
|
:py:meth:`~.integral_basis` method. The latter returns a list of
|
||
|
:py:class:`~.ANP` instances by default, but can be made to return instances
|
||
|
of :py:class:`~.Expr` or :py:class:`~.AlgebraicNumber` by passing a ``fmt``
|
||
|
argument. The maximal order, or ring of integers, of the field can also be
|
||
|
obtained from the :py:meth:`~.maximal_order` method, as a
|
||
|
:py:class:`~sympy.polys.numberfields.modules.Submodule`.
|
||
|
|
||
|
>>> zeta5 = exp(2*I*pi/5)
|
||
|
>>> K = QQ.algebraic_field(zeta5)
|
||
|
>>> K
|
||
|
QQ<exp(2*I*pi/5)>
|
||
|
>>> K.discriminant()
|
||
|
125
|
||
|
>>> K = QQ.algebraic_field(sqrt(5))
|
||
|
>>> K
|
||
|
QQ<sqrt(5)>
|
||
|
>>> K.integral_basis(fmt='sympy')
|
||
|
[1, 1/2 + sqrt(5)/2]
|
||
|
>>> K.maximal_order()
|
||
|
Submodule[[2, 0], [1, 1]]/2
|
||
|
|
||
|
The factorization of a rational prime into prime ideals of the field is
|
||
|
computed by the :py:meth:`~.primes_above` method, which returns a list
|
||
|
of :py:class:`~sympy.polys.numberfields.primes.PrimeIdeal` instances.
|
||
|
|
||
|
>>> zeta7 = exp(2*I*pi/7)
|
||
|
>>> K = QQ.algebraic_field(zeta7)
|
||
|
>>> K
|
||
|
QQ<exp(2*I*pi/7)>
|
||
|
>>> K.primes_above(11)
|
||
|
[(11, _x**3 + 5*_x**2 + 4*_x - 1), (11, _x**3 - 4*_x**2 - 5*_x - 1)]
|
||
|
|
||
|
The Galois group of the Galois closure of the field can be computed (when
|
||
|
the minimal polynomial of the field is of sufficiently small degree).
|
||
|
|
||
|
>>> K.galois_group(by_name=True)[0]
|
||
|
S6TransitiveSubgroups.C6
|
||
|
|
||
|
Notes
|
||
|
=====
|
||
|
|
||
|
It is not currently possible to generate an algebraic extension over any
|
||
|
domain other than :ref:`QQ`. Ideally it would be possible to generate
|
||
|
extensions like ``QQ(x)(sqrt(x**2 - 2))``. This is equivalent to the
|
||
|
quotient ring ``QQ(x)[y]/(y**2 - x**2 + 2)`` and there are two
|
||
|
implementations of this kind of quotient ring/extension in the
|
||
|
:py:class:`~.QuotientRing` and :py:class:`~.MonogenicFiniteExtension`
|
||
|
classes. Each of those implementations needs some work to make them fully
|
||
|
usable though.
|
||
|
|
||
|
.. _algebraic number field: https://en.wikipedia.org/wiki/Algebraic_number_field
|
||
|
.. _algebraic numbers: https://en.wikipedia.org/wiki/Algebraic_number
|
||
|
.. _discriminant: https://en.wikipedia.org/wiki/Discriminant_of_an_algebraic_number_field
|
||
|
.. _integral basis: https://en.wikipedia.org/wiki/Algebraic_number_field#Integral_basis
|
||
|
.. _minimal polynomial: https://en.wikipedia.org/wiki/Minimal_polynomial_(field_theory)
|
||
|
.. _primitive element: https://en.wikipedia.org/wiki/Primitive_element_theorem
|
||
|
"""
|
||
|
|
||
|
dtype = ANP
|
||
|
|
||
|
is_AlgebraicField = is_Algebraic = True
|
||
|
is_Numerical = True
|
||
|
|
||
|
has_assoc_Ring = False
|
||
|
has_assoc_Field = True
|
||
|
|
||
|
def __init__(self, dom, *ext, alias=None):
|
||
|
r"""
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
dom : :py:class:`~.Domain`
|
||
|
The base field over which this is an extension field.
|
||
|
Currently only :ref:`QQ` is accepted.
|
||
|
|
||
|
*ext : One or more :py:class:`~.Expr`
|
||
|
Generators of the extension. These should be expressions that are
|
||
|
algebraic over `\mathbb{Q}`.
|
||
|
|
||
|
alias : str, :py:class:`~.Symbol`, None, optional (default=None)
|
||
|
If provided, this will be used as the alias symbol for the
|
||
|
primitive element of the :py:class:`~.AlgebraicField`.
|
||
|
If ``None``, while ``ext`` consists of exactly one
|
||
|
:py:class:`~.AlgebraicNumber`, its alias (if any) will be used.
|
||
|
"""
|
||
|
if not dom.is_QQ:
|
||
|
raise DomainError("ground domain must be a rational field")
|
||
|
|
||
|
from sympy.polys.numberfields import to_number_field
|
||
|
if len(ext) == 1 and isinstance(ext[0], tuple):
|
||
|
orig_ext = ext[0][1:]
|
||
|
else:
|
||
|
orig_ext = ext
|
||
|
|
||
|
if alias is None and len(ext) == 1:
|
||
|
alias = getattr(ext[0], 'alias', None)
|
||
|
|
||
|
self.orig_ext = orig_ext
|
||
|
"""
|
||
|
Original elements given to generate the extension.
|
||
|
|
||
|
>>> from sympy import QQ, sqrt
|
||
|
>>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
|
||
|
>>> K.orig_ext
|
||
|
(sqrt(2), sqrt(3))
|
||
|
"""
|
||
|
|
||
|
self.ext = to_number_field(ext, alias=alias)
|
||
|
"""
|
||
|
Primitive element used for the extension.
|
||
|
|
||
|
>>> from sympy import QQ, sqrt
|
||
|
>>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
|
||
|
>>> K.ext
|
||
|
sqrt(2) + sqrt(3)
|
||
|
"""
|
||
|
|
||
|
self.mod = self.ext.minpoly.rep
|
||
|
"""
|
||
|
Minimal polynomial for the primitive element of the extension.
|
||
|
|
||
|
>>> from sympy import QQ, sqrt
|
||
|
>>> K = QQ.algebraic_field(sqrt(2))
|
||
|
>>> K.mod
|
||
|
DMP([1, 0, -2], QQ, None)
|
||
|
"""
|
||
|
|
||
|
self.domain = self.dom = dom
|
||
|
|
||
|
self.ngens = 1
|
||
|
self.symbols = self.gens = (self.ext,)
|
||
|
self.unit = self([dom(1), dom(0)])
|
||
|
|
||
|
self.zero = self.dtype.zero(self.mod.rep, dom)
|
||
|
self.one = self.dtype.one(self.mod.rep, dom)
|
||
|
|
||
|
self._maximal_order = None
|
||
|
self._discriminant = None
|
||
|
self._nilradicals_mod_p = {}
|
||
|
|
||
|
def new(self, element):
|
||
|
return self.dtype(element, self.mod.rep, self.dom)
|
||
|
|
||
|
def __str__(self):
|
||
|
return str(self.dom) + '<' + str(self.ext) + '>'
|
||
|
|
||
|
def __hash__(self):
|
||
|
return hash((self.__class__.__name__, self.dtype, self.dom, self.ext))
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
"""Returns ``True`` if two domains are equivalent. """
|
||
|
return isinstance(other, AlgebraicField) and \
|
||
|
self.dtype == other.dtype and self.ext == other.ext
|
||
|
|
||
|
def algebraic_field(self, *extension, alias=None):
|
||
|
r"""Returns an algebraic field, i.e. `\mathbb{Q}(\alpha, \ldots)`. """
|
||
|
return AlgebraicField(self.dom, *((self.ext,) + extension), alias=alias)
|
||
|
|
||
|
def to_alg_num(self, a):
|
||
|
"""Convert ``a`` of ``dtype`` to an :py:class:`~.AlgebraicNumber`. """
|
||
|
return self.ext.field_element(a)
|
||
|
|
||
|
def to_sympy(self, a):
|
||
|
"""Convert ``a`` of ``dtype`` to a SymPy object. """
|
||
|
# Precompute a converter to be reused:
|
||
|
if not hasattr(self, '_converter'):
|
||
|
self._converter = _make_converter(self)
|
||
|
|
||
|
return self._converter(a)
|
||
|
|
||
|
def from_sympy(self, a):
|
||
|
"""Convert SymPy's expression to ``dtype``. """
|
||
|
try:
|
||
|
return self([self.dom.from_sympy(a)])
|
||
|
except CoercionFailed:
|
||
|
pass
|
||
|
|
||
|
from sympy.polys.numberfields import to_number_field
|
||
|
|
||
|
try:
|
||
|
return self(to_number_field(a, self.ext).native_coeffs())
|
||
|
except (NotAlgebraic, IsomorphismFailed):
|
||
|
raise CoercionFailed(
|
||
|
"%s is not a valid algebraic number in %s" % (a, self))
|
||
|
|
||
|
def from_ZZ(K1, a, K0):
|
||
|
"""Convert a Python ``int`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def from_ZZ_python(K1, a, K0):
|
||
|
"""Convert a Python ``int`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def from_QQ(K1, a, K0):
|
||
|
"""Convert a Python ``Fraction`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def from_QQ_python(K1, a, K0):
|
||
|
"""Convert a Python ``Fraction`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def from_ZZ_gmpy(K1, a, K0):
|
||
|
"""Convert a GMPY ``mpz`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def from_QQ_gmpy(K1, a, K0):
|
||
|
"""Convert a GMPY ``mpq`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def from_RealField(K1, a, K0):
|
||
|
"""Convert a mpmath ``mpf`` object to ``dtype``. """
|
||
|
return K1(K1.dom.convert(a, K0))
|
||
|
|
||
|
def get_ring(self):
|
||
|
"""Returns a ring associated with ``self``. """
|
||
|
raise DomainError('there is no ring associated with %s' % self)
|
||
|
|
||
|
def is_positive(self, a):
|
||
|
"""Returns True if ``a`` is positive. """
|
||
|
return self.dom.is_positive(a.LC())
|
||
|
|
||
|
def is_negative(self, a):
|
||
|
"""Returns True if ``a`` is negative. """
|
||
|
return self.dom.is_negative(a.LC())
|
||
|
|
||
|
def is_nonpositive(self, a):
|
||
|
"""Returns True if ``a`` is non-positive. """
|
||
|
return self.dom.is_nonpositive(a.LC())
|
||
|
|
||
|
def is_nonnegative(self, a):
|
||
|
"""Returns True if ``a`` is non-negative. """
|
||
|
return self.dom.is_nonnegative(a.LC())
|
||
|
|
||
|
def numer(self, a):
|
||
|
"""Returns numerator of ``a``. """
|
||
|
return a
|
||
|
|
||
|
def denom(self, a):
|
||
|
"""Returns denominator of ``a``. """
|
||
|
return self.one
|
||
|
|
||
|
def from_AlgebraicField(K1, a, K0):
|
||
|
"""Convert AlgebraicField element 'a' to another AlgebraicField """
|
||
|
return K1.from_sympy(K0.to_sympy(a))
|
||
|
|
||
|
def from_GaussianIntegerRing(K1, a, K0):
|
||
|
"""Convert a GaussianInteger element 'a' to ``dtype``. """
|
||
|
return K1.from_sympy(K0.to_sympy(a))
|
||
|
|
||
|
def from_GaussianRationalField(K1, a, K0):
|
||
|
"""Convert a GaussianRational element 'a' to ``dtype``. """
|
||
|
return K1.from_sympy(K0.to_sympy(a))
|
||
|
|
||
|
def _do_round_two(self):
|
||
|
from sympy.polys.numberfields.basis import round_two
|
||
|
ZK, dK = round_two(self, radicals=self._nilradicals_mod_p)
|
||
|
self._maximal_order = ZK
|
||
|
self._discriminant = dK
|
||
|
|
||
|
def maximal_order(self):
|
||
|
"""
|
||
|
Compute the maximal order, or ring of integers, of the field.
|
||
|
|
||
|
Returns
|
||
|
=======
|
||
|
|
||
|
:py:class:`~sympy.polys.numberfields.modules.Submodule`.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
integral_basis
|
||
|
|
||
|
"""
|
||
|
if self._maximal_order is None:
|
||
|
self._do_round_two()
|
||
|
return self._maximal_order
|
||
|
|
||
|
def integral_basis(self, fmt=None):
|
||
|
r"""
|
||
|
Get an integral basis for the field.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
fmt : str, None, optional (default=None)
|
||
|
If ``None``, return a list of :py:class:`~.ANP` instances.
|
||
|
If ``"sympy"``, convert each element of the list to an
|
||
|
:py:class:`~.Expr`, using ``self.to_sympy()``.
|
||
|
If ``"alg"``, convert each element of the list to an
|
||
|
:py:class:`~.AlgebraicNumber`, using ``self.to_alg_num()``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import QQ, AlgebraicNumber, sqrt
|
||
|
>>> alpha = AlgebraicNumber(sqrt(5), alias='alpha')
|
||
|
>>> k = QQ.algebraic_field(alpha)
|
||
|
>>> B0 = k.integral_basis()
|
||
|
>>> B1 = k.integral_basis(fmt='sympy')
|
||
|
>>> B2 = k.integral_basis(fmt='alg')
|
||
|
>>> print(B0[1]) # doctest: +SKIP
|
||
|
ANP([mpq(1,2), mpq(1,2)], [mpq(1,1), mpq(0,1), mpq(-5,1)], QQ)
|
||
|
>>> print(B1[1])
|
||
|
1/2 + alpha/2
|
||
|
>>> print(B2[1])
|
||
|
alpha/2 + 1/2
|
||
|
|
||
|
In the last two cases we get legible expressions, which print somewhat
|
||
|
differently because of the different types involved:
|
||
|
|
||
|
>>> print(type(B1[1]))
|
||
|
<class 'sympy.core.add.Add'>
|
||
|
>>> print(type(B2[1]))
|
||
|
<class 'sympy.core.numbers.AlgebraicNumber'>
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
to_sympy
|
||
|
to_alg_num
|
||
|
maximal_order
|
||
|
"""
|
||
|
ZK = self.maximal_order()
|
||
|
M = ZK.QQ_matrix
|
||
|
n = M.shape[1]
|
||
|
B = [self.new(list(reversed(M[:, j].flat()))) for j in range(n)]
|
||
|
if fmt == 'sympy':
|
||
|
return [self.to_sympy(b) for b in B]
|
||
|
elif fmt == 'alg':
|
||
|
return [self.to_alg_num(b) for b in B]
|
||
|
return B
|
||
|
|
||
|
def discriminant(self):
|
||
|
"""Get the discriminant of the field."""
|
||
|
if self._discriminant is None:
|
||
|
self._do_round_two()
|
||
|
return self._discriminant
|
||
|
|
||
|
def primes_above(self, p):
|
||
|
"""Compute the prime ideals lying above a given rational prime *p*."""
|
||
|
from sympy.polys.numberfields.primes import prime_decomp
|
||
|
ZK = self.maximal_order()
|
||
|
dK = self.discriminant()
|
||
|
rad = self._nilradicals_mod_p.get(p)
|
||
|
return prime_decomp(p, ZK=ZK, dK=dK, radical=rad)
|
||
|
|
||
|
def galois_group(self, by_name=False, max_tries=30, randomize=False):
|
||
|
"""
|
||
|
Compute the Galois group of the Galois closure of this field.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
If the field is Galois, the order of the group will equal the degree
|
||
|
of the field:
|
||
|
|
||
|
>>> from sympy import QQ
|
||
|
>>> from sympy.abc import x
|
||
|
>>> k = QQ.alg_field_from_poly(x**4 + 1)
|
||
|
>>> G, _ = k.galois_group()
|
||
|
>>> G.order()
|
||
|
4
|
||
|
|
||
|
If the field is not Galois, then its Galois closure is a proper
|
||
|
extension, and the order of the Galois group will be greater than the
|
||
|
degree of the field:
|
||
|
|
||
|
>>> k = QQ.alg_field_from_poly(x**4 - 2)
|
||
|
>>> G, _ = k.galois_group()
|
||
|
>>> G.order()
|
||
|
8
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.polys.numberfields.galoisgroups.galois_group
|
||
|
|
||
|
"""
|
||
|
return self.ext.minpoly_of_element().galois_group(
|
||
|
by_name=by_name, max_tries=max_tries, randomize=randomize)
|
||
|
|
||
|
|
||
|
def _make_converter(K):
|
||
|
"""Construct the converter to convert back to Expr"""
|
||
|
# Precompute the effect of converting to SymPy and expanding expressions
|
||
|
# like (sqrt(2) + sqrt(3))**2. Asking Expr to do the expansion on every
|
||
|
# conversion from K to Expr is slow. Here we compute the expansions for
|
||
|
# each power of the generator and collect together the resulting algebraic
|
||
|
# terms and the rational coefficients into a matrix.
|
||
|
|
||
|
gen = K.ext.as_expr()
|
||
|
todom = K.dom.from_sympy
|
||
|
|
||
|
# We'll let Expr compute the expansions. We won't make any presumptions
|
||
|
# about what this results in except that it is QQ-linear in some terms
|
||
|
# that we will call algebraics. The final result will be expressed in
|
||
|
# terms of those.
|
||
|
powers = [S.One, gen]
|
||
|
for n in range(2, K.mod.degree()):
|
||
|
powers.append((gen * powers[-1]).expand())
|
||
|
|
||
|
# Collect the rational coefficients and algebraic Expr that can
|
||
|
# map the ANP coefficients into an expanded SymPy expression
|
||
|
terms = [dict(t.as_coeff_Mul()[::-1] for t in Add.make_args(p)) for p in powers]
|
||
|
algebraics = set().union(*terms)
|
||
|
matrix = [[todom(t.get(a, S.Zero)) for t in terms] for a in algebraics]
|
||
|
|
||
|
# Create a function to do the conversion efficiently:
|
||
|
|
||
|
def converter(a):
|
||
|
"""Convert a to Expr using converter"""
|
||
|
ai = a.rep[::-1]
|
||
|
tosympy = K.dom.to_sympy
|
||
|
coeffs_dom = [sum(mij*aj for mij, aj in zip(mi, ai)) for mi in matrix]
|
||
|
coeffs_sympy = [tosympy(c) for c in coeffs_dom]
|
||
|
res = Add(*(Mul(c, a) for c, a in zip(coeffs_sympy, algebraics)))
|
||
|
return res
|
||
|
|
||
|
return converter
|