I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,27 @@
"""Computational algebraic field theory. """
__all__ = [
'minpoly', 'minimal_polynomial',
'field_isomorphism', 'primitive_element', 'to_number_field',
'isolate',
'round_two',
'prime_decomp', 'prime_valuation',
'galois_group',
]
from .minpoly import minpoly, minimal_polynomial
from .subfield import field_isomorphism, primitive_element, to_number_field
from .utilities import isolate
from .basis import round_two
from .primes import prime_decomp, prime_valuation
from .galoisgroups import galois_group

View File

@ -0,0 +1,246 @@
"""Computing integral bases for number fields. """
from sympy.polys.polytools import Poly
from sympy.polys.domains.algebraicfield import AlgebraicField
from sympy.polys.domains.integerring import ZZ
from sympy.polys.domains.rationalfield import QQ
from sympy.utilities.decorator import public
from .modules import ModuleEndomorphism, ModuleHomomorphism, PowerBasis
from .utilities import extract_fundamental_discriminant
def _apply_Dedekind_criterion(T, p):
r"""
Apply the "Dedekind criterion" to test whether the order needs to be
enlarged relative to a given prime *p*.
"""
x = T.gen
T_bar = Poly(T, modulus=p)
lc, fl = T_bar.factor_list()
assert lc == 1
g_bar = Poly(1, x, modulus=p)
for ti_bar, _ in fl:
g_bar *= ti_bar
h_bar = T_bar // g_bar
g = Poly(g_bar, domain=ZZ)
h = Poly(h_bar, domain=ZZ)
f = (g * h - T) // p
f_bar = Poly(f, modulus=p)
Z_bar = f_bar
for b in [g_bar, h_bar]:
Z_bar = Z_bar.gcd(b)
U_bar = T_bar // Z_bar
m = Z_bar.degree()
return U_bar, m
def nilradical_mod_p(H, p, q=None):
r"""
Compute the nilradical mod *p* for a given order *H*, and prime *p*.
Explanation
===========
This is the ideal $I$ in $H/pH$ consisting of all elements some positive
power of which is zero in this quotient ring, i.e. is a multiple of *p*.
Parameters
==========
H : :py:class:`~.Submodule`
The given order.
p : int
The rational prime.
q : int, optional
If known, the smallest power of *p* that is $>=$ the dimension of *H*.
If not provided, we compute it here.
Returns
=======
:py:class:`~.Module` representing the nilradical mod *p* in *H*.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
(See Lemma 6.1.6.)
"""
n = H.n
if q is None:
q = p
while q < n:
q *= p
phi = ModuleEndomorphism(H, lambda x: x**q)
return phi.kernel(modulus=p)
def _second_enlargement(H, p, q):
r"""
Perform the second enlargement in the Round Two algorithm.
"""
Ip = nilradical_mod_p(H, p, q=q)
B = H.parent.submodule_from_matrix(H.matrix * Ip.matrix, denom=H.denom)
C = B + p*H
E = C.endomorphism_ring()
phi = ModuleHomomorphism(H, E, lambda x: E.inner_endomorphism(x))
gamma = phi.kernel(modulus=p)
G = H.parent.submodule_from_matrix(H.matrix * gamma.matrix, denom=H.denom * p)
H1 = G + H
return H1, Ip
@public
def round_two(T, radicals=None):
r"""
Zassenhaus's "Round 2" algorithm.
Explanation
===========
Carry out Zassenhaus's "Round 2" algorithm on an irreducible polynomial
*T* over :ref:`ZZ` or :ref:`QQ`. This computes an integral basis and the
discriminant for the field $K = \mathbb{Q}[x]/(T(x))$.
Alternatively, you may pass an :py:class:`~.AlgebraicField` instance, in
place of the polynomial *T*, in which case the algorithm is applied to the
minimal polynomial for the field's primitive element.
Ordinarily this function need not be called directly, as one can instead
access the :py:meth:`~.AlgebraicField.maximal_order`,
:py:meth:`~.AlgebraicField.integral_basis`, and
:py:meth:`~.AlgebraicField.discriminant` methods of an
:py:class:`~.AlgebraicField`.
Examples
========
Working through an AlgebraicField:
>>> from sympy import Poly, QQ
>>> from sympy.abc import x
>>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
>>> K = QQ.alg_field_from_poly(T, "theta")
>>> print(K.maximal_order())
Submodule[[2, 0, 0], [0, 2, 0], [0, 1, 1]]/2
>>> print(K.discriminant())
-503
>>> print(K.integral_basis(fmt='sympy'))
[1, theta, theta/2 + theta**2/2]
Calling directly:
>>> from sympy import Poly
>>> from sympy.abc import x
>>> from sympy.polys.numberfields.basis import round_two
>>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
>>> print(round_two(T))
(Submodule[[2, 0, 0], [0, 2, 0], [0, 1, 1]]/2, -503)
The nilradicals mod $p$ that are sometimes computed during the Round Two
algorithm may be useful in further calculations. Pass a dictionary under
`radicals` to receive these:
>>> T = Poly(x**3 + 3*x**2 + 5)
>>> rad = {}
>>> ZK, dK = round_two(T, radicals=rad)
>>> print(rad)
{3: Submodule[[-1, 1, 0], [-1, 0, 1]]}
Parameters
==========
T : :py:class:`~.Poly`, :py:class:`~.AlgebraicField`
Either (1) the irreducible polynomial over :ref:`ZZ` or :ref:`QQ`
defining the number field, or (2) an :py:class:`~.AlgebraicField`
representing the number field itself.
radicals : dict, optional
This is a way for any $p$-radicals (if computed) to be returned by
reference. If desired, pass an empty dictionary. If the algorithm
reaches the point where it computes the nilradical mod $p$ of the ring
of integers $Z_K$, then an $\mathbb{F}_p$-basis for this ideal will be
stored in this dictionary under the key ``p``. This can be useful for
other algorithms, such as prime decomposition.
Returns
=======
Pair ``(ZK, dK)``, where:
``ZK`` is a :py:class:`~sympy.polys.numberfields.modules.Submodule`
representing the maximal order.
``dK`` is the discriminant of the field $K = \mathbb{Q}[x]/(T(x))$.
See Also
========
.AlgebraicField.maximal_order
.AlgebraicField.integral_basis
.AlgebraicField.discriminant
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
"""
K = None
if isinstance(T, AlgebraicField):
K, T = T, T.ext.minpoly_of_element()
if ( not T.is_univariate
or not T.is_irreducible
or T.domain not in [ZZ, QQ]):
raise ValueError('Round 2 requires an irreducible univariate polynomial over ZZ or QQ.')
T, _ = T.make_monic_over_integers_by_scaling_roots()
n = T.degree()
D = T.discriminant()
D_modulus = ZZ.from_sympy(abs(D))
# D must be 0 or 1 mod 4 (see Cohen Sec 4.4), which ensures we can write
# it in the form D = D_0 * F**2, where D_0 is 1 or a fundamental discriminant.
_, F = extract_fundamental_discriminant(D)
Ztheta = PowerBasis(K or T)
H = Ztheta.whole_submodule()
nilrad = None
while F:
# Next prime:
p, e = F.popitem()
U_bar, m = _apply_Dedekind_criterion(T, p)
if m == 0:
continue
# For a given prime p, the first enlargement of the order spanned by
# the current basis can be done in a simple way:
U = Ztheta.element_from_poly(Poly(U_bar, domain=ZZ))
# TODO:
# Theory says only first m columns of the U//p*H term below are needed.
# Could be slightly more efficient to use only those. Maybe `Submodule`
# class should support a slice operator?
H = H.add(U // p * H, hnf_modulus=D_modulus)
if e <= m:
continue
# A second, and possibly more, enlargements for p will be needed.
# These enlargements require a more involved procedure.
q = p
while q < n:
q *= p
H1, nilrad = _second_enlargement(H, p, q)
while H1 != H:
H = H1
H1, nilrad = _second_enlargement(H, p, q)
# Note: We do not store all nilradicals mod p, only the very last. This is
# because, unless computed against the entire integral basis, it might not
# be accurate. (In other words, if H was not already equal to ZK when we
# passed it to `_second_enlargement`, then we can't trust the nilradical
# so computed.) Example: if T(x) = x ** 3 + 15 * x ** 2 - 9 * x + 13, then
# F is divisible by 2, 3, and 7, and the nilradical mod 2 as computed above
# will not be accurate for the full, maximal order ZK.
if nilrad is not None and isinstance(radicals, dict):
radicals[p] = nilrad
ZK = H
# Pre-set expensive boolean properties which we already know to be true:
ZK._starts_with_unity = True
ZK._is_sq_maxrank_HNF = True
dK = (D * ZK.matrix.det() ** 2) // ZK.denom ** (2 * n)
return ZK, dK

View File

@ -0,0 +1,54 @@
"""Special exception classes for numberfields. """
class ClosureFailure(Exception):
r"""
Signals that a :py:class:`ModuleElement` which we tried to represent in a
certain :py:class:`Module` cannot in fact be represented there.
Examples
========
>>> from sympy.polys import Poly, cyclotomic_poly, ZZ
>>> from sympy.polys.matrices import DomainMatrix
>>> from sympy.polys.numberfields.modules import PowerBasis, to_col
>>> T = Poly(cyclotomic_poly(5))
>>> A = PowerBasis(T)
>>> B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
Because we are in a cyclotomic field, the power basis ``A`` is an integral
basis, and the submodule ``B`` is just the ideal $(2)$. Therefore ``B`` can
represent an element having all even coefficients over the power basis:
>>> a1 = A(to_col([2, 4, 6, 8]))
>>> print(B.represent(a1))
DomainMatrix([[1], [2], [3], [4]], (4, 1), ZZ)
but ``B`` cannot represent an element with an odd coefficient:
>>> a2 = A(to_col([1, 2, 2, 2]))
>>> B.represent(a2)
Traceback (most recent call last):
...
ClosureFailure: Element in QQ-span but not ZZ-span of this basis.
"""
pass
class StructureError(Exception):
r"""
Represents cases in which an algebraic structure was expected to have a
certain property, or be of a certain type, but was not.
"""
pass
class MissingUnityError(StructureError):
r"""Structure should contain a unity element but does not."""
pass
__all__ = [
'ClosureFailure', 'StructureError', 'MissingUnityError',
]

View File

@ -0,0 +1,676 @@
r"""
Galois resolvents
Each of the functions in ``sympy.polys.numberfields.galoisgroups`` that
computes Galois groups for a particular degree $n$ uses resolvents. Given the
polynomial $T$ whose Galois group is to be computed, a resolvent is a
polynomial $R$ whose roots are defined as functions of the roots of $T$.
One way to compute the coefficients of $R$ is by approximating the roots of $T$
to sufficient precision. This module defines a :py:class:`~.Resolvent` class
that handles this job, determining the necessary precision, and computing $R$.
In some cases, the coefficients of $R$ are symmetric in the roots of $T$,
meaning they are equal to fixed functions of the coefficients of $T$. Therefore
another approach is to compute these functions once and for all, and record
them in a lookup table. This module defines code that can compute such tables.
The tables for polynomials $T$ of degrees 4 through 6, produced by this code,
are recorded in the resolvent_lookup.py module.
"""
from sympy.core.evalf import (
evalf, fastlog, _evalf_with_bounded_error, quad_to_mpmath,
)
from sympy.core.symbol import symbols, Dummy
from sympy.polys.densetools import dup_eval
from sympy.polys.domains import ZZ
from sympy.polys.orderings import lex
from sympy.polys.polyroots import preprocess_roots
from sympy.polys.polytools import Poly
from sympy.polys.rings import xring
from sympy.polys.specialpolys import symmetric_poly
from sympy.utilities.lambdify import lambdify
from mpmath import MPContext
from mpmath.libmp.libmpf import prec_to_dps
class GaloisGroupException(Exception):
...
class ResolventException(GaloisGroupException):
...
class Resolvent:
r"""
If $G$ is a subgroup of the symmetric group $S_n$,
$F$ a multivariate polynomial in $\mathbb{Z}[X_1, \ldots, X_n]$,
$H$ the stabilizer of $F$ in $G$ (i.e. the permutations $\sigma$ such that
$F(X_{\sigma(1)}, \ldots, X_{\sigma(n)}) = F(X_1, \ldots, X_n)$), and $s$
a set of left coset representatives of $H$ in $G$, then the resolvent
polynomial $R(Y)$ is the product over $\sigma \in s$ of
$Y - F(X_{\sigma(1)}, \ldots, X_{\sigma(n)})$.
For example, consider the resolvent for the form
$$F = X_0 X_2 + X_1 X_3$$
and the group $G = S_4$. In this case, the stabilizer $H$ is the dihedral
group $D4 = < (0123), (02) >$, and a set of representatives of $G/H$ is
$\{I, (01), (03)\}$. The resolvent can be constructed as follows:
>>> from sympy.combinatorics.permutations import Permutation
>>> from sympy.core.symbol import symbols
>>> from sympy.polys.numberfields.galoisgroups import Resolvent
>>> X = symbols('X0 X1 X2 X3')
>>> F = X[0]*X[2] + X[1]*X[3]
>>> s = [Permutation([0, 1, 2, 3]), Permutation([1, 0, 2, 3]),
... Permutation([3, 1, 2, 0])]
>>> R = Resolvent(F, X, s)
This resolvent has three roots, which are the conjugates of ``F`` under the
three permutations in ``s``:
>>> R.root_lambdas[0](*X)
X0*X2 + X1*X3
>>> R.root_lambdas[1](*X)
X0*X3 + X1*X2
>>> R.root_lambdas[2](*X)
X0*X1 + X2*X3
Resolvents are useful for computing Galois groups. Given a polynomial $T$
of degree $n$, we will use a resolvent $R$ where $Gal(T) \leq G \leq S_n$.
We will then want to substitute the roots of $T$ for the variables $X_i$
in $R$, and study things like the discriminant of $R$, and the way $R$
factors over $\mathbb{Q}$.
From the symmetry in $R$'s construction, and since $Gal(T) \leq G$, we know
from Galois theory that the coefficients of $R$ must lie in $\mathbb{Z}$.
This allows us to compute the coefficients of $R$ by approximating the
roots of $T$ to sufficient precision, plugging these values in for the
variables $X_i$ in the coefficient expressions of $R$, and then simply
rounding to the nearest integer.
In order to determine a sufficient precision for the roots of $T$, this
``Resolvent`` class imposes certain requirements on the form ``F``. It
could be possible to design a different ``Resolvent`` class, that made
different precision estimates, and different assumptions about ``F``.
``F`` must be homogeneous, and all terms must have unit coefficient.
Furthermore, if $r$ is the number of terms in ``F``, and $t$ the total
degree, and if $m$ is the number of conjugates of ``F``, i.e. the number
of permutations in ``s``, then we require that $m < r 2^t$. Again, it is
not impossible to work with forms ``F`` that violate these assumptions, but
this ``Resolvent`` class requires them.
Since determining the integer coefficients of the resolvent for a given
polynomial $T$ is one of the main problems this class solves, we take some
time to explain the precision bounds it uses.
The general problem is:
Given a multivariate polynomial $P \in \mathbb{Z}[X_1, \ldots, X_n]$, and a
bound $M \in \mathbb{R}_+$, compute an $\varepsilon > 0$ such that for any
complex numbers $a_1, \ldots, a_n$ with $|a_i| < M$, if the $a_i$ are
approximated to within an accuracy of $\varepsilon$ by $b_i$, that is,
$|a_i - b_i| < \varepsilon$ for $i = 1, \ldots, n$, then
$|P(a_1, \ldots, a_n) - P(b_1, \ldots, b_n)| < 1/2$. In other words, if it
is known that $P(a_1, \ldots, a_n) = c$ for some $c \in \mathbb{Z}$, then
$P(b_1, \ldots, b_n)$ can be rounded to the nearest integer in order to
determine $c$.
To derive our error bound, consider the monomial $xyz$. Defining
$d_i = b_i - a_i$, our error is
$|(a_1 + d_1)(a_2 + d_2)(a_3 + d_3) - a_1 a_2 a_3|$, which is bounded
above by $|(M + \varepsilon)^3 - M^3|$. Passing to a general monomial of
total degree $t$, this expression is bounded by
$M^{t-1}\varepsilon(t + 2^t\varepsilon/M)$ provided $\varepsilon < M$,
and by $(t+1)M^{t-1}\varepsilon$ provided $\varepsilon < M/2^t$.
But since our goal is to make the error less than $1/2$, we will choose
$\varepsilon < 1/(2(t+1)M^{t-1})$, which implies the condition that
$\varepsilon < M/2^t$, as long as $M \geq 2$.
Passing from the general monomial to the general polynomial is easy, by
scaling and summing error bounds.
In our specific case, we are given a homogeneous polynomial $F$ of
$r$ terms and total degree $t$, all of whose coefficients are $\pm 1$. We
are given the $m$ permutations that make the conjugates of $F$, and
we want to bound the error in the coefficients of the monic polynomial
$R(Y)$ having $F$ and its conjugates as roots (i.e. the resolvent).
For $j$ from $1$ to $m$, the coefficient of $Y^{m-j}$ in $R(Y)$ is the
$j$th elementary symmetric polynomial in the conjugates of $F$. This sums
the products of these conjugates, taken $j$ at a time, in all possible
combinations. There are $\binom{m}{j}$ such combinations, and each product
of $j$ conjugates of $F$ expands to a sum of $r^j$ terms, each of unit
coefficient, and total degree $jt$. An error bound for the $j$th coeff of
$R$ is therefore
$$\binom{m}{j} r^j (jt + 1) M^{jt - 1} \varepsilon$$
When our goal is to evaluate all the coefficients of $R$, we will want to
use the maximum of these error bounds. It is clear that this bound is
strictly increasing for $j$ up to the ceiling of $m/2$. After that point,
the first factor $\binom{m}{j}$ begins to decrease, while the others
continue to increase. However, the binomial coefficient never falls by more
than a factor of $1/m$ at a time, so our assumptions that $M \geq 2$ and
$m < r 2^t$ are enough to tell us that the constant coefficient of $R$,
i.e. that where $j = m$, has the largest error bound. Therefore we can use
$$r^m (mt + 1) M^{mt - 1} \varepsilon$$
as our error bound for all the coefficients.
Note that this bound is also (more than) adequate to determine whether any
of the roots of $R$ is an integer. Each of these roots is a single
conjugate of $F$, which contains less error than the trace, i.e. the
coefficient of $Y^{m - 1}$. By rounding the roots of $R$ to the nearest
integers, we therefore get all the candidates for integer roots of $R$. By
plugging these candidates into $R$, we can check whether any of them
actually is a root.
Note: We take the definition of resolvent from Cohen, but the error bound
is ours.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
(Def 6.3.2)
"""
def __init__(self, F, X, s):
r"""
Parameters
==========
F : :py:class:`~.Expr`
polynomial in the symbols in *X*
X : list of :py:class:`~.Symbol`
s : list of :py:class:`~.Permutation`
representing the cosets of the stabilizer of *F* in
some subgroup $G$ of $S_n$, where $n$ is the length of *X*.
"""
self.F = F
self.X = X
self.s = s
# Number of conjugates:
self.m = len(s)
# Total degree of F (computed below):
self.t = None
# Number of terms in F (computed below):
self.r = 0
for monom, coeff in Poly(F).terms():
if abs(coeff) != 1:
raise ResolventException('Resolvent class expects forms with unit coeffs')
t = sum(monom)
if t != self.t and self.t is not None:
raise ResolventException('Resolvent class expects homogeneous forms')
self.t = t
self.r += 1
m, t, r = self.m, self.t, self.r
if not m < r * 2**t:
raise ResolventException('Resolvent class expects m < r*2^t')
M = symbols('M')
# Precision sufficient for computing the coeffs of the resolvent:
self.coeff_prec_func = Poly(r**m*(m*t + 1)*M**(m*t - 1))
# Precision sufficient for checking whether any of the roots of the
# resolvent are integers:
self.root_prec_func = Poly(r*(t + 1)*M**(t - 1))
# The conjugates of F are the roots of the resolvent.
# For evaluating these to required numerical precisions, we need
# lambdified versions.
# Note: for a given permutation sigma, the conjugate (sigma F) is
# equivalent to lambda [sigma^(-1) X]: F.
self.root_lambdas = [
lambdify((~s[j])(X), F)
for j in range(self.m)
]
# For evaluating the coeffs, we'll also need lambdified versions of
# the elementary symmetric functions for degree m.
Y = symbols('Y')
R = symbols(' '.join(f'R{i}' for i in range(m)))
f = 1
for r in R:
f *= (Y - r)
C = Poly(f, Y).coeffs()
self.esf_lambdas = [lambdify(R, c) for c in C]
def get_prec(self, M, target='coeffs'):
r"""
For a given upper bound *M* on the magnitude of the complex numbers to
be plugged in for this resolvent's symbols, compute a sufficient
precision for evaluating those complex numbers, such that the
coefficients, or the integer roots, of the resolvent can be determined.
Parameters
==========
M : real number
Upper bound on magnitude of the complex numbers to be plugged in.
target : str, 'coeffs' or 'roots', default='coeffs'
Name the task for which a sufficient precision is desired.
This is either determining the coefficients of the resolvent
('coeffs') or determining its possible integer roots ('roots').
The latter may require significantly lower precision.
Returns
=======
int $m$
such that $2^{-m}$ is a sufficient upper bound on the
error in approximating the complex numbers to be plugged in.
"""
# As explained in the docstring for this class, our precision estimates
# require that M be at least 2.
M = max(M, 2)
f = self.coeff_prec_func if target == 'coeffs' else self.root_prec_func
r, _, _, _ = evalf(2*f(M), 1, {})
return fastlog(r) + 1
def approximate_roots_of_poly(self, T, target='coeffs'):
"""
Approximate the roots of a given polynomial *T* to sufficient precision
in order to evaluate this resolvent's coefficients, or determine
whether the resolvent has an integer root.
Parameters
==========
T : :py:class:`~.Poly`
target : str, 'coeffs' or 'roots', default='coeffs'
Set the approximation precision to be sufficient for the desired
task, which is either determining the coefficients of the resolvent
('coeffs') or determining its possible integer roots ('roots').
The latter may require significantly lower precision.
Returns
=======
list of elements of :ref:`CC`
"""
ctx = MPContext()
# Because sympy.polys.polyroots._integer_basis() is called when a CRootOf
# is formed, we proactively extract the integer basis now. This means that
# when we call T.all_roots(), every root will be a CRootOf, not a Mul
# of Integer*CRootOf.
coeff, T = preprocess_roots(T)
coeff = ctx.mpf(str(coeff))
scaled_roots = T.all_roots(radicals=False)
# Since we're going to be approximating the roots of T anyway, we can
# get a good upper bound on the magnitude of the roots by starting with
# a very low precision approx.
approx0 = [coeff * quad_to_mpmath(_evalf_with_bounded_error(r, m=0)) for r in scaled_roots]
# Here we add 1 to account for the possible error in our initial approximation.
M = max(abs(b) for b in approx0) + 1
m = self.get_prec(M, target=target)
n = fastlog(M._mpf_) + 1
p = m + n + 1
ctx.prec = p
d = prec_to_dps(p)
approx1 = [r.eval_approx(d, return_mpmath=True) for r in scaled_roots]
approx1 = [coeff*ctx.mpc(r) for r in approx1]
return approx1
@staticmethod
def round_mpf(a):
if isinstance(a, int):
return a
# If we use python's built-in `round()`, we lose precision.
# If we use `ZZ` directly, we may add or subtract 1.
#
# XXX: We have to convert to int before converting to ZZ because
# flint.fmpz cannot convert a mpmath mpf.
return ZZ(int(a.context.nint(a)))
def round_roots_to_integers_for_poly(self, T):
"""
For a given polynomial *T*, round the roots of this resolvent to the
nearest integers.
Explanation
===========
None of the integers returned by this method is guaranteed to be a
root of the resolvent; however, if the resolvent has any integer roots
(for the given polynomial *T*), then they must be among these.
If the coefficients of the resolvent are also desired, then this method
should not be used. Instead, use the ``eval_for_poly`` method. This
method may be significantly faster than ``eval_for_poly``.
Parameters
==========
T : :py:class:`~.Poly`
Returns
=======
dict
Keys are the indices of those permutations in ``self.s`` such that
the corresponding root did round to a rational integer.
Values are :ref:`ZZ`.
"""
approx_roots_of_T = self.approximate_roots_of_poly(T, target='roots')
approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas]
return {
i: self.round_mpf(r.real)
for i, r in enumerate(approx_roots_of_self)
if self.round_mpf(r.imag) == 0
}
def eval_for_poly(self, T, find_integer_root=False):
r"""
Compute the integer values of the coefficients of this resolvent, when
plugging in the roots of a given polynomial.
Parameters
==========
T : :py:class:`~.Poly`
find_integer_root : ``bool``, default ``False``
If ``True``, then also determine whether the resolvent has an
integer root, and return the first one found, along with its
index, i.e. the index of the permutation ``self.s[i]`` it
corresponds to.
Returns
=======
Tuple ``(R, a, i)``
``R`` is this resolvent as a dense univariate polynomial over
:ref:`ZZ`, i.e. a list of :ref:`ZZ`.
If *find_integer_root* was ``True``, then ``a`` and ``i`` are the
first integer root found, and its index, if one exists.
Otherwise ``a`` and ``i`` are both ``None``.
"""
approx_roots_of_T = self.approximate_roots_of_poly(T, target='coeffs')
approx_roots_of_self = [r(*approx_roots_of_T) for r in self.root_lambdas]
approx_coeffs_of_self = [c(*approx_roots_of_self) for c in self.esf_lambdas]
R = []
for c in approx_coeffs_of_self:
if self.round_mpf(c.imag) != 0:
# If precision was enough, this should never happen.
raise ResolventException(f"Got non-integer coeff for resolvent: {c}")
R.append(self.round_mpf(c.real))
a0, i0 = None, None
if find_integer_root:
for i, r in enumerate(approx_roots_of_self):
if self.round_mpf(r.imag) != 0:
continue
if not dup_eval(R, (a := self.round_mpf(r.real)), ZZ):
a0, i0 = a, i
break
return R, a0, i0
def wrap(text, width=80):
"""Line wrap a polynomial expression. """
out = ''
col = 0
for c in text:
if c == ' ' and col > width:
c, col = '\n', 0
else:
col += 1
out += c
return out
def s_vars(n):
"""Form the symbols s1, s2, ..., sn to stand for elem. symm. polys. """
return symbols([f's{i + 1}' for i in range(n)])
def sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=False):
"""
Compute the coefficients of a resolvent as functions of the coefficients of
the associated polynomial.
F must be a sparse polynomial.
"""
import time, sys
# Roots of resolvent as multivariate forms over vars X:
root_forms = [
F.compose(list(zip(X, sigma(X))))
for sigma in s
]
# Coeffs of resolvent (besides lead coeff of 1) as symmetric forms over vars X:
Y = [Dummy(f'Y{i}') for i in range(len(s))]
coeff_forms = []
for i in range(1, len(s) + 1):
if verbose:
print('----')
print(f'Computing symmetric poly of degree {i}...')
sys.stdout.flush()
t0 = time.time()
G = symmetric_poly(i, *Y)
t1 = time.time()
if verbose:
print(f'took {t1 - t0} seconds')
print('lambdifying...')
sys.stdout.flush()
t0 = time.time()
C = lambdify(Y, (-1)**i*G)
t1 = time.time()
if verbose:
print(f'took {t1 - t0} seconds')
sys.stdout.flush()
coeff_forms.append(C)
coeffs = []
for i, f in enumerate(coeff_forms):
if verbose:
print('----')
print(f'Plugging root forms into elem symm poly {i+1}...')
sys.stdout.flush()
t0 = time.time()
g = f(*root_forms)
t1 = time.time()
coeffs.append(g)
if verbose:
print(f'took {t1 - t0} seconds')
sys.stdout.flush()
# Now symmetrize these coeffs. This means recasting them as polynomials in
# the elementary symmetric polys over X.
symmetrized = []
symmetrization_times = []
ss = s_vars(len(X))
for i, A in list(enumerate(coeffs)):
if verbose:
print('-----')
print(f'Coeff {i+1}...')
sys.stdout.flush()
t0 = time.time()
B, rem, _ = A.symmetrize()
t1 = time.time()
if rem != 0:
msg = f"Got nonzero remainder {rem} for resolvent (F, X, s) = ({F}, {X}, {s})"
raise ResolventException(msg)
B_str = str(B.as_expr(*ss))
symmetrized.append(B_str)
symmetrization_times.append(t1 - t0)
if verbose:
print(wrap(B_str))
print(f'took {t1 - t0} seconds')
sys.stdout.flush()
return symmetrized, symmetrization_times
def define_resolvents():
"""Define all the resolvents for polys T of degree 4 through 6. """
from sympy.combinatorics.galois import PGL2F5
from sympy.combinatorics.permutations import Permutation
R4, X4 = xring("X0,X1,X2,X3", ZZ, lex)
X = X4
# The one resolvent used in `_galois_group_degree_4_lookup()`:
F40 = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2
s40 = [
Permutation(3),
Permutation(3)(0, 1),
Permutation(3)(0, 2),
Permutation(3)(0, 3),
Permutation(3)(1, 2),
Permutation(3)(2, 3),
]
# First resolvent used in `_galois_group_degree_4_root_approx()`:
F41 = X[0]*X[2] + X[1]*X[3]
s41 = [
Permutation(3),
Permutation(3)(0, 1),
Permutation(3)(0, 3)
]
R5, X5 = xring("X0,X1,X2,X3,X4", ZZ, lex)
X = X5
# First resolvent used in `_galois_group_degree_5_hybrid()`,
# and only one used in `_galois_group_degree_5_lookup_ext_factor()`:
F51 = ( X[0]**2*(X[1]*X[4] + X[2]*X[3])
+ X[1]**2*(X[2]*X[0] + X[3]*X[4])
+ X[2]**2*(X[3]*X[1] + X[4]*X[0])
+ X[3]**2*(X[4]*X[2] + X[0]*X[1])
+ X[4]**2*(X[0]*X[3] + X[1]*X[2]))
s51 = [
Permutation(4),
Permutation(4)(0, 1),
Permutation(4)(0, 2),
Permutation(4)(0, 3),
Permutation(4)(0, 4),
Permutation(4)(1, 4)
]
R6, X6 = xring("X0,X1,X2,X3,X4,X5", ZZ, lex)
X = X6
# First resolvent used in `_galois_group_degree_6_lookup()`:
H = PGL2F5()
term0 = X[0]**2*X[5]**2*(X[1]*X[4] + X[2]*X[3])
terms = {term0.compose(list(zip(X, s(X)))) for s in H.elements}
F61 = sum(terms)
s61 = [Permutation(5)] + [Permutation(5)(0, n) for n in range(1, 6)]
# Second resolvent used in `_galois_group_degree_6_lookup()`:
F62 = X[0]*X[1]*X[2] + X[3]*X[4]*X[5]
s62 = [Permutation(5)] + [
Permutation(5)(i, j + 3) for i in range(3) for j in range(3)
]
return {
(4, 0): (F40, X4, s40),
(4, 1): (F41, X4, s41),
(5, 1): (F51, X5, s51),
(6, 1): (F61, X6, s61),
(6, 2): (F62, X6, s62),
}
def generate_lambda_lookup(verbose=False, trial_run=False):
"""
Generate the whole lookup table of coeff lambdas, for all resolvents.
"""
jobs = define_resolvents()
lambda_lists = {}
total_time = 0
time_for_61 = 0
time_for_61_last = 0
for k, (F, X, s) in jobs.items():
symmetrized, times = sparse_symmetrize_resolvent_coeffs(F, X, s, verbose=verbose)
total_time += sum(times)
if k == (6, 1):
time_for_61 = sum(times)
time_for_61_last = times[-1]
sv = s_vars(len(X))
head = f'lambda {", ".join(str(v) for v in sv)}:'
lambda_lists[k] = ',\n '.join([
f'{head} ({wrap(f)})'
for f in symmetrized
])
if trial_run:
break
table = (
"# This table was generated by a call to\n"
"# `sympy.polys.numberfields.galois_resolvents.generate_lambda_lookup()`.\n"
f"# The entire job took {total_time:.2f}s.\n"
f"# Of this, Case (6, 1) took {time_for_61:.2f}s.\n"
f"# The final polynomial of Case (6, 1) alone took {time_for_61_last:.2f}s.\n"
"resolvent_coeff_lambdas = {\n")
for k, L in lambda_lists.items():
table += f" {k}: [\n"
table += " " + L + '\n'
table += " ],\n"
table += "}\n"
return table
def get_resolvent_by_lookup(T, number):
"""
Use the lookup table, to return a resolvent (as dup) for a given
polynomial *T*.
Parameters
==========
T : Poly
The polynomial whose resolvent is needed
number : int
For some degrees, there are multiple resolvents.
Use this to indicate which one you want.
Returns
=======
dup
"""
from sympy.polys.numberfields.resolvent_lookup import resolvent_coeff_lambdas
degree = T.degree()
L = resolvent_coeff_lambdas[(degree, number)]
T_coeffs = T.rep.to_list()[1:]
return [ZZ(1)] + [c(*T_coeffs) for c in L]
# Use
# (.venv) $ python -m sympy.polys.numberfields.galois_resolvents
# to reproduce the table found in resolvent_lookup.py
if __name__ == "__main__":
import sys
verbose = '-v' in sys.argv[1:]
trial_run = '-t' in sys.argv[1:]
table = generate_lambda_lookup(verbose=verbose, trial_run=trial_run)
print(table)

View File

@ -0,0 +1,623 @@
"""
Compute Galois groups of polynomials.
We use algorithms from [1], with some modifications to use lookup tables for
resolvents.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
"""
from collections import defaultdict
import random
from sympy.core.symbol import Dummy, symbols
from sympy.ntheory.primetest import is_square
from sympy.polys.domains import ZZ
from sympy.polys.densebasic import dup_random
from sympy.polys.densetools import dup_eval
from sympy.polys.euclidtools import dup_discriminant
from sympy.polys.factortools import dup_factor_list, dup_irreducible_p
from sympy.polys.numberfields.galois_resolvents import (
GaloisGroupException, get_resolvent_by_lookup, define_resolvents,
Resolvent,
)
from sympy.polys.numberfields.utilities import coeff_search
from sympy.polys.polytools import (Poly, poly_from_expr,
PolificationFailed, ComputationFailed)
from sympy.polys.sqfreetools import dup_sqf_p
from sympy.utilities import public
class MaxTriesException(GaloisGroupException):
...
def tschirnhausen_transformation(T, max_coeff=10, max_tries=30, history=None,
fixed_order=True):
r"""
Given a univariate, monic, irreducible polynomial over the integers, find
another such polynomial defining the same number field.
Explanation
===========
See Alg 6.3.4 of [1].
Parameters
==========
T : Poly
The given polynomial
max_coeff : int
When choosing a transformation as part of the process,
keep the coeffs between plus and minus this.
max_tries : int
Consider at most this many transformations.
history : set, None, optional (default=None)
Pass a set of ``Poly.rep``'s in order to prevent any of these
polynomials from being returned as the polynomial ``U`` i.e. the
transformation of the given polynomial *T*. The given poly *T* will
automatically be added to this set, before we try to find a new one.
fixed_order : bool, default True
If ``True``, work through candidate transformations A(x) in a fixed
order, from small coeffs to large, resulting in deterministic behavior.
If ``False``, the A(x) are chosen randomly, while still working our way
up from small coefficients to larger ones.
Returns
=======
Pair ``(A, U)``
``A`` and ``U`` are ``Poly``, ``A`` is the
transformation, and ``U`` is the transformed polynomial that defines
the same number field as *T*. The polynomial ``A`` maps the roots of
*T* to the roots of ``U``.
Raises
======
MaxTriesException
if could not find a polynomial before exceeding *max_tries*.
"""
X = Dummy('X')
n = T.degree()
if history is None:
history = set()
history.add(T.rep)
if fixed_order:
coeff_generators = {}
deg_coeff_sum = 3
current_degree = 2
def get_coeff_generator(degree):
gen = coeff_generators.get(degree, coeff_search(degree, 1))
coeff_generators[degree] = gen
return gen
for i in range(max_tries):
# We never use linear A(x), since applying a fixed linear transformation
# to all roots will only multiply the discriminant of T by a square
# integer. This will change nothing important. In particular, if disc(T)
# was zero before, it will still be zero now, and typically we apply
# the transformation in hopes of replacing T by a squarefree poly.
if fixed_order:
# If d is degree and c max coeff, we move through the dc-space
# along lines of constant sum. First d + c = 3 with (d, c) = (2, 1).
# Then d + c = 4 with (d, c) = (3, 1), (2, 2). Then d + c = 5 with
# (d, c) = (4, 1), (3, 2), (2, 3), and so forth. For a given (d, c)
# we go though all sets of coeffs where max = c, before moving on.
gen = get_coeff_generator(current_degree)
coeffs = next(gen)
m = max(abs(c) for c in coeffs)
if current_degree + m > deg_coeff_sum:
if current_degree == 2:
deg_coeff_sum += 1
current_degree = deg_coeff_sum - 1
else:
current_degree -= 1
gen = get_coeff_generator(current_degree)
coeffs = next(gen)
a = [ZZ(1)] + [ZZ(c) for c in coeffs]
else:
# We use a progressive coeff bound, up to the max specified, since it
# is preferable to succeed with smaller coeffs.
# Give each coeff bound five tries, before incrementing.
C = min(i//5 + 1, max_coeff)
d = random.randint(2, n - 1)
a = dup_random(d, -C, C, ZZ)
A = Poly(a, T.gen)
U = Poly(T.resultant(X - A), X)
if U.rep not in history and dup_sqf_p(U.rep.to_list(), ZZ):
return A, U
raise MaxTriesException
def has_square_disc(T):
"""Convenience to check if a Poly or dup has square discriminant. """
d = T.discriminant() if isinstance(T, Poly) else dup_discriminant(T, ZZ)
return is_square(d)
def _galois_group_degree_3(T, max_tries=30, randomize=False):
r"""
Compute the Galois group of a polynomial of degree 3.
Explanation
===========
Uses Prop 6.3.5 of [1].
"""
from sympy.combinatorics.galois import S3TransitiveSubgroups
return ((S3TransitiveSubgroups.A3, True) if has_square_disc(T)
else (S3TransitiveSubgroups.S3, False))
def _galois_group_degree_4_root_approx(T, max_tries=30, randomize=False):
r"""
Compute the Galois group of a polynomial of degree 4.
Explanation
===========
Follows Alg 6.3.7 of [1], using a pure root approximation approach.
"""
from sympy.combinatorics.permutations import Permutation
from sympy.combinatorics.galois import S4TransitiveSubgroups
X = symbols('X0 X1 X2 X3')
# We start by considering the resolvent for the form
# F = X0*X2 + X1*X3
# and the group G = S4. In this case, the stabilizer H is D4 = < (0123), (02) >,
# and a set of representatives of G/H is {I, (01), (03)}
F1 = X[0]*X[2] + X[1]*X[3]
s1 = [
Permutation(3),
Permutation(3)(0, 1),
Permutation(3)(0, 3)
]
R1 = Resolvent(F1, X, s1)
# In the second half of the algorithm (if we reach it), we use another
# form and set of coset representatives. However, we may need to permute
# them first, so cannot form their resolvent now.
F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2
s2_pre = [
Permutation(3),
Permutation(3)(0, 2)
]
history = set()
for i in range(max_tries):
if i > 0:
# If we're retrying, need a new polynomial T.
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
history=history,
fixed_order=not randomize)
R_dup, _, i0 = R1.eval_for_poly(T, find_integer_root=True)
# If R is not squarefree, must retry.
if not dup_sqf_p(R_dup, ZZ):
continue
# By Prop 6.3.1 of [1], Gal(T) is contained in A4 iff disc(T) is square.
sq_disc = has_square_disc(T)
if i0 is None:
# By Thm 6.3.3 of [1], Gal(T) is not conjugate to any subgroup of the
# stabilizer H = D4 that we chose. This means Gal(T) is either A4 or S4.
return ((S4TransitiveSubgroups.A4, True) if sq_disc
else (S4TransitiveSubgroups.S4, False))
# Gal(T) is conjugate to a subgroup of H = D4, so it is either V, C4
# or D4 itself.
if sq_disc:
# Neither C4 nor D4 is contained in A4, so Gal(T) must be V.
return (S4TransitiveSubgroups.V, True)
# Gal(T) can only be D4 or C4.
# We will now use our second resolvent, with G being that conjugate of D4 that
# Gal(T) is contained in. To determine the right conjugate, we will need
# the permutation corresponding to the integer root we found.
sigma = s1[i0]
# Applying sigma means permuting the args of F, and
# conjugating the set of coset representatives.
F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True)
s2 = [sigma*tau*sigma for tau in s2_pre]
R2 = Resolvent(F2, X, s2)
R_dup, _, _ = R2.eval_for_poly(T)
d = dup_discriminant(R_dup, ZZ)
# If d is zero (R has a repeated root), must retry.
if d == 0:
continue
if is_square(d):
return (S4TransitiveSubgroups.C4, False)
else:
return (S4TransitiveSubgroups.D4, False)
raise MaxTriesException
def _galois_group_degree_4_lookup(T, max_tries=30, randomize=False):
r"""
Compute the Galois group of a polynomial of degree 4.
Explanation
===========
Based on Alg 6.3.6 of [1], but uses resolvent coeff lookup.
"""
from sympy.combinatorics.galois import S4TransitiveSubgroups
history = set()
for i in range(max_tries):
R_dup = get_resolvent_by_lookup(T, 0)
if dup_sqf_p(R_dup, ZZ):
break
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
history=history,
fixed_order=not randomize)
else:
raise MaxTriesException
# Compute list L of degrees of irreducible factors of R, in increasing order:
fl = dup_factor_list(R_dup, ZZ)
L = sorted(sum([
[len(r) - 1] * e for r, e in fl[1]
], []))
if L == [6]:
return ((S4TransitiveSubgroups.A4, True) if has_square_disc(T)
else (S4TransitiveSubgroups.S4, False))
if L == [1, 1, 4]:
return (S4TransitiveSubgroups.C4, False)
if L == [2, 2, 2]:
return (S4TransitiveSubgroups.V, True)
assert L == [2, 4]
return (S4TransitiveSubgroups.D4, False)
def _galois_group_degree_5_hybrid(T, max_tries=30, randomize=False):
r"""
Compute the Galois group of a polynomial of degree 5.
Explanation
===========
Based on Alg 6.3.9 of [1], but uses a hybrid approach, combining resolvent
coeff lookup, with root approximation.
"""
from sympy.combinatorics.galois import S5TransitiveSubgroups
from sympy.combinatorics.permutations import Permutation
X5 = symbols("X0,X1,X2,X3,X4")
res = define_resolvents()
F51, _, s51 = res[(5, 1)]
F51 = F51.as_expr(*X5)
R51 = Resolvent(F51, X5, s51)
history = set()
reached_second_stage = False
for i in range(max_tries):
if i > 0:
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
history=history,
fixed_order=not randomize)
R51_dup = get_resolvent_by_lookup(T, 1)
if not dup_sqf_p(R51_dup, ZZ):
continue
# First stage
# If we have not yet reached the second stage, then the group still
# might be S5, A5, or M20, so must test for that.
if not reached_second_stage:
sq_disc = has_square_disc(T)
if dup_irreducible_p(R51_dup, ZZ):
return ((S5TransitiveSubgroups.A5, True) if sq_disc
else (S5TransitiveSubgroups.S5, False))
if not sq_disc:
return (S5TransitiveSubgroups.M20, False)
# Second stage
reached_second_stage = True
# R51 must have an integer root for T.
# To choose our second resolvent, we need to know which conjugate of
# F51 is a root.
rounded_roots = R51.round_roots_to_integers_for_poly(T)
# These are integers, and candidates to be roots of R51.
# We find the first one that actually is a root.
for permutation_index, candidate_root in rounded_roots.items():
if not dup_eval(R51_dup, candidate_root, ZZ):
break
X = X5
F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[4]**2 + X[4]*X[0]**2
s2_pre = [
Permutation(4),
Permutation(4)(0, 1)(2, 4)
]
i0 = permutation_index
sigma = s51[i0]
F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True)
s2 = [sigma*tau*sigma for tau in s2_pre]
R2 = Resolvent(F2, X, s2)
R_dup, _, _ = R2.eval_for_poly(T)
d = dup_discriminant(R_dup, ZZ)
if d == 0:
continue
if is_square(d):
return (S5TransitiveSubgroups.C5, True)
else:
return (S5TransitiveSubgroups.D5, True)
raise MaxTriesException
def _galois_group_degree_5_lookup_ext_factor(T, max_tries=30, randomize=False):
r"""
Compute the Galois group of a polynomial of degree 5.
Explanation
===========
Based on Alg 6.3.9 of [1], but uses resolvent coeff lookup, plus
factorization over an algebraic extension.
"""
from sympy.combinatorics.galois import S5TransitiveSubgroups
_T = T
history = set()
for i in range(max_tries):
R_dup = get_resolvent_by_lookup(T, 1)
if dup_sqf_p(R_dup, ZZ):
break
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
history=history,
fixed_order=not randomize)
else:
raise MaxTriesException
sq_disc = has_square_disc(T)
if dup_irreducible_p(R_dup, ZZ):
return ((S5TransitiveSubgroups.A5, True) if sq_disc
else (S5TransitiveSubgroups.S5, False))
if not sq_disc:
return (S5TransitiveSubgroups.M20, False)
# If we get this far, Gal(T) can only be D5 or C5.
# But for Gal(T) to have order 5, T must already split completely in
# the extension field obtained by adjoining a single one of its roots.
fl = Poly(_T, domain=ZZ.alg_field_from_poly(_T)).factor_list()[1]
if len(fl) == 5:
return (S5TransitiveSubgroups.C5, True)
else:
return (S5TransitiveSubgroups.D5, True)
def _galois_group_degree_6_lookup(T, max_tries=30, randomize=False):
r"""
Compute the Galois group of a polynomial of degree 6.
Explanation
===========
Based on Alg 6.3.10 of [1], but uses resolvent coeff lookup.
"""
from sympy.combinatorics.galois import S6TransitiveSubgroups
# First resolvent:
history = set()
for i in range(max_tries):
R_dup = get_resolvent_by_lookup(T, 1)
if dup_sqf_p(R_dup, ZZ):
break
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
history=history,
fixed_order=not randomize)
else:
raise MaxTriesException
fl = dup_factor_list(R_dup, ZZ)
# Group the factors by degree.
factors_by_deg = defaultdict(list)
for r, _ in fl[1]:
factors_by_deg[len(r) - 1].append(r)
L = sorted(sum([
[d] * len(ff) for d, ff in factors_by_deg.items()
], []))
T_has_sq_disc = has_square_disc(T)
if L == [1, 2, 3]:
f1 = factors_by_deg[3][0]
return ((S6TransitiveSubgroups.C6, False) if has_square_disc(f1)
else (S6TransitiveSubgroups.D6, False))
elif L == [3, 3]:
f1, f2 = factors_by_deg[3]
any_square = has_square_disc(f1) or has_square_disc(f2)
return ((S6TransitiveSubgroups.G18, False) if any_square
else (S6TransitiveSubgroups.G36m, False))
elif L == [2, 4]:
if T_has_sq_disc:
return (S6TransitiveSubgroups.S4p, True)
else:
f1 = factors_by_deg[4][0]
return ((S6TransitiveSubgroups.A4xC2, False) if has_square_disc(f1)
else (S6TransitiveSubgroups.S4xC2, False))
elif L == [1, 1, 4]:
return ((S6TransitiveSubgroups.A4, True) if T_has_sq_disc
else (S6TransitiveSubgroups.S4m, False))
elif L == [1, 5]:
return ((S6TransitiveSubgroups.PSL2F5, True) if T_has_sq_disc
else (S6TransitiveSubgroups.PGL2F5, False))
elif L == [1, 1, 1, 3]:
return (S6TransitiveSubgroups.S3, False)
assert L == [6]
# Second resolvent:
history = set()
for i in range(max_tries):
R_dup = get_resolvent_by_lookup(T, 2)
if dup_sqf_p(R_dup, ZZ):
break
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
history=history,
fixed_order=not randomize)
else:
raise MaxTriesException
T_has_sq_disc = has_square_disc(T)
if dup_irreducible_p(R_dup, ZZ):
return ((S6TransitiveSubgroups.A6, True) if T_has_sq_disc
else (S6TransitiveSubgroups.S6, False))
else:
return ((S6TransitiveSubgroups.G36p, True) if T_has_sq_disc
else (S6TransitiveSubgroups.G72, False))
@public
def galois_group(f, *gens, by_name=False, max_tries=30, randomize=False, **args):
r"""
Compute the Galois group for polynomials *f* up to degree 6.
Examples
========
>>> from sympy import galois_group
>>> from sympy.abc import x
>>> f = x**4 + 1
>>> G, alt = galois_group(f)
>>> print(G)
PermutationGroup([
(0 1)(2 3),
(0 2)(1 3)])
The group is returned along with a boolean, indicating whether it is
contained in the alternating group $A_n$, where $n$ is the degree of *T*.
Along with other group properties, this can help determine which group it
is:
>>> alt
True
>>> G.order()
4
Alternatively, the group can be returned by name:
>>> G_name, _ = galois_group(f, by_name=True)
>>> print(G_name)
S4TransitiveSubgroups.V
The group itself can then be obtained by calling the name's
``get_perm_group()`` method:
>>> G_name.get_perm_group()
PermutationGroup([
(0 1)(2 3),
(0 2)(1 3)])
Group names are values of the enum classes
:py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`,
:py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`,
etc.
Parameters
==========
f : Expr
Irreducible polynomial over :ref:`ZZ` or :ref:`QQ`, whose Galois group
is to be determined.
gens : optional list of symbols
For converting *f* to Poly, and will be passed on to the
:py:func:`~.poly_from_expr` function.
by_name : bool, default False
If ``True``, the Galois group will be returned by name.
Otherwise it will be returned as a :py:class:`~.PermutationGroup`.
max_tries : int, default 30
Make at most this many attempts in those steps that involve
generating Tschirnhausen transformations.
randomize : bool, default False
If ``True``, then use random coefficients when generating Tschirnhausen
transformations. Otherwise try transformations in a fixed order. Both
approaches start with small coefficients and degrees and work upward.
args : optional
For converting *f* to Poly, and will be passed on to the
:py:func:`~.poly_from_expr` function.
Returns
=======
Pair ``(G, alt)``
The first element ``G`` indicates the Galois group. It is an instance
of one of the :py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`
:py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`, etc. enum
classes if *by_name* was ``True``, and a :py:class:`~.PermutationGroup`
if ``False``.
The second element is a boolean, saying whether the group is contained
in the alternating group $A_n$ ($n$ the degree of *T*).
Raises
======
ValueError
if *f* is of an unsupported degree.
MaxTriesException
if could not complete before exceeding *max_tries* in those steps
that involve generating Tschirnhausen transformations.
See Also
========
.Poly.galois_group
"""
gens = gens or []
args = args or {}
try:
F, opt = poly_from_expr(f, *gens, **args)
except PolificationFailed as exc:
raise ComputationFailed('galois_group', 1, exc)
return F.galois_group(by_name=by_name, max_tries=max_tries,
randomize=randomize)

View File

@ -0,0 +1,883 @@
"""Minimal polynomials for algebraic numbers."""
from functools import reduce
from sympy.core.add import Add
from sympy.core.exprtools import Factors
from sympy.core.function import expand_mul, expand_multinomial, _mexpand
from sympy.core.mul import Mul
from sympy.core.numbers import (I, Rational, pi, _illegal)
from sympy.core.singleton import S
from sympy.core.symbol import Dummy
from sympy.core.sympify import sympify
from sympy.core.traversal import preorder_traversal
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import sqrt, cbrt
from sympy.functions.elementary.trigonometric import cos, sin, tan
from sympy.ntheory.factor_ import divisors
from sympy.utilities.iterables import subsets
from sympy.polys.domains import ZZ, QQ, FractionField
from sympy.polys.orthopolys import dup_chebyshevt
from sympy.polys.polyerrors import (
NotAlgebraic,
GeneratorsError,
)
from sympy.polys.polytools import (
Poly, PurePoly, invert, factor_list, groebner, resultant,
degree, poly_from_expr, parallel_poly_from_expr, lcm
)
from sympy.polys.polyutils import dict_from_expr, expr_from_dict
from sympy.polys.ring_series import rs_compose_add
from sympy.polys.rings import ring
from sympy.polys.rootoftools import CRootOf
from sympy.polys.specialpolys import cyclotomic_poly
from sympy.utilities import (
numbered_symbols, public, sift
)
def _choose_factor(factors, x, v, dom=QQ, prec=200, bound=5):
"""
Return a factor having root ``v``
It is assumed that one of the factors has root ``v``.
"""
if isinstance(factors[0], tuple):
factors = [f[0] for f in factors]
if len(factors) == 1:
return factors[0]
prec1 = 10
points = {}
symbols = dom.symbols if hasattr(dom, 'symbols') else []
while prec1 <= prec:
# when dealing with non-Rational numbers we usually evaluate
# with `subs` argument but we only need a ballpark evaluation
fe = [f.as_expr().xreplace({x:v}) for f in factors]
if v.is_number:
fe = [f.n(prec) for f in fe]
# assign integers [0, n) to symbols (if any)
for n in subsets(range(bound), k=len(symbols), repetition=True):
for s, i in zip(symbols, n):
points[s] = i
# evaluate the expression at these points
candidates = [(abs(f.subs(points).n(prec1)), i)
for i,f in enumerate(fe)]
# if we get invalid numbers (e.g. from division by zero)
# we try again
if any(i in _illegal for i, _ in candidates):
continue
# find the smallest two -- if they differ significantly
# then we assume we have found the factor that becomes
# 0 when v is substituted into it
can = sorted(candidates)
(a, ix), (b, _) = can[:2]
if b > a * 10**6: # XXX what to use?
return factors[ix]
prec1 *= 2
raise NotImplementedError("multiple candidates for the minimal polynomial of %s" % v)
def _is_sum_surds(p):
args = p.args if p.is_Add else [p]
for y in args:
if not ((y**2).is_Rational and y.is_extended_real):
return False
return True
def _separate_sq(p):
"""
helper function for ``_minimal_polynomial_sq``
It selects a rational ``g`` such that the polynomial ``p``
consists of a sum of terms whose surds squared have gcd equal to ``g``
and a sum of terms with surds squared prime with ``g``;
then it takes the field norm to eliminate ``sqrt(g)``
See simplify.simplify.split_surds and polytools.sqf_norm.
Examples
========
>>> from sympy import sqrt
>>> from sympy.abc import x
>>> from sympy.polys.numberfields.minpoly import _separate_sq
>>> p= -x + sqrt(2) + sqrt(3) + sqrt(7)
>>> p = _separate_sq(p); p
-x**2 + 2*sqrt(3)*x + 2*sqrt(7)*x - 2*sqrt(21) - 8
>>> p = _separate_sq(p); p
-x**4 + 4*sqrt(7)*x**3 - 32*x**2 + 8*sqrt(7)*x + 20
>>> p = _separate_sq(p); p
-x**8 + 48*x**6 - 536*x**4 + 1728*x**2 - 400
"""
def is_sqrt(expr):
return expr.is_Pow and expr.exp is S.Half
# p = c1*sqrt(q1) + ... + cn*sqrt(qn) -> a = [(c1, q1), .., (cn, qn)]
a = []
for y in p.args:
if not y.is_Mul:
if is_sqrt(y):
a.append((S.One, y**2))
elif y.is_Atom:
a.append((y, S.One))
elif y.is_Pow and y.exp.is_integer:
a.append((y, S.One))
else:
raise NotImplementedError
else:
T, F = sift(y.args, is_sqrt, binary=True)
a.append((Mul(*F), Mul(*T)**2))
a.sort(key=lambda z: z[1])
if a[-1][1] is S.One:
# there are no surds
return p
surds = [z for y, z in a]
for i in range(len(surds)):
if surds[i] != 1:
break
from sympy.simplify.radsimp import _split_gcd
g, b1, b2 = _split_gcd(*surds[i:])
a1 = []
a2 = []
for y, z in a:
if z in b1:
a1.append(y*z**S.Half)
else:
a2.append(y*z**S.Half)
p1 = Add(*a1)
p2 = Add(*a2)
p = _mexpand(p1**2) - _mexpand(p2**2)
return p
def _minimal_polynomial_sq(p, n, x):
"""
Returns the minimal polynomial for the ``nth-root`` of a sum of surds
or ``None`` if it fails.
Parameters
==========
p : sum of surds
n : positive integer
x : variable of the returned polynomial
Examples
========
>>> from sympy.polys.numberfields.minpoly import _minimal_polynomial_sq
>>> from sympy import sqrt
>>> from sympy.abc import x
>>> q = 1 + sqrt(2) + sqrt(3)
>>> _minimal_polynomial_sq(q, 3, x)
x**12 - 4*x**9 - 4*x**6 + 16*x**3 - 8
"""
p = sympify(p)
n = sympify(n)
if not n.is_Integer or not n > 0 or not _is_sum_surds(p):
return None
pn = p**Rational(1, n)
# eliminate the square roots
p -= x
while 1:
p1 = _separate_sq(p)
if p1 is p:
p = p1.subs({x:x**n})
break
else:
p = p1
# _separate_sq eliminates field extensions in a minimal way, so that
# if n = 1 then `p = constant*(minimal_polynomial(p))`
# if n > 1 it contains the minimal polynomial as a factor.
if n == 1:
p1 = Poly(p)
if p.coeff(x**p1.degree(x)) < 0:
p = -p
p = p.primitive()[1]
return p
# by construction `p` has root `pn`
# the minimal polynomial is the factor vanishing in x = pn
factors = factor_list(p)[1]
result = _choose_factor(factors, x, pn)
return result
def _minpoly_op_algebraic_element(op, ex1, ex2, x, dom, mp1=None, mp2=None):
"""
return the minimal polynomial for ``op(ex1, ex2)``
Parameters
==========
op : operation ``Add`` or ``Mul``
ex1, ex2 : expressions for the algebraic elements
x : indeterminate of the polynomials
dom: ground domain
mp1, mp2 : minimal polynomials for ``ex1`` and ``ex2`` or None
Examples
========
>>> from sympy import sqrt, Add, Mul, QQ
>>> from sympy.polys.numberfields.minpoly import _minpoly_op_algebraic_element
>>> from sympy.abc import x, y
>>> p1 = sqrt(sqrt(2) + 1)
>>> p2 = sqrt(sqrt(2) - 1)
>>> _minpoly_op_algebraic_element(Mul, p1, p2, x, QQ)
x - 1
>>> q1 = sqrt(y)
>>> q2 = 1 / y
>>> _minpoly_op_algebraic_element(Add, q1, q2, x, QQ.frac_field(y))
x**2*y**2 - 2*x*y - y**3 + 1
References
==========
.. [1] https://en.wikipedia.org/wiki/Resultant
.. [2] I.M. Isaacs, Proc. Amer. Math. Soc. 25 (1970), 638
"Degrees of sums in a separable field extension".
"""
y = Dummy(str(x))
if mp1 is None:
mp1 = _minpoly_compose(ex1, x, dom)
if mp2 is None:
mp2 = _minpoly_compose(ex2, y, dom)
else:
mp2 = mp2.subs({x: y})
if op is Add:
# mp1a = mp1.subs({x: x - y})
if dom == QQ:
R, X = ring('X', QQ)
p1 = R(dict_from_expr(mp1)[0])
p2 = R(dict_from_expr(mp2)[0])
else:
(p1, p2), _ = parallel_poly_from_expr((mp1, x - y), x, y)
r = p1.compose(p2)
mp1a = r.as_expr()
elif op is Mul:
mp1a = _muly(mp1, x, y)
else:
raise NotImplementedError('option not available')
if op is Mul or dom != QQ:
r = resultant(mp1a, mp2, gens=[y, x])
else:
r = rs_compose_add(p1, p2)
r = expr_from_dict(r.as_expr_dict(), x)
deg1 = degree(mp1, x)
deg2 = degree(mp2, y)
if op is Mul and deg1 == 1 or deg2 == 1:
# if deg1 = 1, then mp1 = x - a; mp1a = x - y - a;
# r = mp2(x - a), so that `r` is irreducible
return r
r = Poly(r, x, domain=dom)
_, factors = r.factor_list()
res = _choose_factor(factors, x, op(ex1, ex2), dom)
return res.as_expr()
def _invertx(p, x):
"""
Returns ``expand_mul(x**degree(p, x)*p.subs(x, 1/x))``
"""
p1 = poly_from_expr(p, x)[0]
n = degree(p1)
a = [c * x**(n - i) for (i,), c in p1.terms()]
return Add(*a)
def _muly(p, x, y):
"""
Returns ``_mexpand(y**deg*p.subs({x:x / y}))``
"""
p1 = poly_from_expr(p, x)[0]
n = degree(p1)
a = [c * x**i * y**(n - i) for (i,), c in p1.terms()]
return Add(*a)
def _minpoly_pow(ex, pw, x, dom, mp=None):
"""
Returns ``minpoly(ex**pw, x)``
Parameters
==========
ex : algebraic element
pw : rational number
x : indeterminate of the polynomial
dom: ground domain
mp : minimal polynomial of ``p``
Examples
========
>>> from sympy import sqrt, QQ, Rational
>>> from sympy.polys.numberfields.minpoly import _minpoly_pow, minpoly
>>> from sympy.abc import x, y
>>> p = sqrt(1 + sqrt(2))
>>> _minpoly_pow(p, 2, x, QQ)
x**2 - 2*x - 1
>>> minpoly(p**2, x)
x**2 - 2*x - 1
>>> _minpoly_pow(y, Rational(1, 3), x, QQ.frac_field(y))
x**3 - y
>>> minpoly(y**Rational(1, 3), x)
x**3 - y
"""
pw = sympify(pw)
if not mp:
mp = _minpoly_compose(ex, x, dom)
if not pw.is_rational:
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
if pw < 0:
if mp == x:
raise ZeroDivisionError('%s is zero' % ex)
mp = _invertx(mp, x)
if pw == -1:
return mp
pw = -pw
ex = 1/ex
y = Dummy(str(x))
mp = mp.subs({x: y})
n, d = pw.as_numer_denom()
res = Poly(resultant(mp, x**d - y**n, gens=[y]), x, domain=dom)
_, factors = res.factor_list()
res = _choose_factor(factors, x, ex**pw, dom)
return res.as_expr()
def _minpoly_add(x, dom, *a):
"""
returns ``minpoly(Add(*a), dom, x)``
"""
mp = _minpoly_op_algebraic_element(Add, a[0], a[1], x, dom)
p = a[0] + a[1]
for px in a[2:]:
mp = _minpoly_op_algebraic_element(Add, p, px, x, dom, mp1=mp)
p = p + px
return mp
def _minpoly_mul(x, dom, *a):
"""
returns ``minpoly(Mul(*a), dom, x)``
"""
mp = _minpoly_op_algebraic_element(Mul, a[0], a[1], x, dom)
p = a[0] * a[1]
for px in a[2:]:
mp = _minpoly_op_algebraic_element(Mul, p, px, x, dom, mp1=mp)
p = p * px
return mp
def _minpoly_sin(ex, x):
"""
Returns the minimal polynomial of ``sin(ex)``
see https://mathworld.wolfram.com/TrigonometryAngles.html
"""
c, a = ex.args[0].as_coeff_Mul()
if a is pi:
if c.is_rational:
n = c.q
q = sympify(n)
if q.is_prime:
# for a = pi*p/q with q odd prime, using chebyshevt
# write sin(q*a) = mp(sin(a))*sin(a);
# the roots of mp(x) are sin(pi*p/q) for p = 1,..., q - 1
a = dup_chebyshevt(n, ZZ)
return Add(*[x**(n - i - 1)*a[i] for i in range(n)])
if c.p == 1:
if q == 9:
return 64*x**6 - 96*x**4 + 36*x**2 - 3
if n % 2 == 1:
# for a = pi*p/q with q odd, use
# sin(q*a) = 0 to see that the minimal polynomial must be
# a factor of dup_chebyshevt(n, ZZ)
a = dup_chebyshevt(n, ZZ)
a = [x**(n - i)*a[i] for i in range(n + 1)]
r = Add(*a)
_, factors = factor_list(r)
res = _choose_factor(factors, x, ex)
return res
expr = ((1 - cos(2*c*pi))/2)**S.Half
res = _minpoly_compose(expr, x, QQ)
return res
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
def _minpoly_cos(ex, x):
"""
Returns the minimal polynomial of ``cos(ex)``
see https://mathworld.wolfram.com/TrigonometryAngles.html
"""
c, a = ex.args[0].as_coeff_Mul()
if a is pi:
if c.is_rational:
if c.p == 1:
if c.q == 7:
return 8*x**3 - 4*x**2 - 4*x + 1
if c.q == 9:
return 8*x**3 - 6*x - 1
elif c.p == 2:
q = sympify(c.q)
if q.is_prime:
s = _minpoly_sin(ex, x)
return _mexpand(s.subs({x:sqrt((1 - x)/2)}))
# for a = pi*p/q, cos(q*a) =T_q(cos(a)) = (-1)**p
n = int(c.q)
a = dup_chebyshevt(n, ZZ)
a = [x**(n - i)*a[i] for i in range(n + 1)]
r = Add(*a) - (-1)**c.p
_, factors = factor_list(r)
res = _choose_factor(factors, x, ex)
return res
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
def _minpoly_tan(ex, x):
"""
Returns the minimal polynomial of ``tan(ex)``
see https://github.com/sympy/sympy/issues/21430
"""
c, a = ex.args[0].as_coeff_Mul()
if a is pi:
if c.is_rational:
c = c * 2
n = int(c.q)
a = n if c.p % 2 == 0 else 1
terms = []
for k in range((c.p+1)%2, n+1, 2):
terms.append(a*x**k)
a = -(a*(n-k-1)*(n-k)) // ((k+1)*(k+2))
r = Add(*terms)
_, factors = factor_list(r)
res = _choose_factor(factors, x, ex)
return res
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
def _minpoly_exp(ex, x):
"""
Returns the minimal polynomial of ``exp(ex)``
"""
c, a = ex.args[0].as_coeff_Mul()
if a == I*pi:
if c.is_rational:
q = sympify(c.q)
if c.p == 1 or c.p == -1:
if q == 3:
return x**2 - x + 1
if q == 4:
return x**4 + 1
if q == 6:
return x**4 - x**2 + 1
if q == 8:
return x**8 + 1
if q == 9:
return x**6 - x**3 + 1
if q == 10:
return x**8 - x**6 + x**4 - x**2 + 1
if q.is_prime:
s = 0
for i in range(q):
s += (-x)**i
return s
# x**(2*q) = product(factors)
factors = [cyclotomic_poly(i, x) for i in divisors(2*q)]
mp = _choose_factor(factors, x, ex)
return mp
else:
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
def _minpoly_rootof(ex, x):
"""
Returns the minimal polynomial of a ``CRootOf`` object.
"""
p = ex.expr
p = p.subs({ex.poly.gens[0]:x})
_, factors = factor_list(p, x)
result = _choose_factor(factors, x, ex)
return result
def _minpoly_compose(ex, x, dom):
"""
Computes the minimal polynomial of an algebraic element
using operations on minimal polynomials
Examples
========
>>> from sympy import minimal_polynomial, sqrt, Rational
>>> from sympy.abc import x, y
>>> minimal_polynomial(sqrt(2) + 3*Rational(1, 3), x, compose=True)
x**2 - 2*x - 1
>>> minimal_polynomial(sqrt(y) + 1/y, x, compose=True)
x**2*y**2 - 2*x*y - y**3 + 1
"""
if ex.is_Rational:
return ex.q*x - ex.p
if ex is I:
_, factors = factor_list(x**2 + 1, x, domain=dom)
return x**2 + 1 if len(factors) == 1 else x - I
if ex is S.GoldenRatio:
_, factors = factor_list(x**2 - x - 1, x, domain=dom)
if len(factors) == 1:
return x**2 - x - 1
else:
return _choose_factor(factors, x, (1 + sqrt(5))/2, dom=dom)
if ex is S.TribonacciConstant:
_, factors = factor_list(x**3 - x**2 - x - 1, x, domain=dom)
if len(factors) == 1:
return x**3 - x**2 - x - 1
else:
fac = (1 + cbrt(19 - 3*sqrt(33)) + cbrt(19 + 3*sqrt(33))) / 3
return _choose_factor(factors, x, fac, dom=dom)
if hasattr(dom, 'symbols') and ex in dom.symbols:
return x - ex
if dom.is_QQ and _is_sum_surds(ex):
# eliminate the square roots
ex -= x
while 1:
ex1 = _separate_sq(ex)
if ex1 is ex:
return ex
else:
ex = ex1
if ex.is_Add:
res = _minpoly_add(x, dom, *ex.args)
elif ex.is_Mul:
f = Factors(ex).factors
r = sift(f.items(), lambda itx: itx[0].is_Rational and itx[1].is_Rational)
if r[True] and dom == QQ:
ex1 = Mul(*[bx**ex for bx, ex in r[False] + r[None]])
r1 = dict(r[True])
dens = [y.q for y in r1.values()]
lcmdens = reduce(lcm, dens, 1)
neg1 = S.NegativeOne
expn1 = r1.pop(neg1, S.Zero)
nums = [base**(y.p*lcmdens // y.q) for base, y in r1.items()]
ex2 = Mul(*nums)
mp1 = minimal_polynomial(ex1, x)
# use the fact that in SymPy canonicalization products of integers
# raised to rational powers are organized in relatively prime
# bases, and that in ``base**(n/d)`` a perfect power is
# simplified with the root
# Powers of -1 have to be treated separately to preserve sign.
mp2 = ex2.q*x**lcmdens - ex2.p*neg1**(expn1*lcmdens)
ex2 = neg1**expn1 * ex2**Rational(1, lcmdens)
res = _minpoly_op_algebraic_element(Mul, ex1, ex2, x, dom, mp1=mp1, mp2=mp2)
else:
res = _minpoly_mul(x, dom, *ex.args)
elif ex.is_Pow:
res = _minpoly_pow(ex.base, ex.exp, x, dom)
elif ex.__class__ is sin:
res = _minpoly_sin(ex, x)
elif ex.__class__ is cos:
res = _minpoly_cos(ex, x)
elif ex.__class__ is tan:
res = _minpoly_tan(ex, x)
elif ex.__class__ is exp:
res = _minpoly_exp(ex, x)
elif ex.__class__ is CRootOf:
res = _minpoly_rootof(ex, x)
else:
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex)
return res
@public
def minimal_polynomial(ex, x=None, compose=True, polys=False, domain=None):
"""
Computes the minimal polynomial of an algebraic element.
Parameters
==========
ex : Expr
Element or expression whose minimal polynomial is to be calculated.
x : Symbol, optional
Independent variable of the minimal polynomial
compose : boolean, optional (default=True)
Method to use for computing minimal polynomial. If ``compose=True``
(default) then ``_minpoly_compose`` is used, if ``compose=False`` then
groebner bases are used.
polys : boolean, optional (default=False)
If ``True`` returns a ``Poly`` object else an ``Expr`` object.
domain : Domain, optional
Ground domain
Notes
=====
By default ``compose=True``, the minimal polynomial of the subexpressions of ``ex``
are computed, then the arithmetic operations on them are performed using the resultant
and factorization.
If ``compose=False``, a bottom-up algorithm is used with ``groebner``.
The default algorithm stalls less frequently.
If no ground domain is given, it will be generated automatically from the expression.
Examples
========
>>> from sympy import minimal_polynomial, sqrt, solve, QQ
>>> from sympy.abc import x, y
>>> minimal_polynomial(sqrt(2), x)
x**2 - 2
>>> minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2)))
x - sqrt(2)
>>> minimal_polynomial(sqrt(2) + sqrt(3), x)
x**4 - 10*x**2 + 1
>>> minimal_polynomial(solve(x**3 + x + 3)[0], x)
x**3 + x + 3
>>> minimal_polynomial(sqrt(y), x)
x**2 - y
"""
ex = sympify(ex)
if ex.is_number:
# not sure if it's always needed but try it for numbers (issue 8354)
ex = _mexpand(ex, recursive=True)
for expr in preorder_traversal(ex):
if expr.is_AlgebraicNumber:
compose = False
break
if x is not None:
x, cls = sympify(x), Poly
else:
x, cls = Dummy('x'), PurePoly
if not domain:
if ex.free_symbols:
domain = FractionField(QQ, list(ex.free_symbols))
else:
domain = QQ
if hasattr(domain, 'symbols') and x in domain.symbols:
raise GeneratorsError("the variable %s is an element of the ground "
"domain %s" % (x, domain))
if compose:
result = _minpoly_compose(ex, x, domain)
result = result.primitive()[1]
c = result.coeff(x**degree(result, x))
if c.is_negative:
result = expand_mul(-result)
return cls(result, x, field=True) if polys else result.collect(x)
if not domain.is_QQ:
raise NotImplementedError("groebner method only works for QQ")
result = _minpoly_groebner(ex, x, cls)
return cls(result, x, field=True) if polys else result.collect(x)
def _minpoly_groebner(ex, x, cls):
"""
Computes the minimal polynomial of an algebraic number
using Groebner bases
Examples
========
>>> from sympy import minimal_polynomial, sqrt, Rational
>>> from sympy.abc import x
>>> minimal_polynomial(sqrt(2) + 3*Rational(1, 3), x, compose=False)
x**2 - 2*x - 1
"""
generator = numbered_symbols('a', cls=Dummy)
mapping, symbols = {}, {}
def update_mapping(ex, exp, base=None):
a = next(generator)
symbols[ex] = a
if base is not None:
mapping[ex] = a**exp + base
else:
mapping[ex] = exp.as_expr(a)
return a
def bottom_up_scan(ex):
"""
Transform a given algebraic expression *ex* into a multivariate
polynomial, by introducing fresh variables with defining equations.
Explanation
===========
The critical elements of the algebraic expression *ex* are root
extractions, instances of :py:class:`~.AlgebraicNumber`, and negative
powers.
When we encounter a root extraction or an :py:class:`~.AlgebraicNumber`
we replace this expression with a fresh variable ``a_i``, and record
the defining polynomial for ``a_i``. For example, if ``a_0**(1/3)``
occurs, we will replace it with ``a_1``, and record the new defining
polynomial ``a_1**3 - a_0``.
When we encounter a negative power we transform it into a positive
power by algebraically inverting the base. This means computing the
minimal polynomial in ``x`` for the base, inverting ``x`` modulo this
poly (which generates a new polynomial) and then substituting the
original base expression for ``x`` in this last polynomial.
We return the transformed expression, and we record the defining
equations for new symbols using the ``update_mapping()`` function.
"""
if ex.is_Atom:
if ex is S.ImaginaryUnit:
if ex not in mapping:
return update_mapping(ex, 2, 1)
else:
return symbols[ex]
elif ex.is_Rational:
return ex
elif ex.is_Add:
return Add(*[ bottom_up_scan(g) for g in ex.args ])
elif ex.is_Mul:
return Mul(*[ bottom_up_scan(g) for g in ex.args ])
elif ex.is_Pow:
if ex.exp.is_Rational:
if ex.exp < 0:
minpoly_base = _minpoly_groebner(ex.base, x, cls)
inverse = invert(x, minpoly_base).as_expr()
base_inv = inverse.subs(x, ex.base).expand()
if ex.exp == -1:
return bottom_up_scan(base_inv)
else:
ex = base_inv**(-ex.exp)
if not ex.exp.is_Integer:
base, exp = (
ex.base**ex.exp.p).expand(), Rational(1, ex.exp.q)
else:
base, exp = ex.base, ex.exp
base = bottom_up_scan(base)
expr = base**exp
if expr not in mapping:
if exp.is_Integer:
return expr.expand()
else:
return update_mapping(expr, 1 / exp, -base)
else:
return symbols[expr]
elif ex.is_AlgebraicNumber:
if ex not in mapping:
return update_mapping(ex, ex.minpoly_of_element())
else:
return symbols[ex]
raise NotAlgebraic("%s does not seem to be an algebraic number" % ex)
def simpler_inverse(ex):
"""
Returns True if it is more likely that the minimal polynomial
algorithm works better with the inverse
"""
if ex.is_Pow:
if (1/ex.exp).is_integer and ex.exp < 0:
if ex.base.is_Add:
return True
if ex.is_Mul:
hit = True
for p in ex.args:
if p.is_Add:
return False
if p.is_Pow:
if p.base.is_Add and p.exp > 0:
return False
if hit:
return True
return False
inverted = False
ex = expand_multinomial(ex)
if ex.is_AlgebraicNumber:
return ex.minpoly_of_element().as_expr(x)
elif ex.is_Rational:
result = ex.q*x - ex.p
else:
inverted = simpler_inverse(ex)
if inverted:
ex = ex**-1
res = None
if ex.is_Pow and (1/ex.exp).is_Integer:
n = 1/ex.exp
res = _minimal_polynomial_sq(ex.base, n, x)
elif _is_sum_surds(ex):
res = _minimal_polynomial_sq(ex, S.One, x)
if res is not None:
result = res
if res is None:
bus = bottom_up_scan(ex)
F = [x - bus] + list(mapping.values())
G = groebner(F, list(symbols.values()) + [x], order='lex')
_, factors = factor_list(G[-1])
# by construction G[-1] has root `ex`
result = _choose_factor(factors, x, ex)
if inverted:
result = _invertx(result, x)
if result.coeff(x**degree(result, x)) < 0:
result = expand_mul(-result)
return result
@public
def minpoly(ex, x=None, compose=True, polys=False, domain=None):
"""This is a synonym for :py:func:`~.minimal_polynomial`."""
return minimal_polynomial(ex, x=x, compose=compose, polys=polys, domain=domain)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,784 @@
"""Prime ideals in number fields. """
from sympy.polys.polytools import Poly
from sympy.polys.domains.finitefield import FF
from sympy.polys.domains.rationalfield import QQ
from sympy.polys.domains.integerring import ZZ
from sympy.polys.matrices.domainmatrix import DomainMatrix
from sympy.polys.polyerrors import CoercionFailed
from sympy.polys.polyutils import IntegerPowerable
from sympy.utilities.decorator import public
from .basis import round_two, nilradical_mod_p
from .exceptions import StructureError
from .modules import ModuleEndomorphism, find_min_poly
from .utilities import coeff_search, supplement_a_subspace
def _check_formal_conditions_for_maximal_order(submodule):
r"""
Several functions in this module accept an argument which is to be a
:py:class:`~.Submodule` representing the maximal order in a number field,
such as returned by the :py:func:`~sympy.polys.numberfields.basis.round_two`
algorithm.
We do not attempt to check that the given ``Submodule`` actually represents
a maximal order, but we do check a basic set of formal conditions that the
``Submodule`` must satisfy, at a minimum. The purpose is to catch an
obviously ill-formed argument.
"""
prefix = 'The submodule representing the maximal order should '
cond = None
if not submodule.is_power_basis_submodule():
cond = 'be a direct submodule of a power basis.'
elif not submodule.starts_with_unity():
cond = 'have 1 as its first generator.'
elif not submodule.is_sq_maxrank_HNF():
cond = 'have square matrix, of maximal rank, in Hermite Normal Form.'
if cond is not None:
raise StructureError(prefix + cond)
class PrimeIdeal(IntegerPowerable):
r"""
A prime ideal in a ring of algebraic integers.
"""
def __init__(self, ZK, p, alpha, f, e=None):
"""
Parameters
==========
ZK : :py:class:`~.Submodule`
The maximal order where this ideal lives.
p : int
The rational prime this ideal divides.
alpha : :py:class:`~.PowerBasisElement`
Such that the ideal is equal to ``p*ZK + alpha*ZK``.
f : int
The inertia degree.
e : int, ``None``, optional
The ramification index, if already known. If ``None``, we will
compute it here.
"""
_check_formal_conditions_for_maximal_order(ZK)
self.ZK = ZK
self.p = p
self.alpha = alpha
self.f = f
self._test_factor = None
self.e = e if e is not None else self.valuation(p * ZK)
def __str__(self):
if self.is_inert:
return f'({self.p})'
return f'({self.p}, {self.alpha.as_expr()})'
@property
def is_inert(self):
"""
Say whether the rational prime we divide is inert, i.e. stays prime in
our ring of integers.
"""
return self.f == self.ZK.n
def repr(self, field_gen=None, just_gens=False):
"""
Print a representation of this prime ideal.
Examples
========
>>> from sympy import cyclotomic_poly, QQ
>>> from sympy.abc import x, zeta
>>> T = cyclotomic_poly(7, x)
>>> K = QQ.algebraic_field((T, zeta))
>>> P = K.primes_above(11)
>>> print(P[0].repr())
[ (11, x**3 + 5*x**2 + 4*x - 1) e=1, f=3 ]
>>> print(P[0].repr(field_gen=zeta))
[ (11, zeta**3 + 5*zeta**2 + 4*zeta - 1) e=1, f=3 ]
>>> print(P[0].repr(field_gen=zeta, just_gens=True))
(11, zeta**3 + 5*zeta**2 + 4*zeta - 1)
Parameters
==========
field_gen : :py:class:`~.Symbol`, ``None``, optional (default=None)
The symbol to use for the generator of the field. This will appear
in our representation of ``self.alpha``. If ``None``, we use the
variable of the defining polynomial of ``self.ZK``.
just_gens : bool, optional (default=False)
If ``True``, just print the "(p, alpha)" part, showing "just the
generators" of the prime ideal. Otherwise, print a string of the
form "[ (p, alpha) e=..., f=... ]", giving the ramification index
and inertia degree, along with the generators.
"""
field_gen = field_gen or self.ZK.parent.T.gen
p, alpha, e, f = self.p, self.alpha, self.e, self.f
alpha_rep = str(alpha.numerator(x=field_gen).as_expr())
if alpha.denom > 1:
alpha_rep = f'({alpha_rep})/{alpha.denom}'
gens = f'({p}, {alpha_rep})'
if just_gens:
return gens
return f'[ {gens} e={e}, f={f} ]'
def __repr__(self):
return self.repr()
def as_submodule(self):
r"""
Represent this prime ideal as a :py:class:`~.Submodule`.
Explanation
===========
The :py:class:`~.PrimeIdeal` class serves to bundle information about
a prime ideal, such as its inertia degree, ramification index, and
two-generator representation, as well as to offer helpful methods like
:py:meth:`~.PrimeIdeal.valuation` and
:py:meth:`~.PrimeIdeal.test_factor`.
However, in order to be added and multiplied by other ideals or
rational numbers, it must first be converted into a
:py:class:`~.Submodule`, which is a class that supports these
operations.
In many cases, the user need not perform this conversion deliberately,
since it is automatically performed by the arithmetic operator methods
:py:meth:`~.PrimeIdeal.__add__` and :py:meth:`~.PrimeIdeal.__mul__`.
Raising a :py:class:`~.PrimeIdeal` to a non-negative integer power is
also supported.
Examples
========
>>> from sympy import Poly, cyclotomic_poly, prime_decomp
>>> T = Poly(cyclotomic_poly(7))
>>> P0 = prime_decomp(7, T)[0]
>>> print(P0**6 == 7*P0.ZK)
True
Note that, on both sides of the equation above, we had a
:py:class:`~.Submodule`. In the next equation we recall that adding
ideals yields their GCD. This time, we need a deliberate conversion
to :py:class:`~.Submodule` on the right:
>>> print(P0 + 7*P0.ZK == P0.as_submodule())
True
Returns
=======
:py:class:`~.Submodule`
Will be equal to ``self.p * self.ZK + self.alpha * self.ZK``.
See Also
========
__add__
__mul__
"""
M = self.p * self.ZK + self.alpha * self.ZK
# Pre-set expensive boolean properties whose value we already know:
M._starts_with_unity = False
M._is_sq_maxrank_HNF = True
return M
def __eq__(self, other):
if isinstance(other, PrimeIdeal):
return self.as_submodule() == other.as_submodule()
return NotImplemented
def __add__(self, other):
"""
Convert to a :py:class:`~.Submodule` and add to another
:py:class:`~.Submodule`.
See Also
========
as_submodule
"""
return self.as_submodule() + other
__radd__ = __add__
def __mul__(self, other):
"""
Convert to a :py:class:`~.Submodule` and multiply by another
:py:class:`~.Submodule` or a rational number.
See Also
========
as_submodule
"""
return self.as_submodule() * other
__rmul__ = __mul__
def _zeroth_power(self):
return self.ZK
def _first_power(self):
return self
def test_factor(self):
r"""
Compute a test factor for this prime ideal.
Explanation
===========
Write $\mathfrak{p}$ for this prime ideal, $p$ for the rational prime
it divides. Then, for computing $\mathfrak{p}$-adic valuations it is
useful to have a number $\beta \in \mathbb{Z}_K$ such that
$p/\mathfrak{p} = p \mathbb{Z}_K + \beta \mathbb{Z}_K$.
Essentially, this is the same as the number $\Psi$ (or the "reagent")
from Kummer's 1847 paper (*Ueber die Zerlegung...*, Crelle vol. 35) in
which ideal divisors were invented.
"""
if self._test_factor is None:
self._test_factor = _compute_test_factor(self.p, [self.alpha], self.ZK)
return self._test_factor
def valuation(self, I):
r"""
Compute the $\mathfrak{p}$-adic valuation of integral ideal I at this
prime ideal.
Parameters
==========
I : :py:class:`~.Submodule`
See Also
========
prime_valuation
"""
return prime_valuation(I, self)
def reduce_element(self, elt):
"""
Reduce a :py:class:`~.PowerBasisElement` to a "small representative"
modulo this prime ideal.
Parameters
==========
elt : :py:class:`~.PowerBasisElement`
The element to be reduced.
Returns
=======
:py:class:`~.PowerBasisElement`
The reduced element.
See Also
========
reduce_ANP
reduce_alg_num
.Submodule.reduce_element
"""
return self.as_submodule().reduce_element(elt)
def reduce_ANP(self, a):
"""
Reduce an :py:class:`~.ANP` to a "small representative" modulo this
prime ideal.
Parameters
==========
elt : :py:class:`~.ANP`
The element to be reduced.
Returns
=======
:py:class:`~.ANP`
The reduced element.
See Also
========
reduce_element
reduce_alg_num
.Submodule.reduce_element
"""
elt = self.ZK.parent.element_from_ANP(a)
red = self.reduce_element(elt)
return red.to_ANP()
def reduce_alg_num(self, a):
"""
Reduce an :py:class:`~.AlgebraicNumber` to a "small representative"
modulo this prime ideal.
Parameters
==========
elt : :py:class:`~.AlgebraicNumber`
The element to be reduced.
Returns
=======
:py:class:`~.AlgebraicNumber`
The reduced element.
See Also
========
reduce_element
reduce_ANP
.Submodule.reduce_element
"""
elt = self.ZK.parent.element_from_alg_num(a)
red = self.reduce_element(elt)
return a.field_element(list(reversed(red.QQ_col.flat())))
def _compute_test_factor(p, gens, ZK):
r"""
Compute the test factor for a :py:class:`~.PrimeIdeal` $\mathfrak{p}$.
Parameters
==========
p : int
The rational prime $\mathfrak{p}$ divides
gens : list of :py:class:`PowerBasisElement`
A complete set of generators for $\mathfrak{p}$ over *ZK*, EXCEPT that
an element equivalent to rational *p* can and should be omitted (since
it has no effect except to waste time).
ZK : :py:class:`~.Submodule`
The maximal order where the prime ideal $\mathfrak{p}$ lives.
Returns
=======
:py:class:`~.PowerBasisElement`
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
(See Proposition 4.8.15.)
"""
_check_formal_conditions_for_maximal_order(ZK)
E = ZK.endomorphism_ring()
matrices = [E.inner_endomorphism(g).matrix(modulus=p) for g in gens]
B = DomainMatrix.zeros((0, ZK.n), FF(p)).vstack(*matrices)
# A nonzero element of the nullspace of B will represent a
# lin comb over the omegas which (i) is not a multiple of p
# (since it is nonzero over FF(p)), while (ii) is such that
# its product with each g in gens _is_ a multiple of p (since
# B represents multiplication by these generators). Theory
# predicts that such an element must exist, so nullspace should
# be non-trivial.
x = B.nullspace()[0, :].transpose()
beta = ZK.parent(ZK.matrix * x.convert_to(ZZ), denom=ZK.denom)
return beta
@public
def prime_valuation(I, P):
r"""
Compute the *P*-adic valuation for an integral ideal *I*.
Examples
========
>>> from sympy import QQ
>>> from sympy.polys.numberfields import prime_valuation
>>> K = QQ.cyclotomic_field(5)
>>> P = K.primes_above(5)
>>> ZK = K.maximal_order()
>>> print(prime_valuation(25*ZK, P[0]))
8
Parameters
==========
I : :py:class:`~.Submodule`
An integral ideal whose valuation is desired.
P : :py:class:`~.PrimeIdeal`
The prime at which to compute the valuation.
Returns
=======
int
See Also
========
.PrimeIdeal.valuation
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
(See Algorithm 4.8.17.)
"""
p, ZK = P.p, P.ZK
n, W, d = ZK.n, ZK.matrix, ZK.denom
A = W.convert_to(QQ).inv() * I.matrix * d / I.denom
# Although A must have integer entries, given that I is an integral ideal,
# as a DomainMatrix it will still be over QQ, so we convert back:
A = A.convert_to(ZZ)
D = A.det()
if D % p != 0:
return 0
beta = P.test_factor()
f = d ** n // W.det()
need_complete_test = (f % p == 0)
v = 0
while True:
# Entering the loop, the cols of A represent lin combs of omegas.
# Turn them into lin combs of thetas:
A = W * A
# And then one column at a time...
for j in range(n):
c = ZK.parent(A[:, j], denom=d)
c *= beta
# ...turn back into lin combs of omegas, after multiplying by beta:
c = ZK.represent(c).flat()
for i in range(n):
A[i, j] = c[i]
if A[n - 1, n - 1].element % p != 0:
break
A = A / p
# As noted above, domain converts to QQ even when division goes evenly.
# So must convert back, even when we don't "need_complete_test".
if need_complete_test:
# In this case, having a non-integer entry is actually just our
# halting condition.
try:
A = A.convert_to(ZZ)
except CoercionFailed:
break
else:
# In this case theory says we should not have any non-integer entries.
A = A.convert_to(ZZ)
v += 1
return v
def _two_elt_rep(gens, ZK, p, f=None, Np=None):
r"""
Given a set of *ZK*-generators of a prime ideal, compute a set of just two
*ZK*-generators for the same ideal, one of which is *p* itself.
Parameters
==========
gens : list of :py:class:`PowerBasisElement`
Generators for the prime ideal over *ZK*, the ring of integers of the
field $K$.
ZK : :py:class:`~.Submodule`
The maximal order in $K$.
p : int
The rational prime divided by the prime ideal.
f : int, optional
The inertia degree of the prime ideal, if known.
Np : int, optional
The norm $p^f$ of the prime ideal, if known.
NOTE: There is no reason to supply both *f* and *Np*. Either one will
save us from having to compute the norm *Np* ourselves. If both are known,
*Np* is preferred since it saves one exponentiation.
Returns
=======
:py:class:`~.PowerBasisElement` representing a single algebraic integer
alpha such that the prime ideal is equal to ``p*ZK + alpha*ZK``.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
(See Algorithm 4.7.10.)
"""
_check_formal_conditions_for_maximal_order(ZK)
pb = ZK.parent
T = pb.T
# Detect the special cases in which either (a) all generators are multiples
# of p, or (b) there are no generators (so `all` is vacuously true):
if all((g % p).equiv(0) for g in gens):
return pb.zero()
if Np is None:
if f is not None:
Np = p**f
else:
Np = abs(pb.submodule_from_gens(gens).matrix.det())
omega = ZK.basis_element_pullbacks()
beta = [p*om for om in omega[1:]] # note: we omit omega[0] == 1
beta += gens
search = coeff_search(len(beta), 1)
for c in search:
alpha = sum(ci*betai for ci, betai in zip(c, beta))
# Note: It may be tempting to reduce alpha mod p here, to try to work
# with smaller numbers, but must not do that, as it can result in an
# infinite loop! E.g. try factoring 2 in Q(sqrt(-7)).
n = alpha.norm(T) // Np
if n % p != 0:
# Now can reduce alpha mod p.
return alpha % p
def _prime_decomp_easy_case(p, ZK):
r"""
Compute the decomposition of rational prime *p* in the ring of integers
*ZK* (given as a :py:class:`~.Submodule`), in the "easy case", i.e. the
case where *p* does not divide the index of $\theta$ in *ZK*, where
$\theta$ is the generator of the ``PowerBasis`` of which *ZK* is a
``Submodule``.
"""
T = ZK.parent.T
T_bar = Poly(T, modulus=p)
lc, fl = T_bar.factor_list()
if len(fl) == 1 and fl[0][1] == 1:
return [PrimeIdeal(ZK, p, ZK.parent.zero(), ZK.n, 1)]
return [PrimeIdeal(ZK, p,
ZK.parent.element_from_poly(Poly(t, domain=ZZ)),
t.degree(), e)
for t, e in fl]
def _prime_decomp_compute_kernel(I, p, ZK):
r"""
Parameters
==========
I : :py:class:`~.Module`
An ideal of ``ZK/pZK``.
p : int
The rational prime being factored.
ZK : :py:class:`~.Submodule`
The maximal order.
Returns
=======
Pair ``(N, G)``, where:
``N`` is a :py:class:`~.Module` representing the kernel of the map
``a |--> a**p - a`` on ``(O/pO)/I``, guaranteed to be a module with
unity.
``G`` is a :py:class:`~.Module` representing a basis for the separable
algebra ``A = O/I`` (see Cohen).
"""
W = I.matrix
n, r = W.shape
# Want to take the Fp-basis given by the columns of I, adjoin (1, 0, ..., 0)
# (which we know is not already in there since I is a basis for a prime ideal)
# and then supplement this with additional columns to make an invertible n x n
# matrix. This will then represent a full basis for ZK, whose first r columns
# are pullbacks of the basis for I.
if r == 0:
B = W.eye(n, ZZ)
else:
B = W.hstack(W.eye(n, ZZ)[:, 0])
if B.shape[1] < n:
B = supplement_a_subspace(B.convert_to(FF(p))).convert_to(ZZ)
G = ZK.submodule_from_matrix(B)
# Must compute G's multiplication table _before_ discarding the first r
# columns. (See Step 9 in Alg 6.2.9 in Cohen, where the betas are actually
# needed in order to represent each product of gammas. However, once we've
# found the representations, then we can ignore the betas.)
G.compute_mult_tab()
G = G.discard_before(r)
phi = ModuleEndomorphism(G, lambda x: x**p - x)
N = phi.kernel(modulus=p)
assert N.starts_with_unity()
return N, G
def _prime_decomp_maximal_ideal(I, p, ZK):
r"""
We have reached the case where we have a maximal (hence prime) ideal *I*,
which we know because the quotient ``O/I`` is a field.
Parameters
==========
I : :py:class:`~.Module`
An ideal of ``O/pO``.
p : int
The rational prime being factored.
ZK : :py:class:`~.Submodule`
The maximal order.
Returns
=======
:py:class:`~.PrimeIdeal` instance representing this prime
"""
m, n = I.matrix.shape
f = m - n
G = ZK.matrix * I.matrix
gens = [ZK.parent(G[:, j], denom=ZK.denom) for j in range(G.shape[1])]
alpha = _two_elt_rep(gens, ZK, p, f=f)
return PrimeIdeal(ZK, p, alpha, f)
def _prime_decomp_split_ideal(I, p, N, G, ZK):
r"""
Perform the step in the prime decomposition algorithm where we have determined
the quotient ``ZK/I`` is _not_ a field, and we want to perform a non-trivial
factorization of *I* by locating an idempotent element of ``ZK/I``.
"""
assert I.parent == ZK and G.parent is ZK and N.parent is G
# Since ZK/I is not a field, the kernel computed in the previous step contains
# more than just the prime field Fp, and our basis N for the nullspace therefore
# contains at least a second column (which represents an element outside Fp).
# Let alpha be such an element:
alpha = N(1).to_parent()
assert alpha.module is G
alpha_powers = []
m = find_min_poly(alpha, FF(p), powers=alpha_powers)
# TODO (future work):
# We don't actually need full factorization, so might use a faster method
# to just break off a single non-constant factor m1?
lc, fl = m.factor_list()
m1 = fl[0][0]
m2 = m.quo(m1)
U, V, g = m1.gcdex(m2)
# Sanity check: theory says m is squarefree, so m1, m2 should be coprime:
assert g == 1
E = list(reversed(Poly(U * m1, domain=ZZ).rep.to_list()))
eps1 = sum(E[i]*alpha_powers[i] for i in range(len(E)))
eps2 = 1 - eps1
idemps = [eps1, eps2]
factors = []
for eps in idemps:
e = eps.to_parent()
assert e.module is ZK
D = I.matrix.convert_to(FF(p)).hstack(*[
(e * om).column(domain=FF(p)) for om in ZK.basis_elements()
])
W = D.columnspace().convert_to(ZZ)
H = ZK.submodule_from_matrix(W)
factors.append(H)
return factors
@public
def prime_decomp(p, T=None, ZK=None, dK=None, radical=None):
r"""
Compute the decomposition of rational prime *p* in a number field.
Explanation
===========
Ordinarily this should be accessed through the
:py:meth:`~.AlgebraicField.primes_above` method of an
:py:class:`~.AlgebraicField`.
Examples
========
>>> from sympy import Poly, QQ
>>> from sympy.abc import x, theta
>>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
>>> K = QQ.algebraic_field((T, theta))
>>> print(K.primes_above(2))
[[ (2, x**2 + 1) e=1, f=1 ], [ (2, (x**2 + 3*x + 2)/2) e=1, f=1 ],
[ (2, (3*x**2 + 3*x)/2) e=1, f=1 ]]
Parameters
==========
p : int
The rational prime whose decomposition is desired.
T : :py:class:`~.Poly`, optional
Monic irreducible polynomial defining the number field $K$ in which to
factor. NOTE: at least one of *T* or *ZK* must be provided.
ZK : :py:class:`~.Submodule`, optional
The maximal order for $K$, if already known.
NOTE: at least one of *T* or *ZK* must be provided.
dK : int, optional
The discriminant of the field $K$, if already known.
radical : :py:class:`~.Submodule`, optional
The nilradical mod *p* in the integers of $K$, if already known.
Returns
=======
List of :py:class:`~.PrimeIdeal` instances.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
(See Algorithm 6.2.9.)
"""
if T is None and ZK is None:
raise ValueError('At least one of T or ZK must be provided.')
if ZK is not None:
_check_formal_conditions_for_maximal_order(ZK)
if T is None:
T = ZK.parent.T
radicals = {}
if dK is None or ZK is None:
ZK, dK = round_two(T, radicals=radicals)
dT = T.discriminant()
f_squared = dT // dK
if f_squared % p != 0:
return _prime_decomp_easy_case(p, ZK)
radical = radical or radicals.get(p) or nilradical_mod_p(ZK, p)
stack = [radical]
primes = []
while stack:
I = stack.pop()
N, G = _prime_decomp_compute_kernel(I, p, ZK)
if N.n == 1:
P = _prime_decomp_maximal_ideal(I, p, ZK)
primes.append(P)
else:
I1, I2 = _prime_decomp_split_ideal(I, p, N, G, ZK)
stack.extend([I1, I2])
return primes

View File

@ -0,0 +1,456 @@
"""Lookup table for Galois resolvents for polys of degree 4 through 6. """
# This table was generated by a call to
# `sympy.polys.numberfields.galois_resolvents.generate_lambda_lookup()`.
# The entire job took 543.23s.
# Of this, Case (6, 1) took 539.03s.
# The final polynomial of Case (6, 1) alone took 455.09s.
resolvent_coeff_lambdas = {
(4, 0): [
lambda s1, s2, s3, s4: (-2*s1*s2 + 6*s3),
lambda s1, s2, s3, s4: (2*s1**3*s3 + s1**2*s2**2 + s1**2*s4 - 17*s1*s2*s3 + 2*s2**3 - 8*s2*s4 + 24*s3**2),
lambda s1, s2, s3, s4: (-2*s1**5*s4 - 2*s1**4*s2*s3 + 10*s1**3*s2*s4 + 8*s1**3*s3**2 + 10*s1**2*s2**2*s3 -
12*s1**2*s3*s4 - 2*s1*s2**4 - 54*s1*s2*s3**2 + 32*s1*s4**2 + 8*s2**3*s3 - 32*s2*s3*s4
+ 56*s3**3),
lambda s1, s2, s3, s4: (2*s1**6*s2*s4 + s1**6*s3**2 - 5*s1**5*s3*s4 - 11*s1**4*s2**2*s4 - 13*s1**4*s2*s3**2
+ 7*s1**4*s4**2 + 3*s1**3*s2**3*s3 + 30*s1**3*s2*s3*s4 + 22*s1**3*s3**3 + 10*s1**2*s2**3*s4
+ 33*s1**2*s2**2*s3**2 - 72*s1**2*s2*s4**2 - 36*s1**2*s3**2*s4 - 13*s1*s2**4*s3 +
48*s1*s2**2*s3*s4 - 116*s1*s2*s3**3 + 144*s1*s3*s4**2 + s2**6 - 12*s2**4*s4 + 22*s2**3*s3**2
+ 48*s2**2*s4**2 - 120*s2*s3**2*s4 + 96*s3**4 - 64*s4**3),
lambda s1, s2, s3, s4: (-2*s1**8*s3*s4 - s1**7*s4**2 + 22*s1**6*s2*s3*s4 + 2*s1**6*s3**3 - 2*s1**5*s2**3*s4
- s1**5*s2**2*s3**2 - 29*s1**5*s3**2*s4 - 60*s1**4*s2**2*s3*s4 - 19*s1**4*s2*s3**3
+ 38*s1**4*s3*s4**2 + 9*s1**3*s2**4*s4 + 10*s1**3*s2**3*s3**2 + 24*s1**3*s2**2*s4**2
+ 134*s1**3*s2*s3**2*s4 + 28*s1**3*s3**4 + 16*s1**3*s4**3 - s1**2*s2**5*s3 - 4*s1**2*s2**3*s3*s4
+ 34*s1**2*s2**2*s3**3 - 288*s1**2*s2*s3*s4**2 - 104*s1**2*s3**3*s4 - 19*s1*s2**4*s3**2
+ 120*s1*s2**2*s3**2*s4 - 128*s1*s2*s3**4 + 336*s1*s3**2*s4**2 + 2*s2**6*s3 - 24*s2**4*s3*s4
+ 28*s2**3*s3**3 + 96*s2**2*s3*s4**2 - 176*s2*s3**3*s4 + 96*s3**5 - 128*s3*s4**3),
lambda s1, s2, s3, s4: (s1**10*s4**2 - 11*s1**8*s2*s4**2 - 2*s1**8*s3**2*s4 + s1**7*s2**2*s3*s4 + 15*s1**7*s3*s4**2
+ 45*s1**6*s2**2*s4**2 + 17*s1**6*s2*s3**2*s4 + s1**6*s3**4 - 5*s1**6*s4**3 - 12*s1**5*s2**3*s3*s4
- 133*s1**5*s2*s3*s4**2 - 22*s1**5*s3**3*s4 + s1**4*s2**5*s4 - 76*s1**4*s2**3*s4**2
- 6*s1**4*s2**2*s3**2*s4 - 12*s1**4*s2*s3**4 + 32*s1**4*s2*s4**3 + 128*s1**4*s3**2*s4**2
+ 29*s1**3*s2**4*s3*s4 + 2*s1**3*s2**3*s3**3 + 344*s1**3*s2**2*s3*s4**2 + 48*s1**3*s2*s3**3*s4
+ 16*s1**3*s3**5 - 48*s1**3*s3*s4**3 - 4*s1**2*s2**6*s4 + 32*s1**2*s2**4*s4**2 - 134*s1**2*s2**3*s3**2*s4
+ 36*s1**2*s2**2*s3**4 - 64*s1**2*s2**2*s4**3 - 648*s1**2*s2*s3**2*s4**2 - 48*s1**2*s3**4*s4
+ 16*s1*s2**5*s3*s4 - 12*s1*s2**4*s3**3 - 128*s1*s2**3*s3*s4**2 + 296*s1*s2**2*s3**3*s4
- 96*s1*s2*s3**5 + 256*s1*s2*s3*s4**3 + 416*s1*s3**3*s4**2 + s2**6*s3**2 - 28*s2**4*s3**2*s4
+ 16*s2**3*s3**4 + 176*s2**2*s3**2*s4**2 - 224*s2*s3**4*s4 + 64*s3**6 - 320*s3**2*s4**3)
],
(4, 1): [
lambda s1, s2, s3, s4: (-s2),
lambda s1, s2, s3, s4: (s1*s3 - 4*s4),
lambda s1, s2, s3, s4: (-s1**2*s4 + 4*s2*s4 - s3**2)
],
(5, 1): [
lambda s1, s2, s3, s4, s5: (-2*s1*s3 + 8*s4),
lambda s1, s2, s3, s4, s5: (-8*s1**3*s5 + 2*s1**2*s2*s4 + s1**2*s3**2 + 30*s1*s2*s5 - 14*s1*s3*s4 - 6*s2**2*s4
+ 2*s2*s3**2 - 50*s3*s5 + 40*s4**2),
lambda s1, s2, s3, s4, s5: (16*s1**4*s3*s5 - 2*s1**4*s4**2 - 2*s1**3*s2**2*s5 - 2*s1**3*s2*s3*s4 - 44*s1**3*s4*s5
- 66*s1**2*s2*s3*s5 + 21*s1**2*s2*s4**2 + 6*s1**2*s3**2*s4 - 50*s1**2*s5**2 + 9*s1*s2**3*s5
+ 5*s1*s2**2*s3*s4 - 2*s1*s2*s3**3 + 190*s1*s2*s4*s5 + 120*s1*s3**2*s5 - 80*s1*s3*s4**2
- 15*s2**2*s3*s5 - 40*s2**2*s4**2 + 21*s2*s3**2*s4 + 125*s2*s5**2 - 2*s3**4 - 400*s3*s4*s5
+ 160*s4**3),
lambda s1, s2, s3, s4, s5: (16*s1**6*s5**2 - 8*s1**5*s2*s4*s5 - 8*s1**5*s3**2*s5 + 2*s1**5*s3*s4**2 + 2*s1**4*s2**2*s3*s5
+ s1**4*s2**2*s4**2 - 120*s1**4*s2*s5**2 + 68*s1**4*s3*s4*s5 - 8*s1**4*s4**3 + 46*s1**3*s2**2*s4*s5
+ 28*s1**3*s2*s3**2*s5 - 19*s1**3*s2*s3*s4**2 + 250*s1**3*s3*s5**2 - 144*s1**3*s4**2*s5
- 9*s1**2*s2**3*s3*s5 - 6*s1**2*s2**3*s4**2 + 3*s1**2*s2**2*s3**2*s4 + 225*s1**2*s2**2*s5**2
- 354*s1**2*s2*s3*s4*s5 + 76*s1**2*s2*s4**3 - 70*s1**2*s3**3*s5 + 41*s1**2*s3**2*s4**2
- 200*s1**2*s4*s5**2 - 54*s1*s2**3*s4*s5 + 45*s1*s2**2*s3**2*s5 + 30*s1*s2**2*s3*s4**2
- 19*s1*s2*s3**3*s4 - 875*s1*s2*s3*s5**2 + 640*s1*s2*s4**2*s5 + 2*s1*s3**5 + 630*s1*s3**2*s4*s5
- 264*s1*s3*s4**3 + 9*s2**4*s4**2 - 6*s2**3*s3**2*s4 + s2**2*s3**4 + 90*s2**2*s3*s4*s5
- 136*s2**2*s4**3 - 50*s2*s3**3*s5 + 76*s2*s3**2*s4**2 + 500*s2*s4*s5**2 - 8*s3**4*s4
+ 625*s3**2*s5**2 - 1400*s3*s4**2*s5 + 400*s4**4),
lambda s1, s2, s3, s4, s5: (-32*s1**7*s3*s5**2 + 8*s1**7*s4**2*s5 + 8*s1**6*s2**2*s5**2 + 8*s1**6*s2*s3*s4*s5
- 2*s1**6*s2*s4**3 + 48*s1**6*s4*s5**2 - 2*s1**5*s2**3*s4*s5 + 264*s1**5*s2*s3*s5**2
- 94*s1**5*s2*s4**2*s5 - 24*s1**5*s3**2*s4*s5 + 6*s1**5*s3*s4**3 - 56*s1**5*s5**3
- 66*s1**4*s2**3*s5**2 - 50*s1**4*s2**2*s3*s4*s5 + 19*s1**4*s2**2*s4**3 + 8*s1**4*s2*s3**3*s5
- 2*s1**4*s2*s3**2*s4**2 - 318*s1**4*s2*s4*s5**2 - 352*s1**4*s3**2*s5**2 + 166*s1**4*s3*s4**2*s5
+ 3*s1**4*s4**4 + 15*s1**3*s2**4*s4*s5 - 2*s1**3*s2**3*s3**2*s5 - s1**3*s2**3*s3*s4**2
- 574*s1**3*s2**2*s3*s5**2 + 347*s1**3*s2**2*s4**2*s5 + 194*s1**3*s2*s3**2*s4*s5 -
89*s1**3*s2*s3*s4**3 + 350*s1**3*s2*s5**3 - 8*s1**3*s3**4*s5 + 4*s1**3*s3**3*s4**2
+ 1090*s1**3*s3*s4*s5**2 - 364*s1**3*s4**3*s5 + 162*s1**2*s2**4*s5**2 + 33*s1**2*s2**3*s3*s4*s5
- 51*s1**2*s2**3*s4**3 - 32*s1**2*s2**2*s3**3*s5 + 28*s1**2*s2**2*s3**2*s4**2 + 305*s1**2*s2**2*s4*s5**2
- 2*s1**2*s2*s3**4*s4 + 1340*s1**2*s2*s3**2*s5**2 - 901*s1**2*s2*s3*s4**2*s5 + 76*s1**2*s2*s4**4
- 234*s1**2*s3**3*s4*s5 + 102*s1**2*s3**2*s4**3 - 750*s1**2*s3*s5**3 - 550*s1**2*s4**2*s5**2
- 27*s1*s2**5*s4*s5 + 9*s1*s2**4*s3**2*s5 + 3*s1*s2**4*s3*s4**2 - s1*s2**3*s3**3*s4
+ 180*s1*s2**3*s3*s5**2 - 366*s1*s2**3*s4**2*s5 - 231*s1*s2**2*s3**2*s4*s5 + 212*s1*s2**2*s3*s4**3
- 375*s1*s2**2*s5**3 + 112*s1*s2*s3**4*s5 - 89*s1*s2*s3**3*s4**2 - 3075*s1*s2*s3*s4*s5**2
+ 1640*s1*s2*s4**3*s5 + 6*s1*s3**5*s4 - 850*s1*s3**3*s5**2 + 1220*s1*s3**2*s4**2*s5
- 384*s1*s3*s4**4 + 2500*s1*s4*s5**3 - 108*s2**5*s5**2 + 117*s2**4*s3*s4*s5 + 32*s2**4*s4**3
- 31*s2**3*s3**3*s5 - 51*s2**3*s3**2*s4**2 + 525*s2**3*s4*s5**2 + 19*s2**2*s3**4*s4
- 325*s2**2*s3**2*s5**2 + 260*s2**2*s3*s4**2*s5 - 256*s2**2*s4**4 - 2*s2*s3**6 + 105*s2*s3**3*s4*s5
+ 76*s2*s3**2*s4**3 + 625*s2*s3*s5**3 - 500*s2*s4**2*s5**2 - 58*s3**5*s5 + 3*s3**4*s4**2
+ 2750*s3**2*s4*s5**2 - 2400*s3*s4**3*s5 + 512*s4**5 - 3125*s5**4),
lambda s1, s2, s3, s4, s5: (16*s1**8*s3**2*s5**2 - 8*s1**8*s3*s4**2*s5 + s1**8*s4**4 - 8*s1**7*s2**2*s3*s5**2
+ 2*s1**7*s2**2*s4**2*s5 - 48*s1**7*s3*s4*s5**2 + 12*s1**7*s4**3*s5 + s1**6*s2**4*s5**2
+ 12*s1**6*s2**2*s4*s5**2 - 144*s1**6*s2*s3**2*s5**2 + 88*s1**6*s2*s3*s4**2*s5 - 13*s1**6*s2*s4**4
+ 56*s1**6*s3*s5**3 + 86*s1**6*s4**2*s5**2 + 72*s1**5*s2**3*s3*s5**2 - 22*s1**5*s2**3*s4**2*s5
- 4*s1**5*s2**2*s3**2*s4*s5 + s1**5*s2**2*s3*s4**3 - 14*s1**5*s2**2*s5**3 + 304*s1**5*s2*s3*s4*s5**2
- 148*s1**5*s2*s4**3*s5 + 152*s1**5*s3**3*s5**2 - 54*s1**5*s3**2*s4**2*s5 + 5*s1**5*s3*s4**4
- 468*s1**5*s4*s5**3 - 9*s1**4*s2**5*s5**2 + s1**4*s2**4*s3*s4*s5 - 76*s1**4*s2**3*s4*s5**2
+ 370*s1**4*s2**2*s3**2*s5**2 - 287*s1**4*s2**2*s3*s4**2*s5 + 65*s1**4*s2**2*s4**4
- 28*s1**4*s2*s3**3*s4*s5 + 5*s1**4*s2*s3**2*s4**3 - 200*s1**4*s2*s3*s5**3 - 294*s1**4*s2*s4**2*s5**2
+ 8*s1**4*s3**5*s5 - 2*s1**4*s3**4*s4**2 - 676*s1**4*s3**2*s4*s5**2 + 180*s1**4*s3*s4**3*s5
+ 17*s1**4*s4**5 + 625*s1**4*s5**4 - 210*s1**3*s2**4*s3*s5**2 + 76*s1**3*s2**4*s4**2*s5
+ 43*s1**3*s2**3*s3**2*s4*s5 - 15*s1**3*s2**3*s3*s4**3 + 50*s1**3*s2**3*s5**3 - 6*s1**3*s2**2*s3**4*s5
+ 2*s1**3*s2**2*s3**3*s4**2 - 397*s1**3*s2**2*s3*s4*s5**2 + 514*s1**3*s2**2*s4**3*s5
- 700*s1**3*s2*s3**3*s5**2 + 447*s1**3*s2*s3**2*s4**2*s5 - 118*s1**3*s2*s3*s4**4 +
2300*s1**3*s2*s4*s5**3 - 12*s1**3*s3**4*s4*s5 + 6*s1**3*s3**3*s4**3 + 250*s1**3*s3**2*s5**3
+ 1470*s1**3*s3*s4**2*s5**2 - 276*s1**3*s4**4*s5 + 27*s1**2*s2**6*s5**2 - 9*s1**2*s2**5*s3*s4*s5
+ s1**2*s2**5*s4**3 + s1**2*s2**4*s3**3*s5 + 141*s1**2*s2**4*s4*s5**2 - 185*s1**2*s2**3*s3**2*s5**2
+ 168*s1**2*s2**3*s3*s4**2*s5 - 128*s1**2*s2**3*s4**4 + 93*s1**2*s2**2*s3**3*s4*s5
+ 19*s1**2*s2**2*s3**2*s4**3 - 125*s1**2*s2**2*s3*s5**3 - 610*s1**2*s2**2*s4**2*s5**2
- 36*s1**2*s2*s3**5*s5 + 5*s1**2*s2*s3**4*s4**2 + 1995*s1**2*s2*s3**2*s4*s5**2 - 1174*s1**2*s2*s3*s4**3*s5
- 16*s1**2*s2*s4**5 - 3125*s1**2*s2*s5**4 + 375*s1**2*s3**4*s5**2 - 172*s1**2*s3**3*s4**2*s5
+ 82*s1**2*s3**2*s4**4 - 3500*s1**2*s3*s4*s5**3 - 1450*s1**2*s4**3*s5**2 + 198*s1*s2**5*s3*s5**2
- 78*s1*s2**5*s4**2*s5 - 95*s1*s2**4*s3**2*s4*s5 + 44*s1*s2**4*s3*s4**3 + 25*s1*s2**3*s3**4*s5
- 15*s1*s2**3*s3**3*s4**2 + 15*s1*s2**3*s3*s4*s5**2 - 384*s1*s2**3*s4**3*s5 + s1*s2**2*s3**5*s4
+ 525*s1*s2**2*s3**3*s5**2 - 528*s1*s2**2*s3**2*s4**2*s5 + 384*s1*s2**2*s3*s4**4 -
1750*s1*s2**2*s4*s5**3 - 29*s1*s2*s3**4*s4*s5 - 118*s1*s2*s3**3*s4**3 + 625*s1*s2*s3**2*s5**3
- 850*s1*s2*s3*s4**2*s5**2 + 1760*s1*s2*s4**4*s5 + 38*s1*s3**6*s5 + 5*s1*s3**5*s4**2
- 2050*s1*s3**3*s4*s5**2 + 780*s1*s3**2*s4**3*s5 - 192*s1*s3*s4**5 + 3125*s1*s3*s5**4
+ 7500*s1*s4**2*s5**3 - 27*s2**7*s5**2 + 18*s2**6*s3*s4*s5 - 4*s2**6*s4**3 - 4*s2**5*s3**3*s5
+ s2**5*s3**2*s4**2 - 99*s2**5*s4*s5**2 - 150*s2**4*s3**2*s5**2 + 196*s2**4*s3*s4**2*s5
+ 48*s2**4*s4**4 + 12*s2**3*s3**3*s4*s5 - 128*s2**3*s3**2*s4**3 + 1200*s2**3*s4**2*s5**2
- 12*s2**2*s3**5*s5 + 65*s2**2*s3**4*s4**2 - 725*s2**2*s3**2*s4*s5**2 - 160*s2**2*s3*s4**3*s5
- 192*s2**2*s4**5 + 3125*s2**2*s5**4 - 13*s2*s3**6*s4 - 125*s2*s3**4*s5**2 + 590*s2*s3**3*s4**2*s5
- 16*s2*s3**2*s4**4 - 1250*s2*s3*s4*s5**3 - 2000*s2*s4**3*s5**2 + s3**8 - 124*s3**5*s4*s5
+ 17*s3**4*s4**3 + 3250*s3**2*s4**2*s5**2 - 1600*s3*s4**4*s5 + 256*s4**6 - 9375*s4*s5**4)
],
(6, 1): [
lambda s1, s2, s3, s4, s5, s6: (8*s1*s5 - 2*s2*s4 - 18*s6),
lambda s1, s2, s3, s4, s5, s6: (-50*s1**2*s4*s6 + 40*s1**2*s5**2 + 30*s1*s2*s3*s6 - 14*s1*s2*s4*s5 - 6*s1*s3**2*s5
+ 2*s1*s3*s4**2 - 30*s1*s5*s6 - 8*s2**3*s6 + 2*s2**2*s3*s5 + s2**2*s4**2 + 114*s2*s4*s6
- 50*s2*s5**2 - 54*s3**2*s6 + 30*s3*s4*s5 - 8*s4**3 - 135*s6**2),
lambda s1, s2, s3, s4, s5, s6: (125*s1**3*s3*s6**2 - 400*s1**3*s4*s5*s6 + 160*s1**3*s5**3 - 50*s1**2*s2**2*s6**2 +
190*s1**2*s2*s3*s5*s6 + 120*s1**2*s2*s4**2*s6 - 80*s1**2*s2*s4*s5**2 - 15*s1**2*s3**2*s4*s6
- 40*s1**2*s3**2*s5**2 + 21*s1**2*s3*s4**2*s5 - 2*s1**2*s4**4 + 900*s1**2*s4*s6**2
- 80*s1**2*s5**2*s6 - 44*s1*s2**3*s5*s6 - 66*s1*s2**2*s3*s4*s6 + 21*s1*s2**2*s3*s5**2
+ 6*s1*s2**2*s4**2*s5 + 9*s1*s2*s3**3*s6 + 5*s1*s2*s3**2*s4*s5 - 2*s1*s2*s3*s4**3
- 990*s1*s2*s3*s6**2 + 920*s1*s2*s4*s5*s6 - 400*s1*s2*s5**3 - 135*s1*s3**2*s5*s6 -
126*s1*s3*s4**2*s6 + 190*s1*s3*s4*s5**2 - 44*s1*s4**3*s5 - 2070*s1*s5*s6**2 + 16*s2**4*s4*s6
- 2*s2**4*s5**2 - 2*s2**3*s3**2*s6 - 2*s2**3*s3*s4*s5 + 304*s2**3*s6**2 - 126*s2**2*s3*s5*s6
- 232*s2**2*s4**2*s6 + 120*s2**2*s4*s5**2 + 198*s2*s3**2*s4*s6 - 15*s2*s3**2*s5**2
- 66*s2*s3*s4**2*s5 + 16*s2*s4**4 - 1440*s2*s4*s6**2 + 900*s2*s5**2*s6 - 27*s3**4*s6
+ 9*s3**3*s4*s5 - 2*s3**2*s4**3 + 1350*s3**2*s6**2 - 990*s3*s4*s5*s6 + 125*s3*s5**3
+ 304*s4**3*s6 - 50*s4**2*s5**2 + 3240*s6**3),
lambda s1, s2, s3, s4, s5, s6: (500*s1**4*s3*s5*s6**2 + 625*s1**4*s4**2*s6**2 - 1400*s1**4*s4*s5**2*s6 + 400*s1**4*s5**4
- 200*s1**3*s2**2*s5*s6**2 - 875*s1**3*s2*s3*s4*s6**2 + 640*s1**3*s2*s3*s5**2*s6 +
630*s1**3*s2*s4**2*s5*s6 - 264*s1**3*s2*s4*s5**3 + 90*s1**3*s3**2*s4*s5*s6 - 136*s1**3*s3**2*s5**3
- 50*s1**3*s3*s4**3*s6 + 76*s1**3*s3*s4**2*s5**2 - 1125*s1**3*s3*s6**3 - 8*s1**3*s4**4*s5
+ 2550*s1**3*s4*s5*s6**2 - 200*s1**3*s5**3*s6 + 250*s1**2*s2**3*s4*s6**2 - 144*s1**2*s2**3*s5**2*s6
+ 225*s1**2*s2**2*s3**2*s6**2 - 354*s1**2*s2**2*s3*s4*s5*s6 + 76*s1**2*s2**2*s3*s5**3
- 70*s1**2*s2**2*s4**3*s6 + 41*s1**2*s2**2*s4**2*s5**2 + 450*s1**2*s2**2*s6**3 - 54*s1**2*s2*s3**3*s5*s6
+ 45*s1**2*s2*s3**2*s4**2*s6 + 30*s1**2*s2*s3**2*s4*s5**2 - 19*s1**2*s2*s3*s4**3*s5
- 2880*s1**2*s2*s3*s5*s6**2 + 2*s1**2*s2*s4**5 - 3480*s1**2*s2*s4**2*s6**2 + 4692*s1**2*s2*s4*s5**2*s6
- 1400*s1**2*s2*s5**4 + 9*s1**2*s3**4*s5**2 - 6*s1**2*s3**3*s4**2*s5 + s1**2*s3**2*s4**4
+ 1485*s1**2*s3**2*s4*s6**2 - 522*s1**2*s3**2*s5**2*s6 - 1257*s1**2*s3*s4**2*s5*s6
+ 640*s1**2*s3*s4*s5**3 + 218*s1**2*s4**4*s6 - 144*s1**2*s4**3*s5**2 + 1350*s1**2*s4*s6**3
- 5175*s1**2*s5**2*s6**2 - 120*s1*s2**4*s3*s6**2 + 68*s1*s2**4*s4*s5*s6 - 8*s1*s2**4*s5**3
+ 46*s1*s2**3*s3**2*s5*s6 + 28*s1*s2**3*s3*s4**2*s6 - 19*s1*s2**3*s3*s4*s5**2 + 868*s1*s2**3*s5*s6**2
- 9*s1*s2**2*s3**3*s4*s6 - 6*s1*s2**2*s3**3*s5**2 + 3*s1*s2**2*s3**2*s4**2*s5 + 2484*s1*s2**2*s3*s4*s6**2
- 1257*s1*s2**2*s3*s5**2*s6 - 1356*s1*s2**2*s4**2*s5*s6 + 630*s1*s2**2*s4*s5**3 -
891*s1*s2*s3**3*s6**2 + 882*s1*s2*s3**2*s4*s5*s6 + 90*s1*s2*s3**2*s5**3 + 84*s1*s2*s3*s4**3*s6
- 354*s1*s2*s3*s4**2*s5**2 + 3240*s1*s2*s3*s6**3 + 68*s1*s2*s4**4*s5 - 4392*s1*s2*s4*s5*s6**2
+ 2550*s1*s2*s5**3*s6 + 54*s1*s3**4*s5*s6 - 54*s1*s3**3*s4**2*s6 - 54*s1*s3**3*s4*s5**2
+ 46*s1*s3**2*s4**3*s5 + 2727*s1*s3**2*s5*s6**2 - 8*s1*s3*s4**5 + 756*s1*s3*s4**2*s6**2
- 2880*s1*s3*s4*s5**2*s6 + 500*s1*s3*s5**4 + 868*s1*s4**3*s5*s6 - 200*s1*s4**2*s5**3
+ 8100*s1*s5*s6**3 + 16*s2**6*s6**2 - 8*s2**5*s3*s5*s6 - 8*s2**5*s4**2*s6 + 2*s2**5*s4*s5**2
+ 2*s2**4*s3**2*s4*s6 + s2**4*s3**2*s5**2 - 688*s2**4*s4*s6**2 + 218*s2**4*s5**2*s6
+ 234*s2**3*s3**2*s6**2 + 84*s2**3*s3*s4*s5*s6 - 50*s2**3*s3*s5**3 + 168*s2**3*s4**3*s6
- 70*s2**3*s4**2*s5**2 - 1224*s2**3*s6**3 - 54*s2**2*s3**3*s5*s6 - 144*s2**2*s3**2*s4**2*s6
+ 45*s2**2*s3**2*s4*s5**2 + 28*s2**2*s3*s4**3*s5 + 756*s2**2*s3*s5*s6**2 - 8*s2**2*s4**5
+ 4320*s2**2*s4**2*s6**2 - 3480*s2**2*s4*s5**2*s6 + 625*s2**2*s5**4 + 27*s2*s3**4*s4*s6
- 9*s2*s3**3*s4**2*s5 + 2*s2*s3**2*s4**4 - 4752*s2*s3**2*s4*s6**2 + 1485*s2*s3**2*s5**2*s6
+ 2484*s2*s3*s4**2*s5*s6 - 875*s2*s3*s4*s5**3 - 688*s2*s4**4*s6 + 250*s2*s4**3*s5**2
- 4536*s2*s4*s6**3 + 1350*s2*s5**2*s6**2 + 972*s3**4*s6**2 - 891*s3**3*s4*s5*s6 +
234*s3**2*s4**3*s6 + 225*s3**2*s4**2*s5**2 - 1944*s3**2*s6**3 - 120*s3*s4**4*s5 +
3240*s3*s4*s5*s6**2 - 1125*s3*s5**3*s6 + 16*s4**6 - 1224*s4**3*s6**2 + 450*s4**2*s5**2*s6),
lambda s1, s2, s3, s4, s5, s6: (-3125*s1**6*s6**4 + 2500*s1**5*s2*s5*s6**3 + 625*s1**5*s3*s4*s6**3 - 500*s1**5*s3*s5**2*s6**2
+ 2750*s1**5*s4**2*s5*s6**2 - 2400*s1**5*s4*s5**3*s6 + 512*s1**5*s5**5 - 750*s1**4*s2**2*s4*s6**3
- 550*s1**4*s2**2*s5**2*s6**2 - 375*s1**4*s2*s3**2*s6**3 - 3075*s1**4*s2*s3*s4*s5*s6**2
+ 1640*s1**4*s2*s3*s5**3*s6 - 850*s1**4*s2*s4**3*s6**2 + 1220*s1**4*s2*s4**2*s5**2*s6
- 384*s1**4*s2*s4*s5**4 + 22500*s1**4*s2*s6**4 + 525*s1**4*s3**3*s5*s6**2 - 325*s1**4*s3**2*s4**2*s6**2
+ 260*s1**4*s3**2*s4*s5**2*s6 - 256*s1**4*s3**2*s5**4 + 105*s1**4*s3*s4**3*s5*s6 +
76*s1**4*s3*s4**2*s5**3 + 375*s1**4*s3*s5*s6**3 - 58*s1**4*s4**5*s6 + 3*s1**4*s4**4*s5**2
- 12750*s1**4*s4**2*s6**3 + 3700*s1**4*s4*s5**2*s6**2 + 640*s1**4*s5**4*s6 + 350*s1**3*s2**3*s3*s6**3
+ 1090*s1**3*s2**3*s4*s5*s6**2 - 364*s1**3*s2**3*s5**3*s6 + 305*s1**3*s2**2*s3**2*s5*s6**2
+ 1340*s1**3*s2**2*s3*s4**2*s6**2 - 901*s1**3*s2**2*s3*s4*s5**2*s6 + 76*s1**3*s2**2*s3*s5**4
- 234*s1**3*s2**2*s4**3*s5*s6 + 102*s1**3*s2**2*s4**2*s5**3 - 16650*s1**3*s2**2*s5*s6**3
+ 180*s1**3*s2*s3**3*s4*s6**2 - 366*s1**3*s2*s3**3*s5**2*s6 - 231*s1**3*s2*s3**2*s4**2*s5*s6
+ 212*s1**3*s2*s3**2*s4*s5**3 + 112*s1**3*s2*s3*s4**4*s6 - 89*s1**3*s2*s3*s4**3*s5**2
+ 10950*s1**3*s2*s3*s4*s6**3 + 1555*s1**3*s2*s3*s5**2*s6**2 + 6*s1**3*s2*s4**5*s5
- 9540*s1**3*s2*s4**2*s5*s6**2 + 9016*s1**3*s2*s4*s5**3*s6 - 2400*s1**3*s2*s5**5 -
108*s1**3*s3**5*s6**2 + 117*s1**3*s3**4*s4*s5*s6 + 32*s1**3*s3**4*s5**3 - 31*s1**3*s3**3*s4**3*s6
- 51*s1**3*s3**3*s4**2*s5**2 - 2025*s1**3*s3**3*s6**3 + 19*s1**3*s3**2*s4**4*s5 +
2955*s1**3*s3**2*s4*s5*s6**2 - 1436*s1**3*s3**2*s5**3*s6 - 2*s1**3*s3*s4**6 + 2770*s1**3*s3*s4**3*s6**2
- 5123*s1**3*s3*s4**2*s5**2*s6 + 1640*s1**3*s3*s4*s5**4 - 40500*s1**3*s3*s6**4 + 914*s1**3*s4**4*s5*s6
- 364*s1**3*s4**3*s5**3 + 53550*s1**3*s4*s5*s6**3 - 17930*s1**3*s5**3*s6**2 - 56*s1**2*s2**5*s6**3
- 318*s1**2*s2**4*s3*s5*s6**2 - 352*s1**2*s2**4*s4**2*s6**2 + 166*s1**2*s2**4*s4*s5**2*s6
+ 3*s1**2*s2**4*s5**4 - 574*s1**2*s2**3*s3**2*s4*s6**2 + 347*s1**2*s2**3*s3**2*s5**2*s6
+ 194*s1**2*s2**3*s3*s4**2*s5*s6 - 89*s1**2*s2**3*s3*s4*s5**3 - 8*s1**2*s2**3*s4**4*s6
+ 4*s1**2*s2**3*s4**3*s5**2 + 560*s1**2*s2**3*s4*s6**3 + 3662*s1**2*s2**3*s5**2*s6**2
+ 162*s1**2*s2**2*s3**4*s6**2 + 33*s1**2*s2**2*s3**3*s4*s5*s6 - 51*s1**2*s2**2*s3**3*s5**3
- 32*s1**2*s2**2*s3**2*s4**3*s6 + 28*s1**2*s2**2*s3**2*s4**2*s5**2 + 270*s1**2*s2**2*s3**2*s6**3
- 2*s1**2*s2**2*s3*s4**4*s5 + 4872*s1**2*s2**2*s3*s4*s5*s6**2 - 5123*s1**2*s2**2*s3*s5**3*s6
+ 2144*s1**2*s2**2*s4**3*s6**2 - 2812*s1**2*s2**2*s4**2*s5**2*s6 + 1220*s1**2*s2**2*s4*s5**4
- 37800*s1**2*s2**2*s6**4 - 27*s1**2*s2*s3**5*s5*s6 + 9*s1**2*s2*s3**4*s4**2*s6 +
3*s1**2*s2*s3**4*s4*s5**2 - s1**2*s2*s3**3*s4**3*s5 - 3078*s1**2*s2*s3**3*s5*s6**2
- 4014*s1**2*s2*s3**2*s4**2*s6**2 + 5412*s1**2*s2*s3**2*s4*s5**2*s6 + 260*s1**2*s2*s3**2*s5**4
- 310*s1**2*s2*s3*s4**3*s5*s6 - 901*s1**2*s2*s3*s4**2*s5**3 - 3780*s1**2*s2*s3*s5*s6**3
+ 166*s1**2*s2*s4**4*s5**2 + 40320*s1**2*s2*s4**2*s6**3 - 25344*s1**2*s2*s4*s5**2*s6**2
+ 3700*s1**2*s2*s5**4*s6 + 918*s1**2*s3**4*s4*s6**2 + 27*s1**2*s3**4*s5**2*s6 - 342*s1**2*s3**3*s4**2*s5*s6
- 366*s1**2*s3**3*s4*s5**3 + 32*s1**2*s3**2*s4**4*s6 + 347*s1**2*s3**2*s4**3*s5**2
- 4590*s1**2*s3**2*s4*s6**3 + 594*s1**2*s3**2*s5**2*s6**2 - 94*s1**2*s3*s4**5*s5 +
3618*s1**2*s3*s4**2*s5*s6**2 + 1555*s1**2*s3*s4*s5**3*s6 - 500*s1**2*s3*s5**5 + 8*s1**2*s4**7
- 7192*s1**2*s4**4*s6**2 + 3662*s1**2*s4**3*s5**2*s6 - 550*s1**2*s4**2*s5**4 - 48600*s1**2*s4*s6**4
+ 1080*s1**2*s5**2*s6**3 + 48*s1*s2**6*s5*s6**2 + 264*s1*s2**5*s3*s4*s6**2 - 94*s1*s2**5*s3*s5**2*s6
- 24*s1*s2**5*s4**2*s5*s6 + 6*s1*s2**5*s4*s5**3 - 66*s1*s2**4*s3**3*s6**2 - 50*s1*s2**4*s3**2*s4*s5*s6
+ 19*s1*s2**4*s3**2*s5**3 + 8*s1*s2**4*s3*s4**3*s6 - 2*s1*s2**4*s3*s4**2*s5**2 - 552*s1*s2**4*s3*s6**3
- 2560*s1*s2**4*s4*s5*s6**2 + 914*s1*s2**4*s5**3*s6 + 15*s1*s2**3*s3**4*s5*s6 - 2*s1*s2**3*s3**3*s4**2*s6
- s1*s2**3*s3**3*s4*s5**2 + 1602*s1*s2**3*s3**2*s5*s6**2 - 608*s1*s2**3*s3*s4**2*s6**2
- 310*s1*s2**3*s3*s4*s5**2*s6 + 105*s1*s2**3*s3*s5**4 + 600*s1*s2**3*s4**3*s5*s6 -
234*s1*s2**3*s4**2*s5**3 + 31368*s1*s2**3*s5*s6**3 + 756*s1*s2**2*s3**3*s4*s6**2 -
342*s1*s2**2*s3**3*s5**2*s6 + 216*s1*s2**2*s3**2*s4**2*s5*s6 - 231*s1*s2**2*s3**2*s4*s5**3
- 192*s1*s2**2*s3*s4**4*s6 + 194*s1*s2**2*s3*s4**3*s5**2 - 39096*s1*s2**2*s3*s4*s6**3
+ 3618*s1*s2**2*s3*s5**2*s6**2 - 24*s1*s2**2*s4**5*s5 + 9408*s1*s2**2*s4**2*s5*s6**2
- 9540*s1*s2**2*s4*s5**3*s6 + 2750*s1*s2**2*s5**5 - 162*s1*s2*s3**5*s6**2 - 378*s1*s2*s3**4*s4*s5*s6
+ 117*s1*s2*s3**4*s5**3 + 150*s1*s2*s3**3*s4**3*s6 + 33*s1*s2*s3**3*s4**2*s5**2 +
10044*s1*s2*s3**3*s6**3 - 50*s1*s2*s3**2*s4**4*s5 - 8640*s1*s2*s3**2*s4*s5*s6**2 +
2955*s1*s2*s3**2*s5**3*s6 + 8*s1*s2*s3*s4**6 + 6144*s1*s2*s3*s4**3*s6**2 + 4872*s1*s2*s3*s4**2*s5**2*s6
- 3075*s1*s2*s3*s4*s5**4 + 174960*s1*s2*s3*s6**4 - 2560*s1*s2*s4**4*s5*s6 + 1090*s1*s2*s4**3*s5**3
- 148824*s1*s2*s4*s5*s6**3 + 53550*s1*s2*s5**3*s6**2 + 81*s1*s3**6*s5*s6 - 27*s1*s3**5*s4**2*s6
- 27*s1*s3**5*s4*s5**2 + 15*s1*s3**4*s4**3*s5 + 2430*s1*s3**4*s5*s6**2 - 2*s1*s3**3*s4**5
- 2052*s1*s3**3*s4**2*s6**2 - 3078*s1*s3**3*s4*s5**2*s6 + 525*s1*s3**3*s5**4 + 1602*s1*s3**2*s4**3*s5*s6
+ 305*s1*s3**2*s4**2*s5**3 + 18144*s1*s3**2*s5*s6**3 - 104*s1*s3*s4**5*s6 - 318*s1*s3*s4**4*s5**2
- 33696*s1*s3*s4**2*s6**3 - 3780*s1*s3*s4*s5**2*s6**2 + 375*s1*s3*s5**4*s6 + 48*s1*s4**6*s5
+ 31368*s1*s4**3*s5*s6**2 - 16650*s1*s4**2*s5**3*s6 + 2500*s1*s4*s5**5 + 77760*s1*s5*s6**4
- 32*s2**7*s4*s6**2 + 8*s2**7*s5**2*s6 + 8*s2**6*s3**2*s6**2 + 8*s2**6*s3*s4*s5*s6
- 2*s2**6*s3*s5**3 + 96*s2**6*s6**3 - 2*s2**5*s3**3*s5*s6 - 104*s2**5*s3*s5*s6**2
+ 416*s2**5*s4**2*s6**2 - 58*s2**5*s5**4 - 312*s2**4*s3**2*s4*s6**2 + 32*s2**4*s3**2*s5**2*s6
- 192*s2**4*s3*s4**2*s5*s6 + 112*s2**4*s3*s4*s5**3 - 8*s2**4*s4**3*s5**2 + 4224*s2**4*s4*s6**3
- 7192*s2**4*s5**2*s6**2 + 54*s2**3*s3**4*s6**2 + 150*s2**3*s3**3*s4*s5*s6 - 31*s2**3*s3**3*s5**3
- 32*s2**3*s3**2*s4**2*s5**2 - 864*s2**3*s3**2*s6**3 + 8*s2**3*s3*s4**4*s5 + 6144*s2**3*s3*s4*s5*s6**2
+ 2770*s2**3*s3*s5**3*s6 - 4032*s2**3*s4**3*s6**2 + 2144*s2**3*s4**2*s5**2*s6 - 850*s2**3*s4*s5**4
- 16416*s2**3*s6**4 - 27*s2**2*s3**5*s5*s6 + 9*s2**2*s3**4*s4*s5**2 - 2*s2**2*s3**3*s4**3*s5
- 2052*s2**2*s3**3*s5*s6**2 + 2376*s2**2*s3**2*s4**2*s6**2 - 4014*s2**2*s3**2*s4*s5**2*s6
- 325*s2**2*s3**2*s5**4 - 608*s2**2*s3*s4**3*s5*s6 + 1340*s2**2*s3*s4**2*s5**3 - 33696*s2**2*s3*s5*s6**3
+ 416*s2**2*s4**5*s6 - 352*s2**2*s4**4*s5**2 - 6048*s2**2*s4**2*s6**3 + 40320*s2**2*s4*s5**2*s6**2
- 12750*s2**2*s5**4*s6 - 324*s2*s3**4*s4*s6**2 + 918*s2*s3**4*s5**2*s6 + 756*s2*s3**3*s4**2*s5*s6
+ 180*s2*s3**3*s4*s5**3 - 312*s2*s3**2*s4**4*s6 - 574*s2*s3**2*s4**3*s5**2 + 43416*s2*s3**2*s4*s6**3
- 4590*s2*s3**2*s5**2*s6**2 + 264*s2*s3*s4**5*s5 - 39096*s2*s3*s4**2*s5*s6**2 + 10950*s2*s3*s4*s5**3*s6
+ 625*s2*s3*s5**5 - 32*s2*s4**7 + 4224*s2*s4**4*s6**2 + 560*s2*s4**3*s5**2*s6 - 750*s2*s4**2*s5**4
+ 85536*s2*s4*s6**4 - 48600*s2*s5**2*s6**3 - 162*s3**5*s4*s5*s6 - 108*s3**5*s5**3
+ 54*s3**4*s4**3*s6 + 162*s3**4*s4**2*s5**2 - 11664*s3**4*s6**3 - 66*s3**3*s4**4*s5
+ 10044*s3**3*s4*s5*s6**2 - 2025*s3**3*s5**3*s6 + 8*s3**2*s4**6 - 864*s3**2*s4**3*s6**2
+ 270*s3**2*s4**2*s5**2*s6 - 375*s3**2*s4*s5**4 - 163296*s3**2*s6**4 - 552*s3*s4**4*s5*s6
+ 350*s3*s4**3*s5**3 + 174960*s3*s4*s5*s6**3 - 40500*s3*s5**3*s6**2 + 96*s4**6*s6
- 56*s4**5*s5**2 - 16416*s4**3*s6**3 - 37800*s4**2*s5**2*s6**2 + 22500*s4*s5**4*s6
- 3125*s5**6 - 93312*s6**5),
lambda s1, s2, s3, s4, s5, s6: (-9375*s1**7*s5*s6**4 + 3125*s1**6*s2*s4*s6**4 + 7500*s1**6*s2*s5**2*s6**3 + 3125*s1**6*s3**2*s6**4
- 1250*s1**6*s3*s4*s5*s6**3 - 2000*s1**6*s3*s5**3*s6**2 + 3250*s1**6*s4**2*s5**2*s6**2
- 1600*s1**6*s4*s5**4*s6 + 256*s1**6*s5**6 + 40625*s1**6*s6**5 - 3125*s1**5*s2**2*s3*s6**4
- 3500*s1**5*s2**2*s4*s5*s6**3 - 1450*s1**5*s2**2*s5**3*s6**2 - 1750*s1**5*s2*s3**2*s5*s6**3
+ 625*s1**5*s2*s3*s4**2*s6**3 - 850*s1**5*s2*s3*s4*s5**2*s6**2 + 1760*s1**5*s2*s3*s5**4*s6
- 2050*s1**5*s2*s4**3*s5*s6**2 + 780*s1**5*s2*s4**2*s5**3*s6 - 192*s1**5*s2*s4*s5**5
+ 35000*s1**5*s2*s5*s6**4 + 1200*s1**5*s3**3*s5**2*s6**2 - 725*s1**5*s3**2*s4**2*s5*s6**2
- 160*s1**5*s3**2*s4*s5**3*s6 - 192*s1**5*s3**2*s5**5 - 125*s1**5*s3*s4**4*s6**2 +
590*s1**5*s3*s4**3*s5**2*s6 - 16*s1**5*s3*s4**2*s5**4 - 20625*s1**5*s3*s4*s6**4 +
17250*s1**5*s3*s5**2*s6**3 - 124*s1**5*s4**5*s5*s6 + 17*s1**5*s4**4*s5**3 - 20250*s1**5*s4**2*s5*s6**3
+ 1900*s1**5*s4*s5**3*s6**2 + 1344*s1**5*s5**5*s6 + 625*s1**4*s2**4*s6**4 + 2300*s1**4*s2**3*s3*s5*s6**3
+ 250*s1**4*s2**3*s4**2*s6**3 + 1470*s1**4*s2**3*s4*s5**2*s6**2 - 276*s1**4*s2**3*s5**4*s6
- 125*s1**4*s2**2*s3**2*s4*s6**3 - 610*s1**4*s2**2*s3**2*s5**2*s6**2 + 1995*s1**4*s2**2*s3*s4**2*s5*s6**2
- 1174*s1**4*s2**2*s3*s4*s5**3*s6 - 16*s1**4*s2**2*s3*s5**5 + 375*s1**4*s2**2*s4**4*s6**2
- 172*s1**4*s2**2*s4**3*s5**2*s6 + 82*s1**4*s2**2*s4**2*s5**4 - 7750*s1**4*s2**2*s4*s6**4
- 46650*s1**4*s2**2*s5**2*s6**3 + 15*s1**4*s2*s3**3*s4*s5*s6**2 - 384*s1**4*s2*s3**3*s5**3*s6
+ 525*s1**4*s2*s3**2*s4**3*s6**2 - 528*s1**4*s2*s3**2*s4**2*s5**2*s6 + 384*s1**4*s2*s3**2*s4*s5**4
- 10125*s1**4*s2*s3**2*s6**4 - 29*s1**4*s2*s3*s4**4*s5*s6 - 118*s1**4*s2*s3*s4**3*s5**3
+ 36700*s1**4*s2*s3*s4*s5*s6**3 + 2410*s1**4*s2*s3*s5**3*s6**2 + 38*s1**4*s2*s4**6*s6
+ 5*s1**4*s2*s4**5*s5**2 + 5550*s1**4*s2*s4**3*s6**3 - 10040*s1**4*s2*s4**2*s5**2*s6**2
+ 5800*s1**4*s2*s4*s5**4*s6 - 1600*s1**4*s2*s5**6 - 292500*s1**4*s2*s6**5 - 99*s1**4*s3**5*s5*s6**2
- 150*s1**4*s3**4*s4**2*s6**2 + 196*s1**4*s3**4*s4*s5**2*s6 + 48*s1**4*s3**4*s5**4
+ 12*s1**4*s3**3*s4**3*s5*s6 - 128*s1**4*s3**3*s4**2*s5**3 - 6525*s1**4*s3**3*s5*s6**3
- 12*s1**4*s3**2*s4**5*s6 + 65*s1**4*s3**2*s4**4*s5**2 + 225*s1**4*s3**2*s4**2*s6**3
+ 80*s1**4*s3**2*s4*s5**2*s6**2 - 13*s1**4*s3*s4**6*s5 + 5145*s1**4*s3*s4**3*s5*s6**2
- 6746*s1**4*s3*s4**2*s5**3*s6 + 1760*s1**4*s3*s4*s5**5 - 103500*s1**4*s3*s5*s6**4
+ s1**4*s4**8 + 954*s1**4*s4**5*s6**2 + 449*s1**4*s4**4*s5**2*s6 - 276*s1**4*s4**3*s5**4
+ 70125*s1**4*s4**2*s6**4 + 58900*s1**4*s4*s5**2*s6**3 - 23310*s1**4*s5**4*s6**2 -
468*s1**3*s2**5*s5*s6**3 - 200*s1**3*s2**4*s3*s4*s6**3 - 294*s1**3*s2**4*s3*s5**2*s6**2
- 676*s1**3*s2**4*s4**2*s5*s6**2 + 180*s1**3*s2**4*s4*s5**3*s6 + 17*s1**3*s2**4*s5**5
+ 50*s1**3*s2**3*s3**3*s6**3 - 397*s1**3*s2**3*s3**2*s4*s5*s6**2 + 514*s1**3*s2**3*s3**2*s5**3*s6
- 700*s1**3*s2**3*s3*s4**3*s6**2 + 447*s1**3*s2**3*s3*s4**2*s5**2*s6 - 118*s1**3*s2**3*s3*s4*s5**4
+ 11700*s1**3*s2**3*s3*s6**4 - 12*s1**3*s2**3*s4**4*s5*s6 + 6*s1**3*s2**3*s4**3*s5**3
+ 10360*s1**3*s2**3*s4*s5*s6**3 + 11404*s1**3*s2**3*s5**3*s6**2 + 141*s1**3*s2**2*s3**4*s5*s6**2
- 185*s1**3*s2**2*s3**3*s4**2*s6**2 + 168*s1**3*s2**2*s3**3*s4*s5**2*s6 - 128*s1**3*s2**2*s3**3*s5**4
+ 93*s1**3*s2**2*s3**2*s4**3*s5*s6 + 19*s1**3*s2**2*s3**2*s4**2*s5**3 + 5895*s1**3*s2**2*s3**2*s5*s6**3
- 36*s1**3*s2**2*s3*s4**5*s6 + 5*s1**3*s2**2*s3*s4**4*s5**2 - 12020*s1**3*s2**2*s3*s4**2*s6**3
- 5698*s1**3*s2**2*s3*s4*s5**2*s6**2 - 6746*s1**3*s2**2*s3*s5**4*s6 + 5064*s1**3*s2**2*s4**3*s5*s6**2
- 762*s1**3*s2**2*s4**2*s5**3*s6 + 780*s1**3*s2**2*s4*s5**5 + 93900*s1**3*s2**2*s5*s6**4
+ 198*s1**3*s2*s3**5*s4*s6**2 - 78*s1**3*s2*s3**5*s5**2*s6 - 95*s1**3*s2*s3**4*s4**2*s5*s6
+ 44*s1**3*s2*s3**4*s4*s5**3 + 25*s1**3*s2*s3**3*s4**4*s6 - 15*s1**3*s2*s3**3*s4**3*s5**2
+ 1935*s1**3*s2*s3**3*s4*s6**3 - 2808*s1**3*s2*s3**3*s5**2*s6**2 + s1**3*s2*s3**2*s4**5*s5
- 4844*s1**3*s2*s3**2*s4**2*s5*s6**2 + 8996*s1**3*s2*s3**2*s4*s5**3*s6 - 160*s1**3*s2*s3**2*s5**5
- 3616*s1**3*s2*s3*s4**4*s6**2 + 500*s1**3*s2*s3*s4**3*s5**2*s6 - 1174*s1**3*s2*s3*s4**2*s5**4
+ 72900*s1**3*s2*s3*s4*s6**4 - 55665*s1**3*s2*s3*s5**2*s6**3 + 128*s1**3*s2*s4**5*s5*s6
+ 180*s1**3*s2*s4**4*s5**3 + 16240*s1**3*s2*s4**2*s5*s6**3 - 9330*s1**3*s2*s4*s5**3*s6**2
+ 1900*s1**3*s2*s5**5*s6 - 27*s1**3*s3**7*s6**2 + 18*s1**3*s3**6*s4*s5*s6 - 4*s1**3*s3**6*s5**3
- 4*s1**3*s3**5*s4**3*s6 + s1**3*s3**5*s4**2*s5**2 + 54*s1**3*s3**5*s6**3 + 1143*s1**3*s3**4*s4*s5*s6**2
- 820*s1**3*s3**4*s5**3*s6 + 923*s1**3*s3**3*s4**3*s6**2 + 57*s1**3*s3**3*s4**2*s5**2*s6
- 384*s1**3*s3**3*s4*s5**4 + 29700*s1**3*s3**3*s6**4 - 547*s1**3*s3**2*s4**4*s5*s6
+ 514*s1**3*s3**2*s4**3*s5**3 - 10305*s1**3*s3**2*s4*s5*s6**3 - 7405*s1**3*s3**2*s5**3*s6**2
+ 108*s1**3*s3*s4**6*s6 - 148*s1**3*s3*s4**5*s5**2 - 11360*s1**3*s3*s4**3*s6**3 +
22209*s1**3*s3*s4**2*s5**2*s6**2 + 2410*s1**3*s3*s4*s5**4*s6 - 2000*s1**3*s3*s5**6
+ 432000*s1**3*s3*s6**5 + 12*s1**3*s4**7*s5 - 22624*s1**3*s4**4*s5*s6**2 + 11404*s1**3*s4**3*s5**3*s6
- 1450*s1**3*s4**2*s5**5 - 242100*s1**3*s4*s5*s6**4 + 58430*s1**3*s5**3*s6**3 + 56*s1**2*s2**6*s4*s6**3
+ 86*s1**2*s2**6*s5**2*s6**2 - 14*s1**2*s2**5*s3**2*s6**3 + 304*s1**2*s2**5*s3*s4*s5*s6**2
- 148*s1**2*s2**5*s3*s5**3*s6 + 152*s1**2*s2**5*s4**3*s6**2 - 54*s1**2*s2**5*s4**2*s5**2*s6
+ 5*s1**2*s2**5*s4*s5**4 - 2472*s1**2*s2**5*s6**4 - 76*s1**2*s2**4*s3**3*s5*s6**2
+ 370*s1**2*s2**4*s3**2*s4**2*s6**2 - 287*s1**2*s2**4*s3**2*s4*s5**2*s6 + 65*s1**2*s2**4*s3**2*s5**4
- 28*s1**2*s2**4*s3*s4**3*s5*s6 + 5*s1**2*s2**4*s3*s4**2*s5**3 - 8092*s1**2*s2**4*s3*s5*s6**3
+ 8*s1**2*s2**4*s4**5*s6 - 2*s1**2*s2**4*s4**4*s5**2 + 1096*s1**2*s2**4*s4**2*s6**3
- 5144*s1**2*s2**4*s4*s5**2*s6**2 + 449*s1**2*s2**4*s5**4*s6 - 210*s1**2*s2**3*s3**4*s4*s6**2
+ 76*s1**2*s2**3*s3**4*s5**2*s6 + 43*s1**2*s2**3*s3**3*s4**2*s5*s6 - 15*s1**2*s2**3*s3**3*s4*s5**3
- 6*s1**2*s2**3*s3**2*s4**4*s6 + 2*s1**2*s2**3*s3**2*s4**3*s5**2 + 1962*s1**2*s2**3*s3**2*s4*s6**3
+ 3181*s1**2*s2**3*s3**2*s5**2*s6**2 + 1684*s1**2*s2**3*s3*s4**2*s5*s6**2 + 500*s1**2*s2**3*s3*s4*s5**3*s6
+ 590*s1**2*s2**3*s3*s5**5 - 168*s1**2*s2**3*s4**4*s6**2 - 494*s1**2*s2**3*s4**3*s5**2*s6
- 172*s1**2*s2**3*s4**2*s5**4 - 22080*s1**2*s2**3*s4*s6**4 + 58894*s1**2*s2**3*s5**2*s6**3
+ 27*s1**2*s2**2*s3**6*s6**2 - 9*s1**2*s2**2*s3**5*s4*s5*s6 + s1**2*s2**2*s3**5*s5**3
+ s1**2*s2**2*s3**4*s4**3*s6 - 486*s1**2*s2**2*s3**4*s6**3 + 1071*s1**2*s2**2*s3**3*s4*s5*s6**2
+ 57*s1**2*s2**2*s3**3*s5**3*s6 + 2262*s1**2*s2**2*s3**2*s4**3*s6**2 - 2742*s1**2*s2**2*s3**2*s4**2*s5**2*s6
- 528*s1**2*s2**2*s3**2*s4*s5**4 - 29160*s1**2*s2**2*s3**2*s6**4 + 772*s1**2*s2**2*s3*s4**4*s5*s6
+ 447*s1**2*s2**2*s3*s4**3*s5**3 - 96732*s1**2*s2**2*s3*s4*s5*s6**3 + 22209*s1**2*s2**2*s3*s5**3*s6**2
- 160*s1**2*s2**2*s4**6*s6 - 54*s1**2*s2**2*s4**5*s5**2 - 7992*s1**2*s2**2*s4**3*s6**3
+ 8634*s1**2*s2**2*s4**2*s5**2*s6**2 - 10040*s1**2*s2**2*s4*s5**4*s6 + 3250*s1**2*s2**2*s5**6
+ 529200*s1**2*s2**2*s6**5 - 351*s1**2*s2*s3**5*s5*s6**2 - 1215*s1**2*s2*s3**4*s4**2*s6**2
- 360*s1**2*s2*s3**4*s4*s5**2*s6 + 196*s1**2*s2*s3**4*s5**4 + 741*s1**2*s2*s3**3*s4**3*s5*s6
+ 168*s1**2*s2*s3**3*s4**2*s5**3 + 11718*s1**2*s2*s3**3*s5*s6**3 - 106*s1**2*s2*s3**2*s4**5*s6
- 287*s1**2*s2*s3**2*s4**4*s5**2 + 22572*s1**2*s2*s3**2*s4**2*s6**3 - 8892*s1**2*s2*s3**2*s4*s5**2*s6**2
+ 80*s1**2*s2*s3**2*s5**4*s6 + 88*s1**2*s2*s3*s4**6*s5 + 22144*s1**2*s2*s3*s4**3*s5*s6**2
- 5698*s1**2*s2*s3*s4**2*s5**3*s6 - 850*s1**2*s2*s3*s4*s5**5 + 169560*s1**2*s2*s3*s5*s6**4
- 8*s1**2*s2*s4**8 + 3032*s1**2*s2*s4**5*s6**2 - 5144*s1**2*s2*s4**4*s5**2*s6 + 1470*s1**2*s2*s4**3*s5**4
- 249480*s1**2*s2*s4**2*s6**4 - 105390*s1**2*s2*s4*s5**2*s6**3 + 58900*s1**2*s2*s5**4*s6**2
+ 162*s1**2*s3**6*s4*s6**2 + 216*s1**2*s3**6*s5**2*s6 - 216*s1**2*s3**5*s4**2*s5*s6
- 78*s1**2*s3**5*s4*s5**3 + 36*s1**2*s3**4*s4**4*s6 + 76*s1**2*s3**4*s4**3*s5**2 -
3564*s1**2*s3**4*s4*s6**3 + 8802*s1**2*s3**4*s5**2*s6**2 - 22*s1**2*s3**3*s4**5*s5
- 11475*s1**2*s3**3*s4**2*s5*s6**2 - 2808*s1**2*s3**3*s4*s5**3*s6 + 1200*s1**2*s3**3*s5**5
+ 2*s1**2*s3**2*s4**7 + 222*s1**2*s3**2*s4**4*s6**2 + 3181*s1**2*s3**2*s4**3*s5**2*s6
- 610*s1**2*s3**2*s4**2*s5**4 - 165240*s1**2*s3**2*s4*s6**4 + 118260*s1**2*s3**2*s5**2*s6**3
+ 572*s1**2*s3*s4**5*s5*s6 - 294*s1**2*s3*s4**4*s5**3 - 32616*s1**2*s3*s4**2*s5*s6**3
- 55665*s1**2*s3*s4*s5**3*s6**2 + 17250*s1**2*s3*s5**5*s6 - 232*s1**2*s4**7*s6 + 86*s1**2*s4**6*s5**2
+ 48408*s1**2*s4**4*s6**3 + 58894*s1**2*s4**3*s5**2*s6**2 - 46650*s1**2*s4**2*s5**4*s6
+ 7500*s1**2*s4*s5**6 - 129600*s1**2*s4*s6**5 + 41040*s1**2*s5**2*s6**4 - 48*s1*s2**7*s4*s5*s6**2
+ 12*s1*s2**7*s5**3*s6 + 12*s1*s2**6*s3**2*s5*s6**2 - 144*s1*s2**6*s3*s4**2*s6**2
+ 88*s1*s2**6*s3*s4*s5**2*s6 - 13*s1*s2**6*s3*s5**4 + 1680*s1*s2**6*s5*s6**3 + 72*s1*s2**5*s3**3*s4*s6**2
- 22*s1*s2**5*s3**3*s5**2*s6 - 4*s1*s2**5*s3**2*s4**2*s5*s6 + s1*s2**5*s3**2*s4*s5**3
- 144*s1*s2**5*s3*s4*s6**3 + 572*s1*s2**5*s3*s5**2*s6**2 + 736*s1*s2**5*s4**2*s5*s6**2
+ 128*s1*s2**5*s4*s5**3*s6 - 124*s1*s2**5*s5**5 - 9*s1*s2**4*s3**5*s6**2 + s1*s2**4*s3**4*s4*s5*s6
+ 36*s1*s2**4*s3**3*s6**3 - 2028*s1*s2**4*s3**2*s4*s5*s6**2 - 547*s1*s2**4*s3**2*s5**3*s6
- 480*s1*s2**4*s3*s4**3*s6**2 + 772*s1*s2**4*s3*s4**2*s5**2*s6 - 29*s1*s2**4*s3*s4*s5**4
+ 6336*s1*s2**4*s3*s6**4 - 12*s1*s2**4*s4**3*s5**3 + 4368*s1*s2**4*s4*s5*s6**3 - 22624*s1*s2**4*s5**3*s6**2
+ 441*s1*s2**3*s3**4*s5*s6**2 + 336*s1*s2**3*s3**3*s4**2*s6**2 + 741*s1*s2**3*s3**3*s4*s5**2*s6
+ 12*s1*s2**3*s3**3*s5**4 - 868*s1*s2**3*s3**2*s4**3*s5*s6 + 93*s1*s2**3*s3**2*s4**2*s5**3
+ 11016*s1*s2**3*s3**2*s5*s6**3 + 176*s1*s2**3*s3*s4**5*s6 - 28*s1*s2**3*s3*s4**4*s5**2
+ 14784*s1*s2**3*s3*s4**2*s6**3 + 22144*s1*s2**3*s3*s4*s5**2*s6**2 + 5145*s1*s2**3*s3*s5**4*s6
- 11344*s1*s2**3*s4**3*s5*s6**2 + 5064*s1*s2**3*s4**2*s5**3*s6 - 2050*s1*s2**3*s4*s5**5
- 346896*s1*s2**3*s5*s6**4 - 54*s1*s2**2*s3**5*s4*s6**2 - 216*s1*s2**2*s3**5*s5**2*s6
+ 324*s1*s2**2*s3**4*s4**2*s5*s6 - 95*s1*s2**2*s3**4*s4*s5**3 - 80*s1*s2**2*s3**3*s4**4*s6
+ 43*s1*s2**2*s3**3*s4**3*s5**2 - 12204*s1*s2**2*s3**3*s4*s6**3 - 11475*s1*s2**2*s3**3*s5**2*s6**2
- 4*s1*s2**2*s3**2*s4**5*s5 - 3888*s1*s2**2*s3**2*s4**2*s5*s6**2 - 4844*s1*s2**2*s3**2*s4*s5**3*s6
- 725*s1*s2**2*s3**2*s5**5 - 1312*s1*s2**2*s3*s4**4*s6**2 + 1684*s1*s2**2*s3*s4**3*s5**2*s6
+ 1995*s1*s2**2*s3*s4**2*s5**4 + 139104*s1*s2**2*s3*s4*s6**4 - 32616*s1*s2**2*s3*s5**2*s6**3
+ 736*s1*s2**2*s4**5*s5*s6 - 676*s1*s2**2*s4**4*s5**3 + 131040*s1*s2**2*s4**2*s5*s6**3
+ 16240*s1*s2**2*s4*s5**3*s6**2 - 20250*s1*s2**2*s5**5*s6 - 27*s1*s2*s3**6*s4*s5*s6
+ 18*s1*s2*s3**6*s5**3 + 9*s1*s2*s3**5*s4**3*s6 - 9*s1*s2*s3**5*s4**2*s5**2 + 1944*s1*s2*s3**5*s6**3
+ s1*s2*s3**4*s4**4*s5 + 6156*s1*s2*s3**4*s4*s5*s6**2 + 1143*s1*s2*s3**4*s5**3*s6
+ 324*s1*s2*s3**3*s4**3*s6**2 + 1071*s1*s2*s3**3*s4**2*s5**2*s6 + 15*s1*s2*s3**3*s4*s5**4
- 7776*s1*s2*s3**3*s6**4 - 2028*s1*s2*s3**2*s4**4*s5*s6 - 397*s1*s2*s3**2*s4**3*s5**3
+ 112860*s1*s2*s3**2*s4*s5*s6**3 - 10305*s1*s2*s3**2*s5**3*s6**2 + 336*s1*s2*s3*s4**6*s6
+ 304*s1*s2*s3*s4**5*s5**2 - 68976*s1*s2*s3*s4**3*s6**3 - 96732*s1*s2*s3*s4**2*s5**2*s6**2
+ 36700*s1*s2*s3*s4*s5**4*s6 - 1250*s1*s2*s3*s5**6 - 1477440*s1*s2*s3*s6**5 - 48*s1*s2*s4**7*s5
+ 4368*s1*s2*s4**4*s5*s6**2 + 10360*s1*s2*s4**3*s5**3*s6 - 3500*s1*s2*s4**2*s5**5
+ 935280*s1*s2*s4*s5*s6**4 - 242100*s1*s2*s5**3*s6**3 - 972*s1*s3**6*s5*s6**2 - 351*s1*s3**5*s4*s5**2*s6
- 99*s1*s3**5*s5**4 + 441*s1*s3**4*s4**3*s5*s6 + 141*s1*s3**4*s4**2*s5**3 - 36936*s1*s3**4*s5*s6**3
- 84*s1*s3**3*s4**5*s6 - 76*s1*s3**3*s4**4*s5**2 + 17496*s1*s3**3*s4**2*s6**3 + 11718*s1*s3**3*s4*s5**2*s6**2
- 6525*s1*s3**3*s5**4*s6 + 12*s1*s3**2*s4**6*s5 + 11016*s1*s3**2*s4**3*s5*s6**2 +
5895*s1*s3**2*s4**2*s5**3*s6 - 1750*s1*s3**2*s4*s5**5 - 252720*s1*s3**2*s5*s6**4 -
2544*s1*s3*s4**5*s6**2 - 8092*s1*s3*s4**4*s5**2*s6 + 2300*s1*s3*s4**3*s5**4 + 536544*s1*s3*s4**2*s6**4
+ 169560*s1*s3*s4*s5**2*s6**3 - 103500*s1*s3*s5**4*s6**2 + 1680*s1*s4**6*s5*s6 - 468*s1*s4**5*s5**3
- 346896*s1*s4**3*s5*s6**3 + 93900*s1*s4**2*s5**3*s6**2 + 35000*s1*s4*s5**5*s6 - 9375*s1*s5**7
+ 108864*s1*s5*s6**5 + 16*s2**8*s4**2*s6**2 - 8*s2**8*s4*s5**2*s6 + s2**8*s5**4 -
8*s2**7*s3**2*s4*s6**2 + 2*s2**7*s3**2*s5**2*s6 - 96*s2**7*s4*s6**3 - 232*s2**7*s5**2*s6**2
+ s2**6*s3**4*s6**2 + 24*s2**6*s3**2*s6**3 + 336*s2**6*s3*s4*s5*s6**2 + 108*s2**6*s3*s5**3*s6
- 32*s2**6*s4**3*s6**2 - 160*s2**6*s4**2*s5**2*s6 + 38*s2**6*s4*s5**4 + 144*s2**6*s6**4
- 84*s2**5*s3**3*s5*s6**2 + 8*s2**5*s3**2*s4**2*s6**2 - 106*s2**5*s3**2*s4*s5**2*s6
- 12*s2**5*s3**2*s5**4 + 176*s2**5*s3*s4**3*s5*s6 - 36*s2**5*s3*s4**2*s5**3 - 2544*s2**5*s3*s5*s6**3
- 32*s2**5*s4**5*s6 + 8*s2**5*s4**4*s5**2 - 3072*s2**5*s4**2*s6**3 + 3032*s2**5*s4*s5**2*s6**2
+ 954*s2**5*s5**4*s6 + 36*s2**4*s3**4*s5**2*s6 - 80*s2**4*s3**3*s4**2*s5*s6 + 25*s2**4*s3**3*s4*s5**3
+ 16*s2**4*s3**2*s4**4*s6 - 6*s2**4*s3**2*s4**3*s5**2 + 2520*s2**4*s3**2*s4*s6**3
+ 222*s2**4*s3**2*s5**2*s6**2 - 1312*s2**4*s3*s4**2*s5*s6**2 - 3616*s2**4*s3*s4*s5**3*s6
- 125*s2**4*s3*s5**5 + 1296*s2**4*s4**4*s6**2 - 168*s2**4*s4**3*s5**2*s6 + 375*s2**4*s4**2*s5**4
+ 19296*s2**4*s4*s6**4 + 48408*s2**4*s5**2*s6**3 + 9*s2**3*s3**5*s4*s5*s6 - 4*s2**3*s3**5*s5**3
- 2*s2**3*s3**4*s4**3*s6 + s2**3*s3**4*s4**2*s5**2 - 432*s2**3*s3**4*s6**3 + 324*s2**3*s3**3*s4*s5*s6**2
+ 923*s2**3*s3**3*s5**3*s6 - 752*s2**3*s3**2*s4**3*s6**2 + 2262*s2**3*s3**2*s4**2*s5**2*s6
+ 525*s2**3*s3**2*s4*s5**4 - 9936*s2**3*s3**2*s6**4 - 480*s2**3*s3*s4**4*s5*s6 - 700*s2**3*s3*s4**3*s5**3
- 68976*s2**3*s3*s4*s5*s6**3 - 11360*s2**3*s3*s5**3*s6**2 - 32*s2**3*s4**6*s6 + 152*s2**3*s4**5*s5**2
+ 6912*s2**3*s4**3*s6**3 - 7992*s2**3*s4**2*s5**2*s6**2 + 5550*s2**3*s4*s5**4*s6 -
29376*s2**3*s6**5 + 108*s2**2*s3**4*s4**2*s6**2 - 1215*s2**2*s3**4*s4*s5**2*s6 - 150*s2**2*s3**4*s5**4
+ 336*s2**2*s3**3*s4**3*s5*s6 - 185*s2**2*s3**3*s4**2*s5**3 + 17496*s2**2*s3**3*s5*s6**3
+ 8*s2**2*s3**2*s4**5*s6 + 370*s2**2*s3**2*s4**4*s5**2 - 864*s2**2*s3**2*s4**2*s6**3
+ 22572*s2**2*s3**2*s4*s5**2*s6**2 + 225*s2**2*s3**2*s5**4*s6 - 144*s2**2*s3*s4**6*s5
+ 14784*s2**2*s3*s4**3*s5*s6**2 - 12020*s2**2*s3*s4**2*s5**3*s6 + 625*s2**2*s3*s4*s5**5
+ 536544*s2**2*s3*s5*s6**4 + 16*s2**2*s4**8 - 3072*s2**2*s4**5*s6**2 + 1096*s2**2*s4**4*s5**2*s6
+ 250*s2**2*s4**3*s5**4 - 93744*s2**2*s4**2*s6**4 - 249480*s2**2*s4*s5**2*s6**3 +
70125*s2**2*s5**4*s6**2 + 162*s2*s3**6*s5**2*s6 - 54*s2*s3**5*s4**2*s5*s6 + 198*s2*s3**5*s4*s5**3
- 210*s2*s3**4*s4**3*s5**2 - 3564*s2*s3**4*s5**2*s6**2 + 72*s2*s3**3*s4**5*s5 - 12204*s2*s3**3*s4**2*s5*s6**2
+ 1935*s2*s3**3*s4*s5**3*s6 - 8*s2*s3**2*s4**7 + 2520*s2*s3**2*s4**4*s6**2 + 1962*s2*s3**2*s4**3*s5**2*s6
- 125*s2*s3**2*s4**2*s5**4 - 178848*s2*s3**2*s4*s6**4 - 165240*s2*s3**2*s5**2*s6**3
- 144*s2*s3*s4**5*s5*s6 - 200*s2*s3*s4**4*s5**3 + 139104*s2*s3*s4**2*s5*s6**3 + 72900*s2*s3*s4*s5**3*s6**2
- 20625*s2*s3*s5**5*s6 - 96*s2*s4**7*s6 + 56*s2*s4**6*s5**2 + 19296*s2*s4**4*s6**3
- 22080*s2*s4**3*s5**2*s6**2 - 7750*s2*s4**2*s5**4*s6 + 3125*s2*s4*s5**6 + 248832*s2*s4*s6**5
- 129600*s2*s5**2*s6**4 - 27*s3**7*s5**3 + 27*s3**6*s4**2*s5**2 - 9*s3**5*s4**4*s5
+ 1944*s3**5*s4*s5*s6**2 + 54*s3**5*s5**3*s6 + s3**4*s4**6 - 432*s3**4*s4**3*s6**2
- 486*s3**4*s4**2*s5**2*s6 + 46656*s3**4*s6**4 + 36*s3**3*s4**4*s5*s6 + 50*s3**3*s4**3*s5**3
- 7776*s3**3*s4*s5*s6**3 + 29700*s3**3*s5**3*s6**2 + 24*s3**2*s4**6*s6 - 14*s3**2*s4**5*s5**2
- 9936*s3**2*s4**3*s6**3 - 29160*s3**2*s4**2*s5**2*s6**2 - 10125*s3**2*s4*s5**4*s6
+ 3125*s3**2*s5**6 + 1026432*s3**2*s6**5 + 6336*s3*s4**4*s5*s6**2 + 11700*s3*s4**3*s5**3*s6
- 3125*s3*s4**2*s5**5 - 1477440*s3*s4*s5*s6**4 + 432000*s3*s5**3*s6**3 + 144*s4**6*s6**2
- 2472*s4**5*s5**2*s6 + 625*s4**4*s5**4 - 29376*s4**3*s6**4 + 529200*s4**2*s5**2*s6**3
- 292500*s4*s5**4*s6**2 + 40625*s5**6*s6 - 186624*s6**6)
],
(6, 2): [
lambda s1, s2, s3, s4, s5, s6: (-s3),
lambda s1, s2, s3, s4, s5, s6: (-s1*s5 + s2*s4 - 9*s6),
lambda s1, s2, s3, s4, s5, s6: (s1*s2*s6 + 2*s1*s3*s5 - s1*s4**2 - s2**2*s5 + 6*s3*s6 + s4*s5),
lambda s1, s2, s3, s4, s5, s6: (s1**2*s4*s6 - s1**2*s5**2 - 3*s1*s2*s3*s6 + s1*s2*s4*s5 + 9*s1*s5*s6 + s2**3*s6 -
9*s2*s4*s6 + s2*s5**2 + 3*s3**2*s6 - 3*s3*s4*s5 + s4**3 + 27*s6**2),
lambda s1, s2, s3, s4, s5, s6: (-2*s1**3*s6**2 + 2*s1**2*s2*s5*s6 + 2*s1**2*s3*s4*s6 - s1**2*s3*s5**2 - s1*s2**2*s4*s6
- 3*s1*s2*s6**2 - 16*s1*s3*s5*s6 + 4*s1*s4**2*s6 + 2*s1*s4*s5**2 + 4*s2**2*s5*s6 +
s2*s3*s4*s6 + 2*s2*s3*s5**2 - s2*s4**2*s5 - 9*s3*s6**2 - 3*s4*s5*s6 - 2*s5**3),
lambda s1, s2, s3, s4, s5, s6: (s1**3*s3*s6**2 - 3*s1**3*s4*s5*s6 + s1**3*s5**3 - s1**2*s2**2*s6**2 + s1**2*s2*s3*s5*s6
- 2*s1**2*s4*s6**2 + 6*s1**2*s5**2*s6 + 16*s1*s2*s3*s6**2 - 3*s1*s2*s5**3 - s1*s3**2*s5*s6
- 2*s1*s3*s4**2*s6 + s1*s3*s4*s5**2 - 30*s1*s5*s6**2 - 4*s2**3*s6**2 - 2*s2**2*s3*s5*s6
+ s2**2*s4**2*s6 + 18*s2*s4*s6**2 - 2*s2*s5**2*s6 - 15*s3**2*s6**2 + 16*s3*s4*s5*s6
+ s3*s5**3 - 4*s4**3*s6 - s4**2*s5**2 - 27*s6**3),
lambda s1, s2, s3, s4, s5, s6: (s1**4*s5*s6**2 + 2*s1**3*s2*s4*s6**2 - s1**3*s2*s5**2*s6 - s1**3*s3**2*s6**2 + 9*s1**3*s6**3
- 14*s1**2*s2*s5*s6**2 - 11*s1**2*s3*s4*s6**2 + 6*s1**2*s3*s5**2*s6 + 3*s1**2*s4**2*s5*s6
- s1**2*s4*s5**3 + 3*s1*s2**2*s5**2*s6 + 3*s1*s2*s3**2*s6**2 - s1*s2*s3*s4*s5*s6 +
39*s1*s3*s5*s6**2 - 14*s1*s4*s5**2*s6 + s1*s5**4 - 11*s2*s3*s5**2*s6 + 2*s2*s4*s5**3
- 3*s3**3*s6**2 + 3*s3**2*s4*s5*s6 - s3**2*s5**3 + 9*s5**3*s6),
lambda s1, s2, s3, s4, s5, s6: (-s1**4*s2*s6**3 + s1**4*s3*s5*s6**2 - 4*s1**3*s3*s6**3 + 10*s1**3*s4*s5*s6**2 - 4*s1**3*s5**3*s6
+ 8*s1**2*s2**2*s6**3 - 8*s1**2*s2*s3*s5*s6**2 - 2*s1**2*s2*s4**2*s6**2 + s1**2*s2*s4*s5**2*s6
+ s1**2*s3**2*s4*s6**2 - 6*s1**2*s4*s6**3 - 7*s1**2*s5**2*s6**2 - 24*s1*s2*s3*s6**3
- 4*s1*s2*s4*s5*s6**2 + 10*s1*s2*s5**3*s6 + 8*s1*s3**2*s5*s6**2 + 8*s1*s3*s4**2*s6**2
- 8*s1*s3*s4*s5**2*s6 + s1*s3*s5**4 + 36*s1*s5*s6**3 + 8*s2**2*s3*s5*s6**2 - 2*s2**2*s4*s5**2*s6
- 2*s2*s3**2*s4*s6**2 + s2*s3**2*s5**2*s6 - 6*s2*s5**2*s6**2 + 18*s3**2*s6**3 - 24*s3*s4*s5*s6**2
- 4*s3*s5**3*s6 + 8*s4**2*s5**2*s6 - s4*s5**4),
lambda s1, s2, s3, s4, s5, s6: (-s1**5*s4*s6**3 - 2*s1**4*s5*s6**3 + 3*s1**3*s2*s5**2*s6**2 + 3*s1**3*s3**2*s6**3
- s1**3*s3*s4*s5*s6**2 - 8*s1**3*s6**4 + 16*s1**2*s2*s5*s6**3 + 8*s1**2*s3*s4*s6**3
- 6*s1**2*s3*s5**2*s6**2 - 8*s1**2*s4**2*s5*s6**2 + 3*s1**2*s4*s5**3*s6 - 8*s1*s2**2*s5**2*s6**2
- 8*s1*s2*s3**2*s6**3 + 8*s1*s2*s3*s4*s5*s6**2 - s1*s2*s3*s5**3*s6 - s1*s3**3*s5*s6**2
- 24*s1*s3*s5*s6**3 + 16*s1*s4*s5**2*s6**2 - 2*s1*s5**4*s6 + 8*s2*s3*s5**2*s6**2 -
s2*s5**5 + 8*s3**3*s6**3 - 8*s3**2*s4*s5*s6**2 + 3*s3**2*s5**3*s6 - 8*s5**3*s6**2),
lambda s1, s2, s3, s4, s5, s6: (s1**6*s6**4 - 4*s1**4*s2*s6**4 - 2*s1**4*s3*s5*s6**3 + s1**4*s4**2*s6**3 + 8*s1**3*s3*s6**4
- 4*s1**3*s4*s5*s6**3 + 2*s1**3*s5**3*s6**2 + 8*s1**2*s2*s3*s5*s6**3 - 2*s1**2*s2*s4*s5**2*s6**2
- 2*s1**2*s3**2*s4*s6**3 + s1**2*s3**2*s5**2*s6**2 - 4*s1*s2*s5**3*s6**2 - 12*s1*s3**2*s5*s6**3
+ 8*s1*s3*s4*s5**2*s6**2 - 2*s1*s3*s5**4*s6 + s2**2*s5**4*s6 - 2*s2*s3**2*s5**2*s6**2
+ s3**4*s6**3 + 8*s3*s5**3*s6**2 - 4*s4*s5**4*s6 + s5**6)
],
}

View File

@ -0,0 +1,507 @@
r"""
Functions in ``polys.numberfields.subfield`` solve the "Subfield Problem" and
allied problems, for algebraic number fields.
Following Cohen (see [Cohen93]_ Section 4.5), we can define the main problem as
follows:
* **Subfield Problem:**
Given two number fields $\mathbb{Q}(\alpha)$, $\mathbb{Q}(\beta)$
via the minimal polynomials for their generators $\alpha$ and $\beta$, decide
whether one field is isomorphic to a subfield of the other.
From a solution to this problem flow solutions to the following problems as
well:
* **Primitive Element Problem:**
Given several algebraic numbers
$\alpha_1, \ldots, \alpha_m$, compute a single algebraic number $\theta$
such that $\mathbb{Q}(\alpha_1, \ldots, \alpha_m) = \mathbb{Q}(\theta)$.
* **Field Isomorphism Problem:**
Decide whether two number fields
$\mathbb{Q}(\alpha)$, $\mathbb{Q}(\beta)$ are isomorphic.
* **Field Membership Problem:**
Given two algebraic numbers $\alpha$,
$\beta$, decide whether $\alpha \in \mathbb{Q}(\beta)$, and if so write
$\alpha = f(\beta)$ for some $f(x) \in \mathbb{Q}[x]$.
"""
from sympy.core.add import Add
from sympy.core.numbers import AlgebraicNumber
from sympy.core.singleton import S
from sympy.core.symbol import Dummy
from sympy.core.sympify import sympify, _sympify
from sympy.ntheory import sieve
from sympy.polys.densetools import dup_eval
from sympy.polys.domains import QQ
from sympy.polys.numberfields.minpoly import _choose_factor, minimal_polynomial
from sympy.polys.polyerrors import IsomorphismFailed
from sympy.polys.polytools import Poly, PurePoly, factor_list
from sympy.utilities import public
from mpmath import MPContext
def is_isomorphism_possible(a, b):
"""Necessary but not sufficient test for isomorphism. """
n = a.minpoly.degree()
m = b.minpoly.degree()
if m % n != 0:
return False
if n == m:
return True
da = a.minpoly.discriminant()
db = b.minpoly.discriminant()
i, k, half = 1, m//n, db//2
while True:
p = sieve[i]
P = p**k
if P > half:
break
if ((da % p) % 2) and not (db % P):
return False
i += 1
return True
def field_isomorphism_pslq(a, b):
"""Construct field isomorphism using PSLQ algorithm. """
if not a.root.is_real or not b.root.is_real:
raise NotImplementedError("PSLQ doesn't support complex coefficients")
f = a.minpoly
g = b.minpoly.replace(f.gen)
n, m, prev = 100, b.minpoly.degree(), None
ctx = MPContext()
for i in range(1, 5):
A = a.root.evalf(n)
B = b.root.evalf(n)
basis = [1, B] + [ B**i for i in range(2, m) ] + [-A]
ctx.dps = n
coeffs = ctx.pslq(basis, maxcoeff=10**10, maxsteps=1000)
if coeffs is None:
# PSLQ can't find an integer linear combination. Give up.
break
if coeffs != prev:
prev = coeffs
else:
# Increasing precision didn't produce anything new. Give up.
break
# We have
# c0 + c1*B + c2*B^2 + ... + cm-1*B^(m-1) - cm*A ~ 0.
# So bring cm*A to the other side, and divide through by cm,
# for an approximate representation of A as a polynomial in B.
# (We know cm != 0 since `b.minpoly` is irreducible.)
coeffs = [S(c)/coeffs[-1] for c in coeffs[:-1]]
# Throw away leading zeros.
while not coeffs[-1]:
coeffs.pop()
coeffs = list(reversed(coeffs))
h = Poly(coeffs, f.gen, domain='QQ')
# We only have A ~ h(B). We must check whether the relation is exact.
if f.compose(h).rem(g).is_zero:
# Now we know that h(b) is in fact equal to _some conjugate of_ a.
# But from the very precise approximation A ~ h(B) we can assume
# the conjugate is a itself.
return coeffs
else:
n *= 2
return None
def field_isomorphism_factor(a, b):
"""Construct field isomorphism via factorization. """
_, factors = factor_list(a.minpoly, extension=b)
for f, _ in factors:
if f.degree() == 1:
# Any linear factor f(x) represents some conjugate of a in QQ(b).
# We want to know whether this linear factor represents a itself.
# Let f = x - c
c = -f.rep.TC()
# Write c as polynomial in b
coeffs = c.to_sympy_list()
d, terms = len(coeffs) - 1, []
for i, coeff in enumerate(coeffs):
terms.append(coeff*b.root**(d - i))
r = Add(*terms)
# Check whether we got the number a
if a.minpoly.same_root(r, a):
return coeffs
# If none of the linear factors represented a in QQ(b), then in fact a is
# not an element of QQ(b).
return None
@public
def field_isomorphism(a, b, *, fast=True):
r"""
Find an embedding of one number field into another.
Explanation
===========
This function looks for an isomorphism from $\mathbb{Q}(a)$ onto some
subfield of $\mathbb{Q}(b)$. Thus, it solves the Subfield Problem.
Examples
========
>>> from sympy import sqrt, field_isomorphism, I
>>> print(field_isomorphism(3, sqrt(2))) # doctest: +SKIP
[3]
>>> print(field_isomorphism( I*sqrt(3), I*sqrt(3)/2)) # doctest: +SKIP
[2, 0]
Parameters
==========
a : :py:class:`~.Expr`
Any expression representing an algebraic number.
b : :py:class:`~.Expr`
Any expression representing an algebraic number.
fast : boolean, optional (default=True)
If ``True``, we first attempt a potentially faster way of computing the
isomorphism, falling back on a slower method if this fails. If
``False``, we go directly to the slower method, which is guaranteed to
return a result.
Returns
=======
List of rational numbers, or None
If $\mathbb{Q}(a)$ is not isomorphic to some subfield of
$\mathbb{Q}(b)$, then return ``None``. Otherwise, return a list of
rational numbers representing an element of $\mathbb{Q}(b)$ to which
$a$ may be mapped, in order to define a monomorphism, i.e. an
isomorphism from $\mathbb{Q}(a)$ to some subfield of $\mathbb{Q}(b)$.
The elements of the list are the coefficients of falling powers of $b$.
"""
a, b = sympify(a), sympify(b)
if not a.is_AlgebraicNumber:
a = AlgebraicNumber(a)
if not b.is_AlgebraicNumber:
b = AlgebraicNumber(b)
a = a.to_primitive_element()
b = b.to_primitive_element()
if a == b:
return a.coeffs()
n = a.minpoly.degree()
m = b.minpoly.degree()
if n == 1:
return [a.root]
if m % n != 0:
return None
if fast:
try:
result = field_isomorphism_pslq(a, b)
if result is not None:
return result
except NotImplementedError:
pass
return field_isomorphism_factor(a, b)
def _switch_domain(g, K):
# An algebraic relation f(a, b) = 0 over Q can also be written
# g(b) = 0 where g is in Q(a)[x] and h(a) = 0 where h is in Q(b)[x].
# This function transforms g into h where Q(b) = K.
frep = g.rep.inject()
hrep = frep.eject(K, front=True)
return g.new(hrep, g.gens[0])
def _linsolve(p):
# Compute root of linear polynomial.
c, d = p.rep.to_list()
return -d/c
@public
def primitive_element(extension, x=None, *, ex=False, polys=False):
r"""
Find a single generator for a number field given by several generators.
Explanation
===========
The basic problem is this: Given several algebraic numbers
$\alpha_1, \alpha_2, \ldots, \alpha_n$, find a single algebraic number
$\theta$ such that
$\mathbb{Q}(\alpha_1, \alpha_2, \ldots, \alpha_n) = \mathbb{Q}(\theta)$.
This function actually guarantees that $\theta$ will be a linear
combination of the $\alpha_i$, with non-negative integer coefficients.
Furthermore, if desired, this function will tell you how to express each
$\alpha_i$ as a $\mathbb{Q}$-linear combination of the powers of $\theta$.
Examples
========
>>> from sympy import primitive_element, sqrt, S, minpoly, simplify
>>> from sympy.abc import x
>>> f, lincomb, reps = primitive_element([sqrt(2), sqrt(3)], x, ex=True)
Then ``lincomb`` tells us the primitive element as a linear combination of
the given generators ``sqrt(2)`` and ``sqrt(3)``.
>>> print(lincomb)
[1, 1]
This means the primtiive element is $\sqrt{2} + \sqrt{3}$.
Meanwhile ``f`` is the minimal polynomial for this primitive element.
>>> print(f)
x**4 - 10*x**2 + 1
>>> print(minpoly(sqrt(2) + sqrt(3), x))
x**4 - 10*x**2 + 1
Finally, ``reps`` (which was returned only because we set keyword arg
``ex=True``) tells us how to recover each of the generators $\sqrt{2}$ and
$\sqrt{3}$ as $\mathbb{Q}$-linear combinations of the powers of the
primitive element $\sqrt{2} + \sqrt{3}$.
>>> print([S(r) for r in reps[0]])
[1/2, 0, -9/2, 0]
>>> theta = sqrt(2) + sqrt(3)
>>> print(simplify(theta**3/2 - 9*theta/2))
sqrt(2)
>>> print([S(r) for r in reps[1]])
[-1/2, 0, 11/2, 0]
>>> print(simplify(-theta**3/2 + 11*theta/2))
sqrt(3)
Parameters
==========
extension : list of :py:class:`~.Expr`
Each expression must represent an algebraic number $\alpha_i$.
x : :py:class:`~.Symbol`, optional (default=None)
The desired symbol to appear in the computed minimal polynomial for the
primitive element $\theta$. If ``None``, we use a dummy symbol.
ex : boolean, optional (default=False)
If and only if ``True``, compute the representation of each $\alpha_i$
as a $\mathbb{Q}$-linear combination over the powers of $\theta$.
polys : boolean, optional (default=False)
If ``True``, return the minimal polynomial as a :py:class:`~.Poly`.
Otherwise return it as an :py:class:`~.Expr`.
Returns
=======
Pair (f, coeffs) or triple (f, coeffs, reps), where:
``f`` is the minimal polynomial for the primitive element.
``coeffs`` gives the primitive element as a linear combination of the
given generators.
``reps`` is present if and only if argument ``ex=True`` was passed,
and is a list of lists of rational numbers. Each list gives the
coefficients of falling powers of the primitive element, to recover
one of the original, given generators.
"""
if not extension:
raise ValueError("Cannot compute primitive element for empty extension")
extension = [_sympify(ext) for ext in extension]
if x is not None:
x, cls = sympify(x), Poly
else:
x, cls = Dummy('x'), PurePoly
if not ex:
gen, coeffs = extension[0], [1]
g = minimal_polynomial(gen, x, polys=True)
for ext in extension[1:]:
if ext.is_Rational:
coeffs.append(0)
continue
_, factors = factor_list(g, extension=ext)
g = _choose_factor(factors, x, gen)
[s], _, g = g.sqf_norm()
gen += s*ext
coeffs.append(s)
if not polys:
return g.as_expr(), coeffs
else:
return cls(g), coeffs
gen, coeffs = extension[0], [1]
f = minimal_polynomial(gen, x, polys=True)
K = QQ.algebraic_field((f, gen)) # incrementally constructed field
reps = [K.unit] # representations of extension elements in K
for ext in extension[1:]:
if ext.is_Rational:
coeffs.append(0) # rational ext is not included in the expression of a primitive element
reps.append(K.convert(ext)) # but it is included in reps
continue
p = minimal_polynomial(ext, x, polys=True)
L = QQ.algebraic_field((p, ext))
_, factors = factor_list(f, domain=L)
f = _choose_factor(factors, x, gen)
[s], g, f = f.sqf_norm()
gen += s*ext
coeffs.append(s)
K = QQ.algebraic_field((f, gen))
h = _switch_domain(g, K)
erep = _linsolve(h.gcd(p)) # ext as element of K
ogen = K.unit - s*erep # old gen as element of K
reps = [dup_eval(_.to_list(), ogen, K) for _ in reps] + [erep]
if K.ext.root.is_Rational: # all extensions are rational
H = [K.convert(_).rep for _ in extension]
coeffs = [0]*len(extension)
f = cls(x, domain=QQ)
else:
H = [_.to_list() for _ in reps]
if not polys:
return f.as_expr(), coeffs, H
else:
return f, coeffs, H
@public
def to_number_field(extension, theta=None, *, gen=None, alias=None):
r"""
Express one algebraic number in the field generated by another.
Explanation
===========
Given two algebraic numbers $\eta, \theta$, this function either expresses
$\eta$ as an element of $\mathbb{Q}(\theta)$, or else raises an exception
if $\eta \not\in \mathbb{Q}(\theta)$.
This function is essentially just a convenience, utilizing
:py:func:`~.field_isomorphism` (our solution of the Subfield Problem) to
solve this, the Field Membership Problem.
As an additional convenience, this function allows you to pass a list of
algebraic numbers $\alpha_1, \alpha_2, \ldots, \alpha_n$ instead of $\eta$.
It then computes $\eta$ for you, as a solution of the Primitive Element
Problem, using :py:func:`~.primitive_element` on the list of $\alpha_i$.
Examples
========
>>> from sympy import sqrt, to_number_field
>>> eta = sqrt(2)
>>> theta = sqrt(2) + sqrt(3)
>>> a = to_number_field(eta, theta)
>>> print(type(a))
<class 'sympy.core.numbers.AlgebraicNumber'>
>>> a.root
sqrt(2) + sqrt(3)
>>> print(a)
sqrt(2)
>>> a.coeffs()
[1/2, 0, -9/2, 0]
We get an :py:class:`~.AlgebraicNumber`, whose ``.root`` is $\theta$, whose
value is $\eta$, and whose ``.coeffs()`` show how to write $\eta$ as a
$\mathbb{Q}$-linear combination in falling powers of $\theta$.
Parameters
==========
extension : :py:class:`~.Expr` or list of :py:class:`~.Expr`
Either the algebraic number that is to be expressed in the other field,
or else a list of algebraic numbers, a primitive element for which is
to be expressed in the other field.
theta : :py:class:`~.Expr`, None, optional (default=None)
If an :py:class:`~.Expr` representing an algebraic number, behavior is
as described under **Explanation**. If ``None``, then this function
reduces to a shorthand for calling :py:func:`~.primitive_element` on
``extension`` and turning the computed primitive element into an
:py:class:`~.AlgebraicNumber`.
gen : :py:class:`~.Symbol`, None, optional (default=None)
If provided, this will be used as the generator symbol for the minimal
polynomial in the returned :py:class:`~.AlgebraicNumber`.
alias : str, :py:class:`~.Symbol`, None, optional (default=None)
If provided, this will be used as the alias symbol for the returned
:py:class:`~.AlgebraicNumber`.
Returns
=======
AlgebraicNumber
Belonging to $\mathbb{Q}(\theta)$ and equaling $\eta$.
Raises
======
IsomorphismFailed
If $\eta \not\in \mathbb{Q}(\theta)$.
See Also
========
field_isomorphism
primitive_element
"""
if hasattr(extension, '__iter__'):
extension = list(extension)
else:
extension = [extension]
if len(extension) == 1 and isinstance(extension[0], tuple):
return AlgebraicNumber(extension[0], alias=alias)
minpoly, coeffs = primitive_element(extension, gen, polys=True)
root = sum(coeff*ext for coeff, ext in zip(coeffs, extension))
if theta is None:
return AlgebraicNumber((minpoly, root), alias=alias)
else:
theta = sympify(theta)
if not theta.is_AlgebraicNumber:
theta = AlgebraicNumber(theta, gen=gen, alias=alias)
coeffs = field_isomorphism(root, theta)
if coeffs is not None:
return AlgebraicNumber(theta, coeffs, alias=alias)
else:
raise IsomorphismFailed(
"%s is not in a subfield of %s" % (root, theta.root))

View File

@ -0,0 +1,85 @@
from sympy.abc import x
from sympy.core import S
from sympy.core.numbers import AlgebraicNumber
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.polys import Poly, cyclotomic_poly
from sympy.polys.domains import QQ
from sympy.polys.matrices import DomainMatrix, DM
from sympy.polys.numberfields.basis import round_two
from sympy.testing.pytest import raises
def test_round_two():
# Poly must be irreducible, and over ZZ or QQ:
raises(ValueError, lambda: round_two(Poly(x ** 2 - 1)))
raises(ValueError, lambda: round_two(Poly(x ** 2 + sqrt(2))))
# Test on many fields:
cases = (
# A couple of cyclotomic fields:
(cyclotomic_poly(5), DomainMatrix.eye(4, QQ), 125),
(cyclotomic_poly(7), DomainMatrix.eye(6, QQ), -16807),
# A couple of quadratic fields (one 1 mod 4, one 3 mod 4):
(x ** 2 - 5, DM([[1, (1, 2)], [0, (1, 2)]], QQ), 5),
(x ** 2 - 7, DM([[1, 0], [0, 1]], QQ), 28),
# Dedekind's example of a field with 2 as essential disc divisor:
(x ** 3 + x ** 2 - 2 * x + 8, DM([[1, 0, 0], [0, 1, 0], [0, (1, 2), (1, 2)]], QQ).transpose(), -503),
# A bunch of cubics with various forms for F -- all of these require
# second or third enlargements. (Five of them require a third, while the rest require just a second.)
# F = 2^2
(x**3 + 3 * x**2 - 4 * x + 4, DM([((1, 2), (1, 4), (1, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), -83),
# F = 2^2 * 3
(x**3 + 3 * x**2 + 3 * x - 3, DM([((1, 2), 0, (1, 2)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -108),
# F = 2^3
(x**3 + 5 * x**2 - x + 3, DM([((1, 4), 0, (3, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), -31),
# F = 2^2 * 5
(x**3 + 5 * x**2 - 5 * x - 5, DM([((1, 2), 0, (1, 2)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), 1300),
# F = 3^2
(x**3 + 3 * x**2 + 5, DM([((1, 3), (1, 3), (1, 3)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -135),
# F = 3^3
(x**3 + 6 * x**2 + 3 * x - 1, DM([((1, 3), (1, 3), (1, 3)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), 81),
# F = 2^2 * 3^2
(x**3 + 6 * x**2 + 4, DM([((1, 3), (2, 3), (1, 3)), (0, 1, 0), (0, 0, (1, 2))], QQ).transpose(), -108),
# F = 2^3 * 7
(x**3 + 7 * x**2 + 7 * x - 7, DM([((1, 4), 0, (3, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), 49),
# F = 2^2 * 13
(x**3 + 7 * x**2 - x + 5, DM([((1, 2), 0, (1, 2)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -2028),
# F = 2^4
(x**3 + 7 * x**2 - 5 * x + 5, DM([((1, 4), 0, (3, 4)), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), -140),
# F = 5^2
(x**3 + 4 * x**2 - 3 * x + 7, DM([((1, 5), (4, 5), (4, 5)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -175),
# F = 7^2
(x**3 + 8 * x**2 + 5 * x - 1, DM([((1, 7), (6, 7), (2, 7)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), 49),
# F = 2 * 5 * 7
(x**3 + 8 * x**2 - 2 * x + 6, DM([(1, 0, 0), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -14700),
# F = 2^2 * 3 * 5
(x**3 + 6 * x**2 - 3 * x + 8, DM([(1, 0, 0), (0, (1, 4), (1, 4)), (0, 0, 1)], QQ).transpose(), -675),
# F = 2 * 3^2 * 7
(x**3 + 9 * x**2 + 6 * x - 8, DM([(1, 0, 0), (0, (1, 2), (1, 2)), (0, 0, 1)], QQ).transpose(), 3969),
# F = 2^2 * 3^2 * 7
(x**3 + 15 * x**2 - 9 * x + 13, DM([((1, 6), (1, 3), (1, 6)), (0, 1, 0), (0, 0, 1)], QQ).transpose(), -5292),
# Polynomial need not be monic
(5*x**3 + 5*x**2 - 10 * x + 40, DM([[1, 0, 0], [0, 1, 0], [0, (1, 2), (1, 2)]], QQ).transpose(), -503),
# Polynomial can have non-integer rational coeffs
(QQ(5, 3)*x**3 + QQ(5, 3)*x**2 - QQ(10, 3)*x + QQ(40, 3), DM([[1, 0, 0], [0, 1, 0], [0, (1, 2), (1, 2)]], QQ).transpose(), -503),
)
for f, B_exp, d_exp in cases:
K = QQ.alg_field_from_poly(f)
B = K.maximal_order().QQ_matrix
d = K.discriminant()
assert d == d_exp
# The computed basis need not equal the expected one, but their quotient
# must be unimodular:
assert (B.inv()*B_exp).det()**2 == 1
def test_AlgebraicField_integral_basis():
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')
assert B0 == [k([1]), k([S.Half, S.Half])]
assert B1 == [1, S.Half + alpha/2]
assert B2 == [k.ext.field_element([1]),
k.ext.field_element([S.Half, S.Half])]

View File

@ -0,0 +1,143 @@
"""Tests for computing Galois groups. """
from sympy.abc import x
from sympy.combinatorics.galois import (
S1TransitiveSubgroups, S2TransitiveSubgroups, S3TransitiveSubgroups,
S4TransitiveSubgroups, S5TransitiveSubgroups, S6TransitiveSubgroups,
)
from sympy.polys.domains.rationalfield import QQ
from sympy.polys.numberfields.galoisgroups import (
tschirnhausen_transformation,
galois_group,
_galois_group_degree_4_root_approx,
_galois_group_degree_5_hybrid,
)
from sympy.polys.numberfields.subfield import field_isomorphism
from sympy.polys.polytools import Poly
from sympy.testing.pytest import raises
def test_tschirnhausen_transformation():
for T in [
Poly(x**2 - 2),
Poly(x**2 + x + 1),
Poly(x**4 + 1),
Poly(x**4 - x**3 + x**2 - x + 1),
]:
_, U = tschirnhausen_transformation(T)
assert U.degree() == T.degree()
assert U.is_monic
assert U.is_irreducible
K = QQ.alg_field_from_poly(T)
L = QQ.alg_field_from_poly(U)
assert field_isomorphism(K.ext, L.ext) is not None
# Test polys are from:
# Cohen, H. *A Course in Computational Algebraic Number Theory*.
test_polys_by_deg = {
# Degree 1
1: [
(x, S1TransitiveSubgroups.S1, True)
],
# Degree 2
2: [
(x**2 + x + 1, S2TransitiveSubgroups.S2, False)
],
# Degree 3
3: [
(x**3 + x**2 - 2*x - 1, S3TransitiveSubgroups.A3, True),
(x**3 + 2, S3TransitiveSubgroups.S3, False),
],
# Degree 4
4: [
(x**4 + x**3 + x**2 + x + 1, S4TransitiveSubgroups.C4, False),
(x**4 + 1, S4TransitiveSubgroups.V, True),
(x**4 - 2, S4TransitiveSubgroups.D4, False),
(x**4 + 8*x + 12, S4TransitiveSubgroups.A4, True),
(x**4 + x + 1, S4TransitiveSubgroups.S4, False),
],
# Degree 5
5: [
(x**5 + x**4 - 4*x**3 - 3*x**2 + 3*x + 1, S5TransitiveSubgroups.C5, True),
(x**5 - 5*x + 12, S5TransitiveSubgroups.D5, True),
(x**5 + 2, S5TransitiveSubgroups.M20, False),
(x**5 + 20*x + 16, S5TransitiveSubgroups.A5, True),
(x**5 - x + 1, S5TransitiveSubgroups.S5, False),
],
# Degree 6
6: [
(x**6 + x**5 + x**4 + x**3 + x**2 + x + 1, S6TransitiveSubgroups.C6, False),
(x**6 + 108, S6TransitiveSubgroups.S3, False),
(x**6 + 2, S6TransitiveSubgroups.D6, False),
(x**6 - 3*x**2 - 1, S6TransitiveSubgroups.A4, True),
(x**6 + 3*x**3 + 3, S6TransitiveSubgroups.G18, False),
(x**6 - 3*x**2 + 1, S6TransitiveSubgroups.A4xC2, False),
(x**6 - 4*x**2 - 1, S6TransitiveSubgroups.S4p, True),
(x**6 - 3*x**5 + 6*x**4 - 7*x**3 + 2*x**2 + x - 4, S6TransitiveSubgroups.S4m, False),
(x**6 + 2*x**3 - 2, S6TransitiveSubgroups.G36m, False),
(x**6 + 2*x**2 + 2, S6TransitiveSubgroups.S4xC2, False),
(x**6 + 10*x**5 + 55*x**4 + 140*x**3 + 175*x**2 + 170*x + 25, S6TransitiveSubgroups.PSL2F5, True),
(x**6 + 10*x**5 + 55*x**4 + 140*x**3 + 175*x**2 - 3019*x + 25, S6TransitiveSubgroups.PGL2F5, False),
(x**6 + 6*x**4 + 2*x**3 + 9*x**2 + 6*x - 4, S6TransitiveSubgroups.G36p, True),
(x**6 + 2*x**4 + 2*x**3 + x**2 + 2*x + 2, S6TransitiveSubgroups.G72, False),
(x**6 + 24*x - 20, S6TransitiveSubgroups.A6, True),
(x**6 + x + 1, S6TransitiveSubgroups.S6, False),
],
}
def test_galois_group():
"""
Try all the test polys.
"""
for deg in range(1, 7):
polys = test_polys_by_deg[deg]
for T, G, alt in polys:
assert galois_group(T, by_name=True) == (G, alt)
def test_galois_group_degree_out_of_bounds():
raises(ValueError, lambda: galois_group(Poly(0, x)))
raises(ValueError, lambda: galois_group(Poly(1, x)))
raises(ValueError, lambda: galois_group(Poly(x ** 7 + 1)))
def test_galois_group_not_by_name():
"""
Check at least one polynomial of each supported degree, to see that
conversion from name to group works.
"""
for deg in range(1, 7):
T, G_name, _ = test_polys_by_deg[deg][0]
G, _ = galois_group(T)
assert G == G_name.get_perm_group()
def test_galois_group_not_monic_over_ZZ():
"""
Check that we can work with polys that are not monic over ZZ.
"""
for deg in range(1, 7):
T, G, alt = test_polys_by_deg[deg][0]
assert galois_group(T/2, by_name=True) == (G, alt)
def test__galois_group_degree_4_root_approx():
for T, G, alt in test_polys_by_deg[4]:
assert _galois_group_degree_4_root_approx(Poly(T)) == (G, alt)
def test__galois_group_degree_5_hybrid():
for T, G, alt in test_polys_by_deg[5]:
assert _galois_group_degree_5_hybrid(Poly(T)) == (G, alt)
def test_AlgebraicField_galois_group():
k = QQ.alg_field_from_poly(Poly(x**4 + 1))
G, _ = k.galois_group(by_name=True)
assert G == S4TransitiveSubgroups.V
k = QQ.alg_field_from_poly(Poly(x**4 - 2))
G, _ = k.galois_group(by_name=True)
assert G == S4TransitiveSubgroups.D4

View File

@ -0,0 +1,474 @@
"""Tests for minimal polynomials. """
from sympy.core.function import expand
from sympy.core import (GoldenRatio, TribonacciConstant)
from sympy.core.numbers import (AlgebraicNumber, I, Rational, oo, pi)
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import (cbrt, sqrt)
from sympy.functions.elementary.trigonometric import (cos, sin, tan)
from sympy.polys.polytools import Poly
from sympy.polys.rootoftools import CRootOf
from sympy.solvers.solveset import nonlinsolve
from sympy.geometry import Circle, intersection
from sympy.testing.pytest import raises, slow
from sympy.sets.sets import FiniteSet
from sympy.geometry.point import Point2D
from sympy.polys.numberfields.minpoly import (
minimal_polynomial,
_choose_factor,
_minpoly_op_algebraic_element,
_separate_sq,
_minpoly_groebner,
)
from sympy.polys.partfrac import apart
from sympy.polys.polyerrors import (
NotAlgebraic,
GeneratorsError,
)
from sympy.polys.domains import QQ
from sympy.polys.rootoftools import rootof
from sympy.polys.polytools import degree
from sympy.abc import x, y, z
Q = Rational
def test_minimal_polynomial():
assert minimal_polynomial(-7, x) == x + 7
assert minimal_polynomial(-1, x) == x + 1
assert minimal_polynomial( 0, x) == x
assert minimal_polynomial( 1, x) == x - 1
assert minimal_polynomial( 7, x) == x - 7
assert minimal_polynomial(sqrt(2), x) == x**2 - 2
assert minimal_polynomial(sqrt(5), x) == x**2 - 5
assert minimal_polynomial(sqrt(6), x) == x**2 - 6
assert minimal_polynomial(2*sqrt(2), x) == x**2 - 8
assert minimal_polynomial(3*sqrt(5), x) == x**2 - 45
assert minimal_polynomial(4*sqrt(6), x) == x**2 - 96
assert minimal_polynomial(2*sqrt(2) + 3, x) == x**2 - 6*x + 1
assert minimal_polynomial(3*sqrt(5) + 6, x) == x**2 - 12*x - 9
assert minimal_polynomial(4*sqrt(6) + 7, x) == x**2 - 14*x - 47
assert minimal_polynomial(2*sqrt(2) - 3, x) == x**2 + 6*x + 1
assert minimal_polynomial(3*sqrt(5) - 6, x) == x**2 + 12*x - 9
assert minimal_polynomial(4*sqrt(6) - 7, x) == x**2 + 14*x - 47
assert minimal_polynomial(sqrt(1 + sqrt(6)), x) == x**4 - 2*x**2 - 5
assert minimal_polynomial(sqrt(I + sqrt(6)), x) == x**8 - 10*x**4 + 49
assert minimal_polynomial(2*I + sqrt(2 + I), x) == x**4 + 4*x**2 + 8*x + 37
assert minimal_polynomial(sqrt(2) + sqrt(3), x) == x**4 - 10*x**2 + 1
assert minimal_polynomial(
sqrt(2) + sqrt(3) + sqrt(6), x) == x**4 - 22*x**2 - 48*x - 23
a = 1 - 9*sqrt(2) + 7*sqrt(3)
assert minimal_polynomial(
1/a, x) == 392*x**4 - 1232*x**3 + 612*x**2 + 4*x - 1
assert minimal_polynomial(
1/sqrt(a), x) == 392*x**8 - 1232*x**6 + 612*x**4 + 4*x**2 - 1
raises(NotAlgebraic, lambda: minimal_polynomial(oo, x))
raises(NotAlgebraic, lambda: minimal_polynomial(2**y, x))
raises(NotAlgebraic, lambda: minimal_polynomial(sin(1), x))
assert minimal_polynomial(sqrt(2)).dummy_eq(x**2 - 2)
assert minimal_polynomial(sqrt(2), x) == x**2 - 2
assert minimal_polynomial(sqrt(2), polys=True) == Poly(x**2 - 2)
assert minimal_polynomial(sqrt(2), x, polys=True) == Poly(x**2 - 2, domain='QQ')
assert minimal_polynomial(sqrt(2), x, polys=True, compose=False) == Poly(x**2 - 2, domain='QQ')
a = AlgebraicNumber(sqrt(2))
b = AlgebraicNumber(sqrt(3))
assert minimal_polynomial(a, x) == x**2 - 2
assert minimal_polynomial(b, x) == x**2 - 3
assert minimal_polynomial(a, x, polys=True) == Poly(x**2 - 2, domain='QQ')
assert minimal_polynomial(b, x, polys=True) == Poly(x**2 - 3, domain='QQ')
assert minimal_polynomial(sqrt(a/2 + 17), x) == 2*x**4 - 68*x**2 + 577
assert minimal_polynomial(sqrt(b/2 + 17), x) == 4*x**4 - 136*x**2 + 1153
a, b = sqrt(2)/3 + 7, AlgebraicNumber(sqrt(2)/3 + 7)
f = 81*x**8 - 2268*x**6 - 4536*x**5 + 22644*x**4 + 63216*x**3 - \
31608*x**2 - 189648*x + 141358
assert minimal_polynomial(sqrt(a) + sqrt(sqrt(a)), x) == f
assert minimal_polynomial(sqrt(b) + sqrt(sqrt(b)), x) == f
assert minimal_polynomial(
a**Q(3, 2), x) == 729*x**4 - 506898*x**2 + 84604519
# issue 5994
eq = S('''
-1/(800*sqrt(-1/240 + 1/(18000*(-1/17280000 +
sqrt(15)*I/28800000)**(1/3)) + 2*(-1/17280000 +
sqrt(15)*I/28800000)**(1/3)))''')
assert minimal_polynomial(eq, x) == 8000*x**2 - 1
ex = (sqrt(5)*sqrt(I)/(5*sqrt(1 + 125*I))
+ 25*sqrt(5)/(I**Q(5,2)*(1 + 125*I)**Q(3,2))
+ 3125*sqrt(5)/(I**Q(11,2)*(1 + 125*I)**Q(3,2))
+ 5*I*sqrt(1 - I/125))
mp = minimal_polynomial(ex, x)
assert mp == 25*x**4 + 5000*x**2 + 250016
ex = 1 + sqrt(2) + sqrt(3)
mp = minimal_polynomial(ex, x)
assert mp == x**4 - 4*x**3 - 4*x**2 + 16*x - 8
ex = 1/(1 + sqrt(2) + sqrt(3))
mp = minimal_polynomial(ex, x)
assert mp == 8*x**4 - 16*x**3 + 4*x**2 + 4*x - 1
p = (expand((1 + sqrt(2) - 2*sqrt(3) + sqrt(7))**3))**Rational(1, 3)
mp = minimal_polynomial(p, x)
assert mp == x**8 - 8*x**7 - 56*x**6 + 448*x**5 + 480*x**4 - 5056*x**3 + 1984*x**2 + 7424*x - 3008
p = expand((1 + sqrt(2) - 2*sqrt(3) + sqrt(7))**3)
mp = minimal_polynomial(p, x)
assert mp == x**8 - 512*x**7 - 118208*x**6 + 31131136*x**5 + 647362560*x**4 - 56026611712*x**3 + 116994310144*x**2 + 404854931456*x - 27216576512
assert minimal_polynomial(S("-sqrt(5)/2 - 1/2 + (-sqrt(5)/2 - 1/2)**2"), x) == x - 1
a = 1 + sqrt(2)
assert minimal_polynomial((a*sqrt(2) + a)**3, x) == x**2 - 198*x + 1
p = 1/(1 + sqrt(2) + sqrt(3))
assert minimal_polynomial(p, x, compose=False) == 8*x**4 - 16*x**3 + 4*x**2 + 4*x - 1
p = 2/(1 + sqrt(2) + sqrt(3))
assert minimal_polynomial(p, x, compose=False) == x**4 - 4*x**3 + 2*x**2 + 4*x - 2
assert minimal_polynomial(1 + sqrt(2)*I, x, compose=False) == x**2 - 2*x + 3
assert minimal_polynomial(1/(1 + sqrt(2)) + 1, x, compose=False) == x**2 - 2
assert minimal_polynomial(sqrt(2)*I + I*(1 + sqrt(2)), x,
compose=False) == x**4 + 18*x**2 + 49
# minimal polynomial of I
assert minimal_polynomial(I, x, domain=QQ.algebraic_field(I)) == x - I
K = QQ.algebraic_field(I*(sqrt(2) + 1))
assert minimal_polynomial(I, x, domain=K) == x - I
assert minimal_polynomial(I, x, domain=QQ) == x**2 + 1
assert minimal_polynomial(I, x, domain='QQ(y)') == x**2 + 1
#issue 11553
assert minimal_polynomial(GoldenRatio, x) == x**2 - x - 1
assert minimal_polynomial(TribonacciConstant + 3, x) == x**3 - 10*x**2 + 32*x - 34
assert minimal_polynomial(GoldenRatio, x, domain=QQ.algebraic_field(sqrt(5))) == \
2*x - sqrt(5) - 1
assert minimal_polynomial(TribonacciConstant, x, domain=QQ.algebraic_field(cbrt(19 - 3*sqrt(33)))) == \
48*x - 19*(19 - 3*sqrt(33))**Rational(2, 3) - 3*sqrt(33)*(19 - 3*sqrt(33))**Rational(2, 3) \
- 16*(19 - 3*sqrt(33))**Rational(1, 3) - 16
# AlgebraicNumber with an alias.
# Wester H24
phi = AlgebraicNumber(S.GoldenRatio.expand(func=True), alias='phi')
assert minimal_polynomial(phi, x) == x**2 - x - 1
def test_minimal_polynomial_issue_19732():
# https://github.com/sympy/sympy/issues/19732
expr = (-280898097948878450887044002323982963174671632174995451265117559518123750720061943079105185551006003416773064305074191140286225850817291393988597615/(-488144716373031204149459129212782509078221364279079444636386844223983756114492222145074506571622290776245390771587888364089507840000000*sqrt(238368341569)*sqrt(S(11918417078450)/63568729
- 24411360*sqrt(238368341569)/63568729) +
238326799225996604451373809274348704114327860564921529846705817404208077866956345381951726531296652901169111729944612727047670549086208000000*sqrt(S(11918417078450)/63568729
- 24411360*sqrt(238368341569)/63568729)) -
180561807339168676696180573852937120123827201075968945871075967679148461189459480842956689723484024031016208588658753107/(-59358007109636562851035004992802812513575019937126272896569856090962677491318275291141463850327474176000000*sqrt(238368341569)*sqrt(S(11918417078450)/63568729
- 24411360*sqrt(238368341569)/63568729) +
28980348180319251787320809875930301310576055074938369007463004788921613896002936637780993064387310446267596800000*sqrt(S(11918417078450)/63568729
- 24411360*sqrt(238368341569)/63568729)))
poly = (2151288870990266634727173620565483054187142169311153766675688628985237817262915166497766867289157986631135400926544697981091151416655364879773546003475813114962656742744975460025956167152918469472166170500512008351638710934022160294849059721218824490226159355197136265032810944357335461128949781377875451881300105989490353140886315677977149440000000000000000000000*x**4
- 5773274155644072033773937864114266313663195672820501581692669271302387257492905909558846459600429795784309388968498783843631580008547382703258503404023153694528041873101120067477617592651525155101107144042679962433039557235772239171616433004024998230222455940044709064078962397144550855715640331680262171410099614469231080995436488414164502751395405398078353242072696360734131090111239998110773292915337556205692674790561090109440000000000000*x**2
+ 211295968822207088328287206509522887719741955693091053353263782924470627623790749534705683380138972642560898936171035770539616881000369889020398551821767092685775598633794696371561234818461806577723412581353857653829324364446419444210520602157621008010129702779407422072249192199762604318993590841636967747488049176548615614290254356975376588506729604345612047361483789518445332415765213187893207704958013682516462853001964919444736320672860140355089)
assert minimal_polynomial(expr, x) == poly
def test_minimal_polynomial_hi_prec():
p = 1/sqrt(1 - 9*sqrt(2) + 7*sqrt(3) + Rational(1, 10)**30)
mp = minimal_polynomial(p, x)
# checked with Wolfram Alpha
assert mp.coeff(x**6) == -1232000000000000000000000000001223999999999999999999999999999987999999999999999999999999999996000000000000000000000000000000
def test_minimal_polynomial_sq():
from sympy.core.add import Add
from sympy.core.function import expand_multinomial
p = expand_multinomial((1 + 5*sqrt(2) + 2*sqrt(3))**3)
mp = minimal_polynomial(p**Rational(1, 3), x)
assert mp == x**4 - 4*x**3 - 118*x**2 + 244*x + 1321
p = expand_multinomial((1 + sqrt(2) - 2*sqrt(3) + sqrt(7))**3)
mp = minimal_polynomial(p**Rational(1, 3), x)
assert mp == x**8 - 8*x**7 - 56*x**6 + 448*x**5 + 480*x**4 - 5056*x**3 + 1984*x**2 + 7424*x - 3008
p = Add(*[sqrt(i) for i in range(1, 12)])
mp = minimal_polynomial(p, x)
assert mp.subs({x: 0}) == -71965773323122507776
def test_minpoly_compose():
# issue 6868
eq = S('''
-1/(800*sqrt(-1/240 + 1/(18000*(-1/17280000 +
sqrt(15)*I/28800000)**(1/3)) + 2*(-1/17280000 +
sqrt(15)*I/28800000)**(1/3)))''')
mp = minimal_polynomial(eq + 3, x)
assert mp == 8000*x**2 - 48000*x + 71999
# issue 5888
assert minimal_polynomial(exp(I*pi/8), x) == x**8 + 1
mp = minimal_polynomial(sin(pi/7) + sqrt(2), x)
assert mp == 4096*x**12 - 63488*x**10 + 351488*x**8 - 826496*x**6 + \
770912*x**4 - 268432*x**2 + 28561
mp = minimal_polynomial(cos(pi/7) + sqrt(2), x)
assert mp == 64*x**6 - 64*x**5 - 432*x**4 + 304*x**3 + 712*x**2 - \
232*x - 239
mp = minimal_polynomial(exp(I*pi/7) + sqrt(2), x)
assert mp == x**12 - 2*x**11 - 9*x**10 + 16*x**9 + 43*x**8 - 70*x**7 - 97*x**6 + 126*x**5 + 211*x**4 - 212*x**3 - 37*x**2 + 142*x + 127
mp = minimal_polynomial(sin(pi/7) + sqrt(2), x)
assert mp == 4096*x**12 - 63488*x**10 + 351488*x**8 - 826496*x**6 + \
770912*x**4 - 268432*x**2 + 28561
mp = minimal_polynomial(cos(pi/7) + sqrt(2), x)
assert mp == 64*x**6 - 64*x**5 - 432*x**4 + 304*x**3 + 712*x**2 - \
232*x - 239
mp = minimal_polynomial(exp(I*pi/7) + sqrt(2), x)
assert mp == x**12 - 2*x**11 - 9*x**10 + 16*x**9 + 43*x**8 - 70*x**7 - 97*x**6 + 126*x**5 + 211*x**4 - 212*x**3 - 37*x**2 + 142*x + 127
mp = minimal_polynomial(exp(I*pi*Rational(2, 7)), x)
assert mp == x**6 + x**5 + x**4 + x**3 + x**2 + x + 1
mp = minimal_polynomial(exp(I*pi*Rational(2, 15)), x)
assert mp == x**8 - x**7 + x**5 - x**4 + x**3 - x + 1
mp = minimal_polynomial(cos(pi*Rational(2, 7)), x)
assert mp == 8*x**3 + 4*x**2 - 4*x - 1
mp = minimal_polynomial(sin(pi*Rational(2, 7)), x)
ex = (5*cos(pi*Rational(2, 7)) - 7)/(9*cos(pi/7) - 5*cos(pi*Rational(3, 7)))
mp = minimal_polynomial(ex, x)
assert mp == x**3 + 2*x**2 - x - 1
assert minimal_polynomial(-1/(2*cos(pi/7)), x) == x**3 + 2*x**2 - x - 1
assert minimal_polynomial(sin(pi*Rational(2, 15)), x) == \
256*x**8 - 448*x**6 + 224*x**4 - 32*x**2 + 1
assert minimal_polynomial(sin(pi*Rational(5, 14)), x) == 8*x**3 - 4*x**2 - 4*x + 1
assert minimal_polynomial(cos(pi/15), x) == 16*x**4 + 8*x**3 - 16*x**2 - 8*x + 1
ex = rootof(x**3 +x*4 + 1, 0)
mp = minimal_polynomial(ex, x)
assert mp == x**3 + 4*x + 1
mp = minimal_polynomial(ex + 1, x)
assert mp == x**3 - 3*x**2 + 7*x - 4
assert minimal_polynomial(exp(I*pi/3), x) == x**2 - x + 1
assert minimal_polynomial(exp(I*pi/4), x) == x**4 + 1
assert minimal_polynomial(exp(I*pi/6), x) == x**4 - x**2 + 1
assert minimal_polynomial(exp(I*pi/9), x) == x**6 - x**3 + 1
assert minimal_polynomial(exp(I*pi/10), x) == x**8 - x**6 + x**4 - x**2 + 1
assert minimal_polynomial(sin(pi/9), x) == 64*x**6 - 96*x**4 + 36*x**2 - 3
assert minimal_polynomial(sin(pi/11), x) == 1024*x**10 - 2816*x**8 + \
2816*x**6 - 1232*x**4 + 220*x**2 - 11
assert minimal_polynomial(sin(pi/21), x) == 4096*x**12 - 11264*x**10 + \
11264*x**8 - 4992*x**6 + 960*x**4 - 64*x**2 + 1
assert minimal_polynomial(cos(pi/9), x) == 8*x**3 - 6*x - 1
ex = 2**Rational(1, 3)*exp(2*I*pi/3)
assert minimal_polynomial(ex, x) == x**3 - 2
raises(NotAlgebraic, lambda: minimal_polynomial(cos(pi*sqrt(2)), x))
raises(NotAlgebraic, lambda: minimal_polynomial(sin(pi*sqrt(2)), x))
raises(NotAlgebraic, lambda: minimal_polynomial(exp(1.618*I*pi), x))
raises(NotAlgebraic, lambda: minimal_polynomial(exp(I*pi*sqrt(2)), x))
# issue 5934
ex = 1/(-36000 - 7200*sqrt(5) + (12*sqrt(10)*sqrt(sqrt(5) + 5) +
24*sqrt(10)*sqrt(-sqrt(5) + 5))**2) + 1
raises(ZeroDivisionError, lambda: minimal_polynomial(ex, x))
ex = sqrt(1 + 2**Rational(1,3)) + sqrt(1 + 2**Rational(1,4)) + sqrt(2)
mp = minimal_polynomial(ex, x)
assert degree(mp) == 48 and mp.subs({x:0}) == -16630256576
ex = tan(pi/5, evaluate=False)
mp = minimal_polynomial(ex, x)
assert mp == x**4 - 10*x**2 + 5
assert mp.subs(x, tan(pi/5)).is_zero
ex = tan(pi/6, evaluate=False)
mp = minimal_polynomial(ex, x)
assert mp == 3*x**2 - 1
assert mp.subs(x, tan(pi/6)).is_zero
ex = tan(pi/10, evaluate=False)
mp = minimal_polynomial(ex, x)
assert mp == 5*x**4 - 10*x**2 + 1
assert mp.subs(x, tan(pi/10)).is_zero
raises(NotAlgebraic, lambda: minimal_polynomial(tan(pi*sqrt(2)), x))
def test_minpoly_issue_7113():
# see discussion in https://github.com/sympy/sympy/pull/2234
from sympy.simplify.simplify import nsimplify
r = nsimplify(pi, tolerance=0.000000001)
mp = minimal_polynomial(r, x)
assert mp == 1768292677839237920489538677417507171630859375*x**109 - \
2734577732179183863586489182929671773182898498218854181690460140337930774573792597743853652058046464
def test_minpoly_issue_23677():
r1 = CRootOf(4000000*x**3 - 239960000*x**2 + 4782399900*x - 31663998001, 0)
r2 = CRootOf(4000000*x**3 - 239960000*x**2 + 4782399900*x - 31663998001, 1)
num = (7680000000000000000*r1**4*r2**4 - 614323200000000000000*r1**4*r2**3
+ 18458112576000000000000*r1**4*r2**2 - 246896663036160000000000*r1**4*r2
+ 1240473830323209600000000*r1**4 - 614323200000000000000*r1**3*r2**4
- 1476464424954240000000000*r1**3*r2**2 - 99225501687553535904000000*r1**3
+ 18458112576000000000000*r1**2*r2**4 - 1476464424954240000000000*r1**2*r2**3
- 593391458458356671712000000*r1**2*r2 + 2981354896834339226880720000*r1**2
- 246896663036160000000000*r1*r2**4 - 593391458458356671712000000*r1*r2**2
- 39878756418031796275267195200*r1 + 1240473830323209600000000*r2**4
- 99225501687553535904000000*r2**3 + 2981354896834339226880720000*r2**2 -
39878756418031796275267195200*r2 + 200361370275616536577343808012)
mp = (x**3 + 59426520028417434406408556687919*x**2 +
1161475464966574421163316896737773190861975156439163671112508400*x +
7467465541178623874454517208254940823818304424383315270991298807299003671748074773558707779600)
assert minimal_polynomial(num, x) == mp
def test_minpoly_issue_7574():
ex = -(-1)**Rational(1, 3) + (-1)**Rational(2,3)
assert minimal_polynomial(ex, x) == x + 1
def test_choose_factor():
# Test that this does not enter an infinite loop:
bad_factors = [Poly(x-2, x), Poly(x+2, x)]
raises(NotImplementedError, lambda: _choose_factor(bad_factors, x, sqrt(3)))
def test_minpoly_fraction_field():
assert minimal_polynomial(1/x, y) == -x*y + 1
assert minimal_polynomial(1 / (x + 1), y) == (x + 1)*y - 1
assert minimal_polynomial(sqrt(x), y) == y**2 - x
assert minimal_polynomial(sqrt(x + 1), y) == y**2 - x - 1
assert minimal_polynomial(sqrt(x) / x, y) == x*y**2 - 1
assert minimal_polynomial(sqrt(2) * sqrt(x), y) == y**2 - 2 * x
assert minimal_polynomial(sqrt(2) + sqrt(x), y) == \
y**4 + (-2*x - 4)*y**2 + x**2 - 4*x + 4
assert minimal_polynomial(x**Rational(1,3), y) == y**3 - x
assert minimal_polynomial(x**Rational(1,3) + sqrt(x), y) == \
y**6 - 3*x*y**4 - 2*x*y**3 + 3*x**2*y**2 - 6*x**2*y - x**3 + x**2
assert minimal_polynomial(sqrt(x) / z, y) == z**2*y**2 - x
assert minimal_polynomial(sqrt(x) / (z + 1), y) == (z**2 + 2*z + 1)*y**2 - x
assert minimal_polynomial(1/x, y, polys=True) == Poly(-x*y + 1, y, domain='ZZ(x)')
assert minimal_polynomial(1 / (x + 1), y, polys=True) == \
Poly((x + 1)*y - 1, y, domain='ZZ(x)')
assert minimal_polynomial(sqrt(x), y, polys=True) == Poly(y**2 - x, y, domain='ZZ(x)')
assert minimal_polynomial(sqrt(x) / z, y, polys=True) == \
Poly(z**2*y**2 - x, y, domain='ZZ(x, z)')
# this is (sqrt(1 + x**3)/x).integrate(x).diff(x) - sqrt(1 + x**3)/x
a = sqrt(x)/sqrt(1 + x**(-3)) - sqrt(x**3 + 1)/x + 1/(x**Rational(5, 2)* \
(1 + x**(-3))**Rational(3, 2)) + 1/(x**Rational(11, 2)*(1 + x**(-3))**Rational(3, 2))
assert minimal_polynomial(a, y) == y
raises(NotAlgebraic, lambda: minimal_polynomial(exp(x), y))
raises(GeneratorsError, lambda: minimal_polynomial(sqrt(x), x))
raises(GeneratorsError, lambda: minimal_polynomial(sqrt(x) - y, x))
raises(NotImplementedError, lambda: minimal_polynomial(sqrt(x), y, compose=False))
@slow
def test_minpoly_fraction_field_slow():
assert minimal_polynomial(minimal_polynomial(sqrt(x**Rational(1,5) - 1),
y).subs(y, sqrt(x**Rational(1,5) - 1)), z) == z
def test_minpoly_domain():
assert minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) == \
x - sqrt(2)
assert minimal_polynomial(sqrt(8), x, domain=QQ.algebraic_field(sqrt(2))) == \
x - 2*sqrt(2)
assert minimal_polynomial(sqrt(Rational(3,2)), x,
domain=QQ.algebraic_field(sqrt(2))) == 2*x**2 - 3
raises(NotAlgebraic, lambda: minimal_polynomial(y, x, domain=QQ))
def test_issue_14831():
a = -2*sqrt(2)*sqrt(12*sqrt(2) + 17)
assert minimal_polynomial(a, x) == x**2 + 16*x - 8
e = (-3*sqrt(12*sqrt(2) + 17) + 12*sqrt(2) +
17 - 2*sqrt(2)*sqrt(12*sqrt(2) + 17))
assert minimal_polynomial(e, x) == x
def test_issue_18248():
assert nonlinsolve([x*y**3-sqrt(2)/3, x*y**6-4/(9*(sqrt(3)))],x,y) == \
FiniteSet((sqrt(3)/2, sqrt(6)/3), (sqrt(3)/2, -sqrt(6)/6 - sqrt(2)*I/2),
(sqrt(3)/2, -sqrt(6)/6 + sqrt(2)*I/2))
def test_issue_13230():
c1 = Circle(Point2D(3, sqrt(5)), 5)
c2 = Circle(Point2D(4, sqrt(7)), 6)
assert intersection(c1, c2) == [Point2D(-1 + (-sqrt(7) + sqrt(5))*(-2*sqrt(7)/29
+ 9*sqrt(5)/29 + sqrt(196*sqrt(35) + 1941)/29), -2*sqrt(7)/29 + 9*sqrt(5)/29
+ sqrt(196*sqrt(35) + 1941)/29), Point2D(-1 + (-sqrt(7) + sqrt(5))*(-sqrt(196*sqrt(35)
+ 1941)/29 - 2*sqrt(7)/29 + 9*sqrt(5)/29), -sqrt(196*sqrt(35) + 1941)/29 - 2*sqrt(7)/29 + 9*sqrt(5)/29)]
def test_issue_19760():
e = 1/(sqrt(1 + sqrt(2)) - sqrt(2)*sqrt(1 + sqrt(2))) + 1
mp_expected = x**4 - 4*x**3 + 4*x**2 - 2
for comp in (True, False):
mp = Poly(minimal_polynomial(e, compose=comp))
assert mp(x) == mp_expected, "minimal_polynomial(e, compose=%s) = %s; %s expected" % (comp, mp(x), mp_expected)
def test_issue_20163():
assert apart(1/(x**6+1), extension=[sqrt(3), I]) == \
(sqrt(3) + I)/(2*x + sqrt(3) + I)/6 + \
(sqrt(3) - I)/(2*x + sqrt(3) - I)/6 - \
(sqrt(3) - I)/(2*x - sqrt(3) + I)/6 - \
(sqrt(3) + I)/(2*x - sqrt(3) - I)/6 + \
I/(x + I)/6 - I/(x - I)/6
def test_issue_22559():
alpha = AlgebraicNumber(sqrt(2))
assert minimal_polynomial(alpha**3, x) == x**2 - 8
def test_issue_22561():
a = AlgebraicNumber(sqrt(2) + sqrt(3), [S(1) / 2, 0, S(-9) / 2, 0], gen=x)
assert a.as_expr() == sqrt(2)
assert minimal_polynomial(a, x) == x**2 - 2
assert minimal_polynomial(a**3, x) == x**2 - 8
def test_separate_sq_not_impl():
raises(NotImplementedError, lambda: _separate_sq(x**(S(1)/3) + x))
def test_minpoly_op_algebraic_element_not_impl():
raises(NotImplementedError,
lambda: _minpoly_op_algebraic_element(Pow, sqrt(2), sqrt(3), x, QQ))
def test_minpoly_groebner():
assert _minpoly_groebner(S(2)/3, x, Poly) == 3*x - 2
assert _minpoly_groebner(
(sqrt(2) + 3)*(sqrt(2) + 1), x, Poly) == x**2 - 10*x - 7
assert _minpoly_groebner((sqrt(2) + 3)**(S(1)/3)*(sqrt(2) + 1)**(S(1)/3),
x, Poly) == x**6 - 10*x**3 - 7
assert _minpoly_groebner((sqrt(2) + 3)**(-S(1)/3)*(sqrt(2) + 1)**(S(1)/3),
x, Poly) == 7*x**6 - 2*x**3 - 1
raises(NotAlgebraic, lambda: _minpoly_groebner(pi**2, x, Poly))

View File

@ -0,0 +1,752 @@
from sympy.abc import x, zeta
from sympy.polys import Poly, cyclotomic_poly
from sympy.polys.domains import FF, QQ, ZZ
from sympy.polys.matrices import DomainMatrix, DM
from sympy.polys.numberfields.exceptions import (
ClosureFailure, MissingUnityError, StructureError
)
from sympy.polys.numberfields.modules import (
Module, ModuleElement, ModuleEndomorphism, PowerBasis, PowerBasisElement,
find_min_poly, is_sq_maxrank_HNF, make_mod_elt, to_col,
)
from sympy.polys.numberfields.utilities import is_int
from sympy.polys.polyerrors import UnificationFailed
from sympy.testing.pytest import raises
def test_to_col():
c = [1, 2, 3, 4]
m = to_col(c)
assert m.domain.is_ZZ
assert m.shape == (4, 1)
assert m.flat() == c
def test_Module_NotImplemented():
M = Module()
raises(NotImplementedError, lambda: M.n)
raises(NotImplementedError, lambda: M.mult_tab())
raises(NotImplementedError, lambda: M.represent(None))
raises(NotImplementedError, lambda: M.starts_with_unity())
raises(NotImplementedError, lambda: M.element_from_rational(QQ(2, 3)))
def test_Module_ancestors():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
D = B.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ))
assert C.ancestors(include_self=True) == [A, B, C]
assert D.ancestors(include_self=True) == [A, B, D]
assert C.power_basis_ancestor() == A
assert C.nearest_common_ancestor(D) == B
M = Module()
assert M.power_basis_ancestor() is None
def test_Module_compat_col():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
col = to_col([1, 2, 3, 4])
row = col.transpose()
assert A.is_compat_col(col) is True
assert A.is_compat_col(row) is False
assert A.is_compat_col(1) is False
assert A.is_compat_col(DomainMatrix.eye(3, ZZ)[:, 0]) is False
assert A.is_compat_col(DomainMatrix.eye(4, QQ)[:, 0]) is False
assert A.is_compat_col(DomainMatrix.eye(4, ZZ)[:, 0]) is True
def test_Module_call():
T = Poly(cyclotomic_poly(5, x))
B = PowerBasis(T)
assert B(0).col.flat() == [1, 0, 0, 0]
assert B(1).col.flat() == [0, 1, 0, 0]
col = DomainMatrix.eye(4, ZZ)[:, 2]
assert B(col).col == col
raises(ValueError, lambda: B(-1))
def test_Module_starts_with_unity():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
assert A.starts_with_unity() is True
assert B.starts_with_unity() is False
def test_Module_basis_elements():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
basis = B.basis_elements()
bp = B.basis_element_pullbacks()
for i, (e, p) in enumerate(zip(basis, bp)):
c = [0] * 4
assert e.module == B
assert p.module == A
c[i] = 1
assert e == B(to_col(c))
c[i] = 2
assert p == A(to_col(c))
def test_Module_zero():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
assert A.zero().col.flat() == [0, 0, 0, 0]
assert A.zero().module == A
assert B.zero().col.flat() == [0, 0, 0, 0]
assert B.zero().module == B
def test_Module_one():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
assert A.one().col.flat() == [1, 0, 0, 0]
assert A.one().module == A
assert B.one().col.flat() == [1, 0, 0, 0]
assert B.one().module == A
def test_Module_element_from_rational():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
rA = A.element_from_rational(QQ(22, 7))
rB = B.element_from_rational(QQ(22, 7))
assert rA.coeffs == [22, 0, 0, 0]
assert rA.denom == 7
assert rA.module == A
assert rB.coeffs == [22, 0, 0, 0]
assert rB.denom == 7
assert rB.module == A
def test_Module_submodule_from_gens():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
gens = [2*A(0), 2*A(1), 6*A(0), 6*A(1)]
B = A.submodule_from_gens(gens)
# Because the 3rd and 4th generators do not add anything new, we expect
# the cols of the matrix of B to just reproduce the first two gens:
M = gens[0].column().hstack(gens[1].column())
assert B.matrix == M
# At least one generator must be provided:
raises(ValueError, lambda: A.submodule_from_gens([]))
# All generators must belong to A:
raises(ValueError, lambda: A.submodule_from_gens([3*A(0), B(0)]))
def test_Module_submodule_from_matrix():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
e = B(to_col([1, 2, 3, 4]))
f = e.to_parent()
assert f.col.flat() == [2, 4, 6, 8]
# Matrix must be over ZZ:
raises(ValueError, lambda: A.submodule_from_matrix(DomainMatrix.eye(4, QQ)))
# Number of rows of matrix must equal number of generators of module A:
raises(ValueError, lambda: A.submodule_from_matrix(2 * DomainMatrix.eye(5, ZZ)))
def test_Module_whole_submodule():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.whole_submodule()
e = B(to_col([1, 2, 3, 4]))
f = e.to_parent()
assert f.col.flat() == [1, 2, 3, 4]
e0, e1, e2, e3 = B(0), B(1), B(2), B(3)
assert e2 * e3 == e0
assert e3 ** 2 == e1
def test_PowerBasis_repr():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
assert repr(A) == 'PowerBasis(x**4 + x**3 + x**2 + x + 1)'
def test_PowerBasis_eq():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = PowerBasis(T)
assert A == B
def test_PowerBasis_mult_tab():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
M = A.mult_tab()
exp = {0: {0: [1, 0, 0, 0], 1: [0, 1, 0, 0], 2: [0, 0, 1, 0], 3: [0, 0, 0, 1]},
1: {1: [0, 0, 1, 0], 2: [0, 0, 0, 1], 3: [-1, -1, -1, -1]},
2: {2: [-1, -1, -1, -1], 3: [1, 0, 0, 0]},
3: {3: [0, 1, 0, 0]}}
# We get the table we expect:
assert M == exp
# And all entries are of expected type:
assert all(is_int(c) for u in M for v in M[u] for c in M[u][v])
def test_PowerBasis_represent():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
col = to_col([1, 2, 3, 4])
a = A(col)
assert A.represent(a) == col
b = A(col, denom=2)
raises(ClosureFailure, lambda: A.represent(b))
def test_PowerBasis_element_from_poly():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
f = Poly(1 + 2*x)
g = Poly(x**4)
h = Poly(0, x)
assert A.element_from_poly(f).coeffs == [1, 2, 0, 0]
assert A.element_from_poly(g).coeffs == [-1, -1, -1, -1]
assert A.element_from_poly(h).coeffs == [0, 0, 0, 0]
def test_PowerBasis_element__conversions():
k = QQ.cyclotomic_field(5)
L = QQ.cyclotomic_field(7)
B = PowerBasis(k)
# ANP --> PowerBasisElement
a = k([QQ(1, 2), QQ(1, 3), 5, 7])
e = B.element_from_ANP(a)
assert e.coeffs == [42, 30, 2, 3]
assert e.denom == 6
# PowerBasisElement --> ANP
assert e.to_ANP() == a
# Cannot convert ANP from different field
d = L([QQ(1, 2), QQ(1, 3), 5, 7])
raises(UnificationFailed, lambda: B.element_from_ANP(d))
# AlgebraicNumber --> PowerBasisElement
alpha = k.to_alg_num(a)
eps = B.element_from_alg_num(alpha)
assert eps.coeffs == [42, 30, 2, 3]
assert eps.denom == 6
# PowerBasisElement --> AlgebraicNumber
assert eps.to_alg_num() == alpha
# Cannot convert AlgebraicNumber from different field
delta = L.to_alg_num(d)
raises(UnificationFailed, lambda: B.element_from_alg_num(delta))
# When we don't know the field:
C = PowerBasis(k.ext.minpoly)
# Can convert from AlgebraicNumber:
eps = C.element_from_alg_num(alpha)
assert eps.coeffs == [42, 30, 2, 3]
assert eps.denom == 6
# But can't convert back:
raises(StructureError, lambda: eps.to_alg_num())
def test_Submodule_repr():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ), denom=3)
assert repr(B) == 'Submodule[[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 2]]/3'
def test_Submodule_reduced():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = A.submodule_from_matrix(6 * DomainMatrix.eye(4, ZZ), denom=3)
D = C.reduced()
assert D.denom == 1 and D == C == B
def test_Submodule_discard_before():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
B.compute_mult_tab()
C = B.discard_before(2)
assert C.parent == B.parent
assert B.is_sq_maxrank_HNF() and not C.is_sq_maxrank_HNF()
assert C.matrix == B.matrix[:, 2:]
assert C.mult_tab() == {0: {0: [-2, -2], 1: [0, 0]}, 1: {1: [0, 0]}}
def test_Submodule_QQ_matrix():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = A.submodule_from_matrix(6 * DomainMatrix.eye(4, ZZ), denom=3)
assert C.QQ_matrix == B.QQ_matrix
def test_Submodule_represent():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
a0 = A(to_col([6, 12, 18, 24]))
a1 = A(to_col([2, 4, 6, 8]))
a2 = A(to_col([1, 3, 5, 7]))
b1 = B.represent(a1)
assert b1.flat() == [1, 2, 3, 4]
c0 = C.represent(a0)
assert c0.flat() == [1, 2, 3, 4]
Y = A.submodule_from_matrix(DomainMatrix([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
], (3, 4), ZZ).transpose())
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
z0 = Z(to_col([1, 2, 3, 4, 5, 6]))
raises(ClosureFailure, lambda: Y.represent(A(3)))
raises(ClosureFailure, lambda: B.represent(a2))
raises(ClosureFailure, lambda: B.represent(z0))
def test_Submodule_is_compat_submodule():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
D = C.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ))
assert B.is_compat_submodule(C) is True
assert B.is_compat_submodule(A) is False
assert B.is_compat_submodule(D) is False
def test_Submodule_eq():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = A.submodule_from_matrix(6 * DomainMatrix.eye(4, ZZ), denom=3)
assert C == B
def test_Submodule_add():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(DomainMatrix([
[4, 0, 0, 0],
[0, 4, 0, 0],
], (2, 4), ZZ).transpose(), denom=6)
C = A.submodule_from_matrix(DomainMatrix([
[0, 10, 0, 0],
[0, 0, 7, 0],
], (2, 4), ZZ).transpose(), denom=15)
D = A.submodule_from_matrix(DomainMatrix([
[20, 0, 0, 0],
[ 0, 20, 0, 0],
[ 0, 0, 14, 0],
], (3, 4), ZZ).transpose(), denom=30)
assert B + C == D
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
Y = Z.submodule_from_gens([Z(0), Z(1)])
raises(TypeError, lambda: B + Y)
def test_Submodule_mul():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
C = A.submodule_from_matrix(DomainMatrix([
[0, 10, 0, 0],
[0, 0, 7, 0],
], (2, 4), ZZ).transpose(), denom=15)
C1 = A.submodule_from_matrix(DomainMatrix([
[0, 20, 0, 0],
[0, 0, 14, 0],
], (2, 4), ZZ).transpose(), denom=3)
C2 = A.submodule_from_matrix(DomainMatrix([
[0, 0, 10, 0],
[0, 0, 0, 7],
], (2, 4), ZZ).transpose(), denom=15)
C3_unred = A.submodule_from_matrix(DomainMatrix([
[0, 0, 100, 0],
[0, 0, 0, 70],
[0, 0, 0, 70],
[-49, -49, -49, -49]
], (4, 4), ZZ).transpose(), denom=225)
C3 = A.submodule_from_matrix(DomainMatrix([
[4900, 4900, 0, 0],
[4410, 4410, 10, 0],
[2107, 2107, 7, 7]
], (3, 4), ZZ).transpose(), denom=225)
assert C * 1 == C
assert C ** 1 == C
assert C * 10 == C1
assert C * A(1) == C2
assert C.mul(C, hnf=False) == C3_unred
assert C * C == C3
assert C ** 2 == C3
def test_Submodule_reduce_element():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.whole_submodule()
b = B(to_col([90, 84, 80, 75]), denom=120)
C = B.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=2)
b_bar_expected = B(to_col([30, 24, 20, 15]), denom=120)
b_bar = C.reduce_element(b)
assert b_bar == b_bar_expected
C = B.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=4)
b_bar_expected = B(to_col([0, 24, 20, 15]), denom=120)
b_bar = C.reduce_element(b)
assert b_bar == b_bar_expected
C = B.submodule_from_matrix(DomainMatrix.eye(4, ZZ), denom=8)
b_bar_expected = B(to_col([0, 9, 5, 0]), denom=120)
b_bar = C.reduce_element(b)
assert b_bar == b_bar_expected
a = A(to_col([1, 2, 3, 4]))
raises(NotImplementedError, lambda: C.reduce_element(a))
C = B.submodule_from_matrix(DomainMatrix([
[5, 4, 3, 2],
[0, 8, 7, 6],
[0, 0,11,12],
[0, 0, 0, 1]
], (4, 4), ZZ).transpose())
raises(StructureError, lambda: C.reduce_element(b))
def test_is_HNF():
M = DM([
[3, 2, 1],
[0, 2, 1],
[0, 0, 1]
], ZZ)
M1 = DM([
[3, 2, 1],
[0, -2, 1],
[0, 0, 1]
], ZZ)
M2 = DM([
[3, 2, 3],
[0, 2, 1],
[0, 0, 1]
], ZZ)
assert is_sq_maxrank_HNF(M) is True
assert is_sq_maxrank_HNF(M1) is False
assert is_sq_maxrank_HNF(M2) is False
def test_make_mod_elt():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
col = to_col([1, 2, 3, 4])
eA = make_mod_elt(A, col)
eB = make_mod_elt(B, col)
assert isinstance(eA, PowerBasisElement)
assert not isinstance(eB, PowerBasisElement)
def test_ModuleElement_repr():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 2, 3, 4]), denom=2)
assert repr(e) == '[1, 2, 3, 4]/2'
def test_ModuleElement_reduced():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([2, 4, 6, 8]), denom=2)
f = e.reduced()
assert f.denom == 1 and f == e
def test_ModuleElement_reduced_mod_p():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([20, 40, 60, 80]))
f = e.reduced_mod_p(7)
assert f.coeffs == [-1, -2, -3, 3]
def test_ModuleElement_from_int_list():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
c = [1, 2, 3, 4]
assert ModuleElement.from_int_list(A, c).coeffs == c
def test_ModuleElement_len():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(0)
assert len(e) == 4
def test_ModuleElement_column():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(0)
col1 = e.column()
assert col1 == e.col and col1 is not e.col
col2 = e.column(domain=FF(5))
assert col2.domain.is_FF
def test_ModuleElement_QQ_col():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 2, 3, 4]), denom=1)
f = A(to_col([3, 6, 9, 12]), denom=3)
assert e.QQ_col == f.QQ_col
def test_ModuleElement_to_ancestors():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
D = C.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ))
eD = D(0)
eC = eD.to_parent()
eB = eD.to_ancestor(B)
eA = eD.over_power_basis()
assert eC.module is C and eC.coeffs == [5, 0, 0, 0]
assert eB.module is B and eB.coeffs == [15, 0, 0, 0]
assert eA.module is A and eA.coeffs == [30, 0, 0, 0]
a = A(0)
raises(ValueError, lambda: a.to_parent())
def test_ModuleElement_compatibility():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
D = B.submodule_from_matrix(5 * DomainMatrix.eye(4, ZZ))
assert C(0).is_compat(C(1)) is True
assert C(0).is_compat(D(0)) is False
u, v = C(0).unify(D(0))
assert u.module is B and v.module is B
assert C(C.represent(u)) == C(0) and D(D.represent(v)) == D(0)
u, v = C(0).unify(C(1))
assert u == C(0) and v == C(1)
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
raises(UnificationFailed, lambda: C(0).unify(Z(1)))
def test_ModuleElement_eq():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 2, 3, 4]), denom=1)
f = A(to_col([3, 6, 9, 12]), denom=3)
assert e == f
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
assert e != Z(0)
assert e != 3.14
def test_ModuleElement_equiv():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 2, 3, 4]), denom=1)
f = A(to_col([3, 6, 9, 12]), denom=3)
assert e.equiv(f)
C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
g = C(to_col([1, 2, 3, 4]), denom=1)
h = A(to_col([3, 6, 9, 12]), denom=1)
assert g.equiv(h)
assert C(to_col([5, 0, 0, 0]), denom=7).equiv(QQ(15, 7))
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
raises(UnificationFailed, lambda: e.equiv(Z(0)))
assert e.equiv(3.14) is False
def test_ModuleElement_add():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
e = A(to_col([1, 2, 3, 4]), denom=6)
f = A(to_col([5, 6, 7, 8]), denom=10)
g = C(to_col([1, 1, 1, 1]), denom=2)
assert e + f == A(to_col([10, 14, 18, 22]), denom=15)
assert e - f == A(to_col([-5, -4, -3, -2]), denom=15)
assert e + g == A(to_col([10, 11, 12, 13]), denom=6)
assert e + QQ(7, 10) == A(to_col([26, 10, 15, 20]), denom=30)
assert g + QQ(7, 10) == A(to_col([22, 15, 15, 15]), denom=10)
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
raises(TypeError, lambda: e + Z(0))
raises(TypeError, lambda: e + 3.14)
def test_ModuleElement_mul():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
e = A(to_col([0, 2, 0, 0]), denom=3)
f = A(to_col([0, 0, 0, 7]), denom=5)
g = C(to_col([0, 0, 0, 1]), denom=2)
h = A(to_col([0, 0, 3, 1]), denom=7)
assert e * f == A(to_col([-14, -14, -14, -14]), denom=15)
assert e * g == A(to_col([-1, -1, -1, -1]))
assert e * h == A(to_col([-2, -2, -2, 4]), denom=21)
assert e * QQ(6, 5) == A(to_col([0, 4, 0, 0]), denom=5)
assert (g * QQ(10, 21)).equiv(A(to_col([0, 0, 0, 5]), denom=7))
assert e // QQ(6, 5) == A(to_col([0, 5, 0, 0]), denom=9)
U = Poly(cyclotomic_poly(7, x))
Z = PowerBasis(U)
raises(TypeError, lambda: e * Z(0))
raises(TypeError, lambda: e * 3.14)
raises(TypeError, lambda: e // 3.14)
raises(ZeroDivisionError, lambda: e // 0)
def test_ModuleElement_div():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
e = A(to_col([0, 2, 0, 0]), denom=3)
f = A(to_col([0, 0, 0, 7]), denom=5)
g = C(to_col([1, 1, 1, 1]))
assert e // f == 10*A(3)//21
assert e // g == -2*A(2)//9
assert 3 // g == -A(1)
def test_ModuleElement_pow():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
C = A.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
e = A(to_col([0, 2, 0, 0]), denom=3)
g = C(to_col([0, 0, 0, 1]), denom=2)
assert e ** 3 == A(to_col([0, 0, 0, 8]), denom=27)
assert g ** 2 == C(to_col([0, 3, 0, 0]), denom=4)
assert e ** 0 == A(to_col([1, 0, 0, 0]))
assert g ** 0 == A(to_col([1, 0, 0, 0]))
assert e ** 1 == e
assert g ** 1 == g
def test_ModuleElement_mod():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 15, 8, 0]), denom=2)
assert e % 7 == A(to_col([1, 1, 8, 0]), denom=2)
assert e % QQ(1, 2) == A.zero()
assert e % QQ(1, 3) == A(to_col([1, 1, 0, 0]), denom=6)
B = A.submodule_from_gens([A(0), 5*A(1), 3*A(2), A(3)])
assert e % B == A(to_col([1, 5, 2, 0]), denom=2)
C = B.whole_submodule()
raises(TypeError, lambda: e % C)
def test_PowerBasisElement_polys():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 15, 8, 0]), denom=2)
assert e.numerator(x=zeta) == Poly(8 * zeta ** 2 + 15 * zeta + 1, domain=ZZ)
assert e.poly(x=zeta) == Poly(4 * zeta ** 2 + QQ(15, 2) * zeta + QQ(1, 2), domain=QQ)
def test_PowerBasisElement_norm():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
lam = A(to_col([1, -1, 0, 0]))
assert lam.norm() == 5
def test_PowerBasisElement_inverse():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
e = A(to_col([1, 1, 1, 1]))
assert 2 // e == -2*A(1)
assert e ** -3 == -A(3)
def test_ModuleHomomorphism_matrix():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
phi = ModuleEndomorphism(A, lambda a: a ** 2)
M = phi.matrix()
assert M == DomainMatrix([
[1, 0, -1, 0],
[0, 0, -1, 1],
[0, 1, -1, 0],
[0, 0, -1, 0]
], (4, 4), ZZ)
def test_ModuleHomomorphism_kernel():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
phi = ModuleEndomorphism(A, lambda a: a ** 5)
N = phi.kernel()
assert N.n == 3
def test_EndomorphismRing_represent():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
R = A.endomorphism_ring()
phi = R.inner_endomorphism(A(1))
col = R.represent(phi)
assert col.transpose() == DomainMatrix([
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1]
], (1, 16), ZZ)
B = A.submodule_from_matrix(DomainMatrix.zeros((4, 0), ZZ))
S = B.endomorphism_ring()
psi = S.inner_endomorphism(A(1))
col = S.represent(psi)
assert col == DomainMatrix([], (0, 0), ZZ)
raises(NotImplementedError, lambda: R.represent(3.14))
def test_find_min_poly():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
powers = []
m = find_min_poly(A(1), QQ, x=x, powers=powers)
assert m == Poly(T, domain=QQ)
assert len(powers) == 5
# powers list need not be passed
m = find_min_poly(A(1), QQ, x=x)
assert m == Poly(T, domain=QQ)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
raises(MissingUnityError, lambda: find_min_poly(B(1), QQ))

View File

@ -0,0 +1,202 @@
"""Tests on algebraic numbers. """
from sympy.core.containers import Tuple
from sympy.core.numbers import (AlgebraicNumber, I, Rational)
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.polys.polytools import Poly
from sympy.polys.numberfields.subfield import to_number_field
from sympy.polys.polyclasses import DMP
from sympy.polys.domains import QQ
from sympy.polys.rootoftools import CRootOf
from sympy.abc import x, y
def test_AlgebraicNumber():
minpoly, root = x**2 - 2, sqrt(2)
a = AlgebraicNumber(root, gen=x)
assert a.rep == DMP([QQ(1), QQ(0)], QQ)
assert a.root == root
assert a.alias is None
assert a.minpoly == minpoly
assert a.is_number
assert a.is_aliased is False
assert a.coeffs() == [S.One, S.Zero]
assert a.native_coeffs() == [QQ(1), QQ(0)]
a = AlgebraicNumber(root, gen=x, alias='y')
assert a.rep == DMP([QQ(1), QQ(0)], QQ)
assert a.root == root
assert a.alias == Symbol('y')
assert a.minpoly == minpoly
assert a.is_number
assert a.is_aliased is True
a = AlgebraicNumber(root, gen=x, alias=Symbol('y'))
assert a.rep == DMP([QQ(1), QQ(0)], QQ)
assert a.root == root
assert a.alias == Symbol('y')
assert a.minpoly == minpoly
assert a.is_number
assert a.is_aliased is True
assert AlgebraicNumber(sqrt(2), []).rep == DMP([], QQ)
assert AlgebraicNumber(sqrt(2), ()).rep == DMP([], QQ)
assert AlgebraicNumber(sqrt(2), (0, 0)).rep == DMP([], QQ)
assert AlgebraicNumber(sqrt(2), [8]).rep == DMP([QQ(8)], QQ)
assert AlgebraicNumber(sqrt(2), [Rational(8, 3)]).rep == DMP([QQ(8, 3)], QQ)
assert AlgebraicNumber(sqrt(2), [7, 3]).rep == DMP([QQ(7), QQ(3)], QQ)
assert AlgebraicNumber(
sqrt(2), [Rational(7, 9), Rational(3, 2)]).rep == DMP([QQ(7, 9), QQ(3, 2)], QQ)
assert AlgebraicNumber(sqrt(2), [1, 2, 3]).rep == DMP([QQ(2), QQ(5)], QQ)
a = AlgebraicNumber(AlgebraicNumber(root, gen=x), [1, 2])
assert a.rep == DMP([QQ(1), QQ(2)], QQ)
assert a.root == root
assert a.alias is None
assert a.minpoly == minpoly
assert a.is_number
assert a.is_aliased is False
assert a.coeffs() == [S.One, S(2)]
assert a.native_coeffs() == [QQ(1), QQ(2)]
a = AlgebraicNumber((minpoly, root), [1, 2])
assert a.rep == DMP([QQ(1), QQ(2)], QQ)
assert a.root == root
assert a.alias is None
assert a.minpoly == minpoly
assert a.is_number
assert a.is_aliased is False
a = AlgebraicNumber((Poly(minpoly), root), [1, 2])
assert a.rep == DMP([QQ(1), QQ(2)], QQ)
assert a.root == root
assert a.alias is None
assert a.minpoly == minpoly
assert a.is_number
assert a.is_aliased is False
assert AlgebraicNumber( sqrt(3)).rep == DMP([ QQ(1), QQ(0)], QQ)
assert AlgebraicNumber(-sqrt(3)).rep == DMP([ QQ(1), QQ(0)], QQ)
a = AlgebraicNumber(sqrt(2))
b = AlgebraicNumber(sqrt(2))
assert a == b
c = AlgebraicNumber(sqrt(2), gen=x)
assert a == b
assert a == c
a = AlgebraicNumber(sqrt(2), [1, 2])
b = AlgebraicNumber(sqrt(2), [1, 3])
assert a != b and a != sqrt(2) + 3
assert (a == x) is False and (a != x) is True
a = AlgebraicNumber(sqrt(2), [1, 0])
b = AlgebraicNumber(sqrt(2), [1, 0], alias=y)
assert a.as_poly(x) == Poly(x, domain='QQ')
assert b.as_poly() == Poly(y, domain='QQ')
assert a.as_expr() == sqrt(2)
assert a.as_expr(x) == x
assert b.as_expr() == sqrt(2)
assert b.as_expr(x) == x
a = AlgebraicNumber(sqrt(2), [2, 3])
b = AlgebraicNumber(sqrt(2), [2, 3], alias=y)
p = a.as_poly()
assert p == Poly(2*p.gen + 3)
assert a.as_poly(x) == Poly(2*x + 3, domain='QQ')
assert b.as_poly() == Poly(2*y + 3, domain='QQ')
assert a.as_expr() == 2*sqrt(2) + 3
assert a.as_expr(x) == 2*x + 3
assert b.as_expr() == 2*sqrt(2) + 3
assert b.as_expr(x) == 2*x + 3
a = AlgebraicNumber(sqrt(2))
b = to_number_field(sqrt(2))
assert a.args == b.args == (sqrt(2), Tuple(1, 0))
b = AlgebraicNumber(sqrt(2), alias='alpha')
assert b.args == (sqrt(2), Tuple(1, 0), Symbol('alpha'))
a = AlgebraicNumber(sqrt(2), [1, 2, 3])
assert a.args == (sqrt(2), Tuple(1, 2, 3))
a = AlgebraicNumber(sqrt(2), [1, 2], "alpha")
b = AlgebraicNumber(a)
c = AlgebraicNumber(a, alias="gamma")
assert a == b
assert c.alias.name == "gamma"
a = AlgebraicNumber(sqrt(2) + sqrt(3), [S(1)/2, 0, S(-9)/2, 0])
b = AlgebraicNumber(a, [1, 0, 0])
assert b.root == a.root
assert a.to_root() == sqrt(2)
assert b.to_root() == 2
a = AlgebraicNumber(2)
assert a.is_primitive_element is True
def test_to_algebraic_integer():
a = AlgebraicNumber(sqrt(3), gen=x).to_algebraic_integer()
assert a.minpoly == x**2 - 3
assert a.root == sqrt(3)
assert a.rep == DMP([QQ(1), QQ(0)], QQ)
a = AlgebraicNumber(2*sqrt(3), gen=x).to_algebraic_integer()
assert a.minpoly == x**2 - 12
assert a.root == 2*sqrt(3)
assert a.rep == DMP([QQ(1), QQ(0)], QQ)
a = AlgebraicNumber(sqrt(3)/2, gen=x).to_algebraic_integer()
assert a.minpoly == x**2 - 12
assert a.root == 2*sqrt(3)
assert a.rep == DMP([QQ(1), QQ(0)], QQ)
a = AlgebraicNumber(sqrt(3)/2, [Rational(7, 19), 3], gen=x).to_algebraic_integer()
assert a.minpoly == x**2 - 12
assert a.root == 2*sqrt(3)
assert a.rep == DMP([QQ(7, 19), QQ(3)], QQ)
def test_AlgebraicNumber_to_root():
assert AlgebraicNumber(sqrt(2)).to_root() == sqrt(2)
zeta5_squared = AlgebraicNumber(CRootOf(x**5 - 1, 4), coeffs=[1, 0, 0])
assert zeta5_squared.to_root() == CRootOf(x**4 + x**3 + x**2 + x + 1, 1)
zeta3_squared = AlgebraicNumber(CRootOf(x**3 - 1, 2), coeffs=[1, 0, 0])
assert zeta3_squared.to_root() == -S(1)/2 - sqrt(3)*I/2
assert zeta3_squared.to_root(radicals=False) == CRootOf(x**2 + x + 1, 0)

View File

@ -0,0 +1,296 @@
from math import prod
from sympy import QQ, ZZ
from sympy.abc import x, theta
from sympy.ntheory import factorint
from sympy.ntheory.residue_ntheory import n_order
from sympy.polys import Poly, cyclotomic_poly
from sympy.polys.matrices import DomainMatrix
from sympy.polys.numberfields.basis import round_two
from sympy.polys.numberfields.exceptions import StructureError
from sympy.polys.numberfields.modules import PowerBasis, to_col
from sympy.polys.numberfields.primes import (
prime_decomp, _two_elt_rep,
_check_formal_conditions_for_maximal_order,
)
from sympy.testing.pytest import raises
def test_check_formal_conditions_for_maximal_order():
T = Poly(cyclotomic_poly(5, x))
A = PowerBasis(T)
B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
D = A.submodule_from_matrix(DomainMatrix.eye(4, ZZ)[:, :-1])
# Is a direct submodule of a power basis, but lacks 1 as first generator:
raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(B))
# Is not a direct submodule of a power basis:
raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(C))
# Is direct submod of pow basis, and starts with 1, but not sq/max rank/HNF:
raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(D))
def test_two_elt_rep():
ell = 7
T = Poly(cyclotomic_poly(ell))
ZK, dK = round_two(T)
for p in [29, 13, 11, 5]:
P = prime_decomp(p, T)
for Pi in P:
# We have Pi in two-element representation, and, because we are
# looking at a cyclotomic field, this was computed by the "easy"
# method that just factors T mod p. We will now convert this to
# a set of Z-generators, then convert that back into a two-element
# rep. The latter need not be identical to the two-elt rep we
# already have, but it must have the same HNF.
H = p*ZK + Pi.alpha*ZK
gens = H.basis_element_pullbacks()
# Note: we could supply f = Pi.f, but prefer to test behavior without it.
b = _two_elt_rep(gens, ZK, p)
if b != Pi.alpha:
H2 = p*ZK + b*ZK
assert H2 == H
def test_valuation_at_prime_ideal():
p = 7
T = Poly(cyclotomic_poly(p))
ZK, dK = round_two(T)
P = prime_decomp(p, T, dK=dK, ZK=ZK)
assert len(P) == 1
P0 = P[0]
v = P0.valuation(p*ZK)
assert v == P0.e
# Test easy 0 case:
assert P0.valuation(5*ZK) == 0
def test_decomp_1():
# All prime decompositions in cyclotomic fields are in the "easy case,"
# since the index is unity.
# Here we check the ramified prime.
T = Poly(cyclotomic_poly(7))
raises(ValueError, lambda: prime_decomp(7))
P = prime_decomp(7, T)
assert len(P) == 1
P0 = P[0]
assert P0.e == 6
assert P0.f == 1
# Test powers:
assert P0**0 == P0.ZK
assert P0**1 == P0
assert P0**6 == 7 * P0.ZK
def test_decomp_2():
# More easy cyclotomic cases, but here we check unramified primes.
ell = 7
T = Poly(cyclotomic_poly(ell))
for p in [29, 13, 11, 5]:
f_exp = n_order(p, ell)
g_exp = (ell - 1) // f_exp
P = prime_decomp(p, T)
assert len(P) == g_exp
for Pi in P:
assert Pi.e == 1
assert Pi.f == f_exp
def test_decomp_3():
T = Poly(x ** 2 - 35)
rad = {}
ZK, dK = round_two(T, radicals=rad)
# 35 is 3 mod 4, so field disc is 4*5*7, and theory says each of the
# rational primes 2, 5, 7 should be the square of a prime ideal.
for p in [2, 5, 7]:
P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
assert len(P) == 1
assert P[0].e == 2
assert P[0]**2 == p*ZK
def test_decomp_4():
T = Poly(x ** 2 - 21)
rad = {}
ZK, dK = round_two(T, radicals=rad)
# 21 is 1 mod 4, so field disc is 3*7, and theory says the
# rational primes 3, 7 should be the square of a prime ideal.
for p in [3, 7]:
P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
assert len(P) == 1
assert P[0].e == 2
assert P[0]**2 == p*ZK
def test_decomp_5():
# Here is our first test of the "hard case" of prime decomposition.
# We work in a quadratic extension Q(sqrt(d)) where d is 1 mod 4, and
# we consider the factorization of the rational prime 2, which divides
# the index.
# Theory says the form of p's factorization depends on the residue of
# d mod 8, so we consider both cases, d = 1 mod 8 and d = 5 mod 8.
for d in [-7, -3]:
T = Poly(x ** 2 - d)
rad = {}
ZK, dK = round_two(T, radicals=rad)
p = 2
P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
if d % 8 == 1:
assert len(P) == 2
assert all(P[i].e == 1 and P[i].f == 1 for i in range(2))
assert prod(Pi**Pi.e for Pi in P) == p * ZK
else:
assert d % 8 == 5
assert len(P) == 1
assert P[0].e == 1
assert P[0].f == 2
assert P[0].as_submodule() == p * ZK
def test_decomp_6():
# Another case where 2 divides the index. This is Dedekind's example of
# an essential discriminant divisor. (See Cohen, Exercise 6.10.)
T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
rad = {}
ZK, dK = round_two(T, radicals=rad)
p = 2
P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
assert len(P) == 3
assert all(Pi.e == Pi.f == 1 for Pi in P)
assert prod(Pi**Pi.e for Pi in P) == p*ZK
def test_decomp_7():
# Try working through an AlgebraicField
T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
K = QQ.alg_field_from_poly(T)
p = 2
P = K.primes_above(p)
ZK = K.maximal_order()
assert len(P) == 3
assert all(Pi.e == Pi.f == 1 for Pi in P)
assert prod(Pi**Pi.e for Pi in P) == p*ZK
def test_decomp_8():
# This time we consider various cubics, and try factoring all primes
# dividing the index.
cases = (
x ** 3 + 3 * x ** 2 - 4 * x + 4,
x ** 3 + 3 * x ** 2 + 3 * x - 3,
x ** 3 + 5 * x ** 2 - x + 3,
x ** 3 + 5 * x ** 2 - 5 * x - 5,
x ** 3 + 3 * x ** 2 + 5,
x ** 3 + 6 * x ** 2 + 3 * x - 1,
x ** 3 + 6 * x ** 2 + 4,
x ** 3 + 7 * x ** 2 + 7 * x - 7,
x ** 3 + 7 * x ** 2 - x + 5,
x ** 3 + 7 * x ** 2 - 5 * x + 5,
x ** 3 + 4 * x ** 2 - 3 * x + 7,
x ** 3 + 8 * x ** 2 + 5 * x - 1,
x ** 3 + 8 * x ** 2 - 2 * x + 6,
x ** 3 + 6 * x ** 2 - 3 * x + 8,
x ** 3 + 9 * x ** 2 + 6 * x - 8,
x ** 3 + 15 * x ** 2 - 9 * x + 13,
)
def display(T, p, radical, P, I, J):
"""Useful for inspection, when running test manually."""
print('=' * 20)
print(T, p, radical)
for Pi in P:
print(f' ({Pi!r})')
print("I: ", I)
print("J: ", J)
print(f'Equal: {I == J}')
inspect = False
for g in cases:
T = Poly(g)
rad = {}
ZK, dK = round_two(T, radicals=rad)
dT = T.discriminant()
f_squared = dT // dK
F = factorint(f_squared)
for p in F:
radical = rad.get(p)
P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=radical)
I = prod(Pi**Pi.e for Pi in P)
J = p * ZK
if inspect:
display(T, p, radical, P, I, J)
assert I == J
def test_PrimeIdeal_eq():
# `==` should fail on objects of different types, so even a completely
# inert PrimeIdeal should test unequal to the rational prime it divides.
T = Poly(cyclotomic_poly(7))
P0 = prime_decomp(5, T)[0]
assert P0.f == 6
assert P0.as_submodule() == 5 * P0.ZK
assert P0 != 5
def test_PrimeIdeal_add():
T = Poly(cyclotomic_poly(7))
P0 = prime_decomp(7, T)[0]
# Adding ideals computes their GCD, so adding the ramified prime dividing
# 7 to 7 itself should reproduce this prime (as a submodule).
assert P0 + 7 * P0.ZK == P0.as_submodule()
def test_str():
# Without alias:
k = QQ.alg_field_from_poly(Poly(x**2 + 7))
frp = k.primes_above(2)[0]
assert str(frp) == '(2, 3*_x/2 + 1/2)'
frp = k.primes_above(3)[0]
assert str(frp) == '(3)'
# With alias:
k = QQ.alg_field_from_poly(Poly(x ** 2 + 7), alias='alpha')
frp = k.primes_above(2)[0]
assert str(frp) == '(2, 3*alpha/2 + 1/2)'
frp = k.primes_above(3)[0]
assert str(frp) == '(3)'
def test_repr():
T = Poly(x**2 + 7)
ZK, dK = round_two(T)
P = prime_decomp(2, T, dK=dK, ZK=ZK)
assert repr(P[0]) == '[ (2, (3*x + 1)/2) e=1, f=1 ]'
assert P[0].repr(field_gen=theta) == '[ (2, (3*theta + 1)/2) e=1, f=1 ]'
assert P[0].repr(field_gen=theta, just_gens=True) == '(2, (3*theta + 1)/2)'
def test_PrimeIdeal_reduce():
k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8))
Zk = k.maximal_order()
P = k.primes_above(2)
frp = P[2]
# reduce_element
a = Zk.parent(to_col([23, 20, 11]), denom=6)
a_bar_expected = Zk.parent(to_col([11, 5, 2]), denom=6)
a_bar = frp.reduce_element(a)
assert a_bar == a_bar_expected
# reduce_ANP
a = k([QQ(11, 6), QQ(20, 6), QQ(23, 6)])
a_bar_expected = k([QQ(2, 6), QQ(5, 6), QQ(11, 6)])
a_bar = frp.reduce_ANP(a)
assert a_bar == a_bar_expected
# reduce_alg_num
a = k.to_alg_num(a)
a_bar_expected = k.to_alg_num(a_bar_expected)
a_bar = frp.reduce_alg_num(a)
assert a_bar == a_bar_expected
def test_issue_23402():
k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8))
P = k.primes_above(3)
assert P[0].alpha.equiv(0)

View File

@ -0,0 +1,304 @@
"""Tests for the subfield problem and allied problems. """
from sympy.core.numbers import (AlgebraicNumber, I, pi, Rational)
from sympy.core.singleton import S
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.external.gmpy import MPQ
from sympy.polys.numberfields.subfield import (
is_isomorphism_possible,
field_isomorphism_pslq,
field_isomorphism,
primitive_element,
to_number_field,
)
from sympy.polys.polyerrors import IsomorphismFailed
from sympy.polys.polytools import Poly
from sympy.polys.rootoftools import CRootOf
from sympy.testing.pytest import raises
from sympy.abc import x
Q = Rational
def test_field_isomorphism_pslq():
a = AlgebraicNumber(I)
b = AlgebraicNumber(I*sqrt(3))
raises(NotImplementedError, lambda: field_isomorphism_pslq(a, b))
a = AlgebraicNumber(sqrt(2))
b = AlgebraicNumber(sqrt(3))
c = AlgebraicNumber(sqrt(7))
d = AlgebraicNumber(sqrt(2) + sqrt(3))
e = AlgebraicNumber(sqrt(2) + sqrt(3) + sqrt(7))
assert field_isomorphism_pslq(a, a) == [1, 0]
assert field_isomorphism_pslq(a, b) is None
assert field_isomorphism_pslq(a, c) is None
assert field_isomorphism_pslq(a, d) == [Q(1, 2), 0, -Q(9, 2), 0]
assert field_isomorphism_pslq(
a, e) == [Q(1, 80), 0, -Q(1, 2), 0, Q(59, 20), 0]
assert field_isomorphism_pslq(b, a) is None
assert field_isomorphism_pslq(b, b) == [1, 0]
assert field_isomorphism_pslq(b, c) is None
assert field_isomorphism_pslq(b, d) == [-Q(1, 2), 0, Q(11, 2), 0]
assert field_isomorphism_pslq(b, e) == [-Q(
3, 640), 0, Q(67, 320), 0, -Q(297, 160), 0, Q(313, 80), 0]
assert field_isomorphism_pslq(c, a) is None
assert field_isomorphism_pslq(c, b) is None
assert field_isomorphism_pslq(c, c) == [1, 0]
assert field_isomorphism_pslq(c, d) is None
assert field_isomorphism_pslq(c, e) == [Q(
3, 640), 0, -Q(71, 320), 0, Q(377, 160), 0, -Q(469, 80), 0]
assert field_isomorphism_pslq(d, a) is None
assert field_isomorphism_pslq(d, b) is None
assert field_isomorphism_pslq(d, c) is None
assert field_isomorphism_pslq(d, d) == [1, 0]
assert field_isomorphism_pslq(d, e) == [-Q(
3, 640), 0, Q(71, 320), 0, -Q(377, 160), 0, Q(549, 80), 0]
assert field_isomorphism_pslq(e, a) is None
assert field_isomorphism_pslq(e, b) is None
assert field_isomorphism_pslq(e, c) is None
assert field_isomorphism_pslq(e, d) is None
assert field_isomorphism_pslq(e, e) == [1, 0]
f = AlgebraicNumber(3*sqrt(2) + 8*sqrt(7) - 5)
assert field_isomorphism_pslq(
f, e) == [Q(3, 80), 0, -Q(139, 80), 0, Q(347, 20), 0, -Q(761, 20), -5]
def test_field_isomorphism():
assert field_isomorphism(3, sqrt(2)) == [3]
assert field_isomorphism( I*sqrt(3), I*sqrt(3)/2) == [ 2, 0]
assert field_isomorphism(-I*sqrt(3), I*sqrt(3)/2) == [-2, 0]
assert field_isomorphism( I*sqrt(3), -I*sqrt(3)/2) == [-2, 0]
assert field_isomorphism(-I*sqrt(3), -I*sqrt(3)/2) == [ 2, 0]
assert field_isomorphism( 2*I*sqrt(3)/7, 5*I*sqrt(3)/3) == [ Rational(6, 35), 0]
assert field_isomorphism(-2*I*sqrt(3)/7, 5*I*sqrt(3)/3) == [Rational(-6, 35), 0]
assert field_isomorphism( 2*I*sqrt(3)/7, -5*I*sqrt(3)/3) == [Rational(-6, 35), 0]
assert field_isomorphism(-2*I*sqrt(3)/7, -5*I*sqrt(3)/3) == [ Rational(6, 35), 0]
assert field_isomorphism(
2*I*sqrt(3)/7 + 27, 5*I*sqrt(3)/3) == [ Rational(6, 35), 27]
assert field_isomorphism(
-2*I*sqrt(3)/7 + 27, 5*I*sqrt(3)/3) == [Rational(-6, 35), 27]
assert field_isomorphism(
2*I*sqrt(3)/7 + 27, -5*I*sqrt(3)/3) == [Rational(-6, 35), 27]
assert field_isomorphism(
-2*I*sqrt(3)/7 + 27, -5*I*sqrt(3)/3) == [ Rational(6, 35), 27]
p = AlgebraicNumber( sqrt(2) + sqrt(3))
q = AlgebraicNumber(-sqrt(2) + sqrt(3))
r = AlgebraicNumber( sqrt(2) - sqrt(3))
s = AlgebraicNumber(-sqrt(2) - sqrt(3))
pos_coeffs = [ S.Half, S.Zero, Rational(-9, 2), S.Zero]
neg_coeffs = [Rational(-1, 2), S.Zero, Rational(9, 2), S.Zero]
a = AlgebraicNumber(sqrt(2))
assert is_isomorphism_possible(a, p) is True
assert is_isomorphism_possible(a, q) is True
assert is_isomorphism_possible(a, r) is True
assert is_isomorphism_possible(a, s) is True
assert field_isomorphism(a, p, fast=True) == pos_coeffs
assert field_isomorphism(a, q, fast=True) == neg_coeffs
assert field_isomorphism(a, r, fast=True) == pos_coeffs
assert field_isomorphism(a, s, fast=True) == neg_coeffs
assert field_isomorphism(a, p, fast=False) == pos_coeffs
assert field_isomorphism(a, q, fast=False) == neg_coeffs
assert field_isomorphism(a, r, fast=False) == pos_coeffs
assert field_isomorphism(a, s, fast=False) == neg_coeffs
a = AlgebraicNumber(-sqrt(2))
assert is_isomorphism_possible(a, p) is True
assert is_isomorphism_possible(a, q) is True
assert is_isomorphism_possible(a, r) is True
assert is_isomorphism_possible(a, s) is True
assert field_isomorphism(a, p, fast=True) == neg_coeffs
assert field_isomorphism(a, q, fast=True) == pos_coeffs
assert field_isomorphism(a, r, fast=True) == neg_coeffs
assert field_isomorphism(a, s, fast=True) == pos_coeffs
assert field_isomorphism(a, p, fast=False) == neg_coeffs
assert field_isomorphism(a, q, fast=False) == pos_coeffs
assert field_isomorphism(a, r, fast=False) == neg_coeffs
assert field_isomorphism(a, s, fast=False) == pos_coeffs
pos_coeffs = [ S.Half, S.Zero, Rational(-11, 2), S.Zero]
neg_coeffs = [Rational(-1, 2), S.Zero, Rational(11, 2), S.Zero]
a = AlgebraicNumber(sqrt(3))
assert is_isomorphism_possible(a, p) is True
assert is_isomorphism_possible(a, q) is True
assert is_isomorphism_possible(a, r) is True
assert is_isomorphism_possible(a, s) is True
assert field_isomorphism(a, p, fast=True) == neg_coeffs
assert field_isomorphism(a, q, fast=True) == neg_coeffs
assert field_isomorphism(a, r, fast=True) == pos_coeffs
assert field_isomorphism(a, s, fast=True) == pos_coeffs
assert field_isomorphism(a, p, fast=False) == neg_coeffs
assert field_isomorphism(a, q, fast=False) == neg_coeffs
assert field_isomorphism(a, r, fast=False) == pos_coeffs
assert field_isomorphism(a, s, fast=False) == pos_coeffs
a = AlgebraicNumber(-sqrt(3))
assert is_isomorphism_possible(a, p) is True
assert is_isomorphism_possible(a, q) is True
assert is_isomorphism_possible(a, r) is True
assert is_isomorphism_possible(a, s) is True
assert field_isomorphism(a, p, fast=True) == pos_coeffs
assert field_isomorphism(a, q, fast=True) == pos_coeffs
assert field_isomorphism(a, r, fast=True) == neg_coeffs
assert field_isomorphism(a, s, fast=True) == neg_coeffs
assert field_isomorphism(a, p, fast=False) == pos_coeffs
assert field_isomorphism(a, q, fast=False) == pos_coeffs
assert field_isomorphism(a, r, fast=False) == neg_coeffs
assert field_isomorphism(a, s, fast=False) == neg_coeffs
pos_coeffs = [ Rational(3, 2), S.Zero, Rational(-33, 2), -S(8)]
neg_coeffs = [Rational(-3, 2), S.Zero, Rational(33, 2), -S(8)]
a = AlgebraicNumber(3*sqrt(3) - 8)
assert is_isomorphism_possible(a, p) is True
assert is_isomorphism_possible(a, q) is True
assert is_isomorphism_possible(a, r) is True
assert is_isomorphism_possible(a, s) is True
assert field_isomorphism(a, p, fast=True) == neg_coeffs
assert field_isomorphism(a, q, fast=True) == neg_coeffs
assert field_isomorphism(a, r, fast=True) == pos_coeffs
assert field_isomorphism(a, s, fast=True) == pos_coeffs
assert field_isomorphism(a, p, fast=False) == neg_coeffs
assert field_isomorphism(a, q, fast=False) == neg_coeffs
assert field_isomorphism(a, r, fast=False) == pos_coeffs
assert field_isomorphism(a, s, fast=False) == pos_coeffs
a = AlgebraicNumber(3*sqrt(2) + 2*sqrt(3) + 1)
pos_1_coeffs = [ S.Half, S.Zero, Rational(-5, 2), S.One]
neg_5_coeffs = [Rational(-5, 2), S.Zero, Rational(49, 2), S.One]
pos_5_coeffs = [ Rational(5, 2), S.Zero, Rational(-49, 2), S.One]
neg_1_coeffs = [Rational(-1, 2), S.Zero, Rational(5, 2), S.One]
assert is_isomorphism_possible(a, p) is True
assert is_isomorphism_possible(a, q) is True
assert is_isomorphism_possible(a, r) is True
assert is_isomorphism_possible(a, s) is True
assert field_isomorphism(a, p, fast=True) == pos_1_coeffs
assert field_isomorphism(a, q, fast=True) == neg_5_coeffs
assert field_isomorphism(a, r, fast=True) == pos_5_coeffs
assert field_isomorphism(a, s, fast=True) == neg_1_coeffs
assert field_isomorphism(a, p, fast=False) == pos_1_coeffs
assert field_isomorphism(a, q, fast=False) == neg_5_coeffs
assert field_isomorphism(a, r, fast=False) == pos_5_coeffs
assert field_isomorphism(a, s, fast=False) == neg_1_coeffs
a = AlgebraicNumber(sqrt(2))
b = AlgebraicNumber(sqrt(3))
c = AlgebraicNumber(sqrt(7))
assert is_isomorphism_possible(a, b) is True
assert is_isomorphism_possible(b, a) is True
assert is_isomorphism_possible(c, p) is False
assert field_isomorphism(sqrt(2), sqrt(3), fast=True) is None
assert field_isomorphism(sqrt(3), sqrt(2), fast=True) is None
assert field_isomorphism(sqrt(2), sqrt(3), fast=False) is None
assert field_isomorphism(sqrt(3), sqrt(2), fast=False) is None
a = AlgebraicNumber(sqrt(2))
b = AlgebraicNumber(2 ** (S(1) / 3))
assert is_isomorphism_possible(a, b) is False
assert field_isomorphism(a, b) is None
def test_primitive_element():
assert primitive_element([sqrt(2)], x) == (x**2 - 2, [1])
assert primitive_element(
[sqrt(2), sqrt(3)], x) == (x**4 - 10*x**2 + 1, [1, 1])
assert primitive_element([sqrt(2)], x, polys=True) == (Poly(x**2 - 2, domain='QQ'), [1])
assert primitive_element([sqrt(
2), sqrt(3)], x, polys=True) == (Poly(x**4 - 10*x**2 + 1, domain='QQ'), [1, 1])
assert primitive_element(
[sqrt(2)], x, ex=True) == (x**2 - 2, [1], [[1, 0]])
assert primitive_element([sqrt(2), sqrt(3)], x, ex=True) == \
(x**4 - 10*x**2 + 1, [1, 1], [[Q(1, 2), 0, -Q(9, 2), 0], [-
Q(1, 2), 0, Q(11, 2), 0]])
assert primitive_element(
[sqrt(2)], x, ex=True, polys=True) == (Poly(x**2 - 2, domain='QQ'), [1], [[1, 0]])
assert primitive_element([sqrt(2), sqrt(3)], x, ex=True, polys=True) == \
(Poly(x**4 - 10*x**2 + 1, domain='QQ'), [1, 1], [[Q(1, 2), 0, -Q(9, 2),
0], [-Q(1, 2), 0, Q(11, 2), 0]])
assert primitive_element([sqrt(2)], polys=True) == (Poly(x**2 - 2), [1])
raises(ValueError, lambda: primitive_element([], x, ex=False))
raises(ValueError, lambda: primitive_element([], x, ex=True))
# Issue 14117
a, b = I*sqrt(2*sqrt(2) + 3), I*sqrt(-2*sqrt(2) + 3)
assert primitive_element([a, b, I], x) == (x**4 + 6*x**2 + 1, [1, 0, 0])
assert primitive_element([sqrt(2), 0], x) == (x**2 - 2, [1, 0])
assert primitive_element([0, sqrt(2)], x) == (x**2 - 2, [1, 1])
assert primitive_element([sqrt(2), 0], x, ex=True) == (x**2 - 2, [1, 0], [[MPQ(1,1), MPQ(0,1)], []])
assert primitive_element([0, sqrt(2)], x, ex=True) == (x**2 - 2, [1, 1], [[], [MPQ(1,1), MPQ(0,1)]])
def test_to_number_field():
assert to_number_field(sqrt(2)) == AlgebraicNumber(sqrt(2))
assert to_number_field(
[sqrt(2), sqrt(3)]) == AlgebraicNumber(sqrt(2) + sqrt(3))
a = AlgebraicNumber(sqrt(2) + sqrt(3), [S.Half, S.Zero, Rational(-9, 2), S.Zero])
assert to_number_field(sqrt(2), sqrt(2) + sqrt(3)) == a
assert to_number_field(sqrt(2), AlgebraicNumber(sqrt(2) + sqrt(3))) == a
raises(IsomorphismFailed, lambda: to_number_field(sqrt(2), sqrt(3)))
def test_issue_22561():
a = to_number_field(sqrt(2), sqrt(2) + sqrt(3))
b = to_number_field(sqrt(2), sqrt(2) + sqrt(5))
assert field_isomorphism(a, b) == [1, 0]
def test_issue_22736():
a = CRootOf(x**4 + x**3 + x**2 + x + 1, -1)
a._reset()
b = exp(2*I*pi/5)
assert field_isomorphism(a, b) == [1, 0]

View File

@ -0,0 +1,113 @@
from sympy.abc import x
from sympy.core.numbers import (I, Rational)
from sympy.core.singleton import S
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.polys import Poly, cyclotomic_poly
from sympy.polys.domains import FF, QQ
from sympy.polys.matrices import DomainMatrix, DM
from sympy.polys.matrices.exceptions import DMRankError
from sympy.polys.numberfields.utilities import (
AlgIntPowers, coeff_search, extract_fundamental_discriminant,
isolate, supplement_a_subspace,
)
from sympy.printing.lambdarepr import IntervalPrinter
from sympy.testing.pytest import raises
def test_AlgIntPowers_01():
T = Poly(cyclotomic_poly(5))
zeta_pow = AlgIntPowers(T)
raises(ValueError, lambda: zeta_pow[-1])
for e in range(10):
a = e % 5
if a < 4:
c = zeta_pow[e]
assert c[a] == 1 and all(c[i] == 0 for i in range(4) if i != a)
else:
assert zeta_pow[e] == [-1] * 4
def test_AlgIntPowers_02():
T = Poly(x**3 + 2*x**2 + 3*x + 4)
m = 7
theta_pow = AlgIntPowers(T, m)
for e in range(10):
computed = theta_pow[e]
coeffs = (Poly(x)**e % T + Poly(x**3)).rep.to_list()[1:]
expected = [c % m for c in reversed(coeffs)]
assert computed == expected
def test_coeff_search():
C = []
search = coeff_search(2, 1)
for i, c in enumerate(search):
C.append(c)
if i == 12:
break
assert C == [[1, 1], [1, 0], [1, -1], [0, 1], [2, 2], [2, 1], [2, 0], [2, -1], [2, -2], [1, 2], [1, -2], [0, 2], [3, 3]]
def test_extract_fundamental_discriminant():
# To extract, integer must be 0 or 1 mod 4.
raises(ValueError, lambda: extract_fundamental_discriminant(2))
raises(ValueError, lambda: extract_fundamental_discriminant(3))
# Try many cases, of different forms:
cases = (
(0, {}, {0: 1}),
(1, {}, {}),
(8, {2: 3}, {}),
(-8, {2: 3, -1: 1}, {}),
(12, {2: 2, 3: 1}, {}),
(36, {}, {2: 1, 3: 1}),
(45, {5: 1}, {3: 1}),
(48, {2: 2, 3: 1}, {2: 1}),
(1125, {5: 1}, {3: 1, 5: 1}),
)
for a, D_expected, F_expected in cases:
D, F = extract_fundamental_discriminant(a)
assert D == D_expected
assert F == F_expected
def test_supplement_a_subspace_1():
M = DM([[1, 7, 0], [2, 3, 4]], QQ).transpose()
# First supplement over QQ:
B = supplement_a_subspace(M)
assert B[:, :2] == M
assert B[:, 2] == DomainMatrix.eye(3, QQ).to_dense()[:, 0]
# Now supplement over FF(7):
M = M.convert_to(FF(7))
B = supplement_a_subspace(M)
assert B[:, :2] == M
# When we work mod 7, first col of M goes to [1, 0, 0],
# so the supplementary vector cannot equal this, as it did
# when we worked over QQ. Instead, we get the second std basis vector:
assert B[:, 2] == DomainMatrix.eye(3, FF(7)).to_dense()[:, 1]
def test_supplement_a_subspace_2():
M = DM([[1, 0, 0], [2, 0, 0]], QQ).transpose()
with raises(DMRankError):
supplement_a_subspace(M)
def test_IntervalPrinter():
ip = IntervalPrinter()
assert ip.doprint(x**Rational(1, 3)) == "x**(mpi('1/3'))"
assert ip.doprint(sqrt(x)) == "x**(mpi('1/2'))"
def test_isolate():
assert isolate(1) == (1, 1)
assert isolate(S.Half) == (S.Half, S.Half)
assert isolate(sqrt(2)) == (1, 2)
assert isolate(-sqrt(2)) == (-2, -1)
assert isolate(sqrt(2), eps=Rational(1, 100)) == (Rational(24, 17), Rational(17, 12))
assert isolate(-sqrt(2), eps=Rational(1, 100)) == (Rational(-17, 12), Rational(-24, 17))
raises(NotImplementedError, lambda: isolate(I))

View File

@ -0,0 +1,474 @@
"""Utilities for algebraic number theory. """
from sympy.core.sympify import sympify
from sympy.ntheory.factor_ import factorint
from sympy.polys.domains.rationalfield import QQ
from sympy.polys.domains.integerring import ZZ
from sympy.polys.matrices.exceptions import DMRankError
from sympy.polys.numberfields.minpoly import minpoly
from sympy.printing.lambdarepr import IntervalPrinter
from sympy.utilities.decorator import public
from sympy.utilities.lambdify import lambdify
from mpmath import mp
def is_rat(c):
r"""
Test whether an argument is of an acceptable type to be used as a rational
number.
Explanation
===========
Returns ``True`` on any argument of type ``int``, :ref:`ZZ`, or :ref:`QQ`.
See Also
========
is_int
"""
# ``c in QQ`` is too accepting (e.g. ``3.14 in QQ`` is ``True``),
# ``QQ.of_type(c)`` is too demanding (e.g. ``QQ.of_type(3)`` is ``False``).
#
# Meanwhile, if gmpy2 is installed then ``ZZ.of_type()`` accepts only
# ``mpz``, not ``int``, so we need another clause to ensure ``int`` is
# accepted.
return isinstance(c, int) or ZZ.of_type(c) or QQ.of_type(c)
def is_int(c):
r"""
Test whether an argument is of an acceptable type to be used as an integer.
Explanation
===========
Returns ``True`` on any argument of type ``int`` or :ref:`ZZ`.
See Also
========
is_rat
"""
# If gmpy2 is installed then ``ZZ.of_type()`` accepts only
# ``mpz``, not ``int``, so we need another clause to ensure ``int`` is
# accepted.
return isinstance(c, int) or ZZ.of_type(c)
def get_num_denom(c):
r"""
Given any argument on which :py:func:`~.is_rat` is ``True``, return the
numerator and denominator of this number.
See Also
========
is_rat
"""
r = QQ(c)
return r.numerator, r.denominator
@public
def extract_fundamental_discriminant(a):
r"""
Extract a fundamental discriminant from an integer *a*.
Explanation
===========
Given any rational integer *a* that is 0 or 1 mod 4, write $a = d f^2$,
where $d$ is either 1 or a fundamental discriminant, and return a pair
of dictionaries ``(D, F)`` giving the prime factorizations of $d$ and $f$
respectively, in the same format returned by :py:func:`~.factorint`.
A fundamental discriminant $d$ is different from unity, and is either
1 mod 4 and squarefree, or is 0 mod 4 and such that $d/4$ is squarefree
and 2 or 3 mod 4. This is the same as being the discriminant of some
quadratic field.
Examples
========
>>> from sympy.polys.numberfields.utilities import extract_fundamental_discriminant
>>> print(extract_fundamental_discriminant(-432))
({3: 1, -1: 1}, {2: 2, 3: 1})
For comparison:
>>> from sympy import factorint
>>> print(factorint(-432))
{2: 4, 3: 3, -1: 1}
Parameters
==========
a: int, must be 0 or 1 mod 4
Returns
=======
Pair ``(D, F)`` of dictionaries.
Raises
======
ValueError
If *a* is not 0 or 1 mod 4.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
(See Prop. 5.1.3)
"""
if a % 4 not in [0, 1]:
raise ValueError('To extract fundamental discriminant, number must be 0 or 1 mod 4.')
if a == 0:
return {}, {0: 1}
if a == 1:
return {}, {}
a_factors = factorint(a)
D = {}
F = {}
# First pass: just make d squarefree, and a/d a perfect square.
# We'll count primes (and units! i.e. -1) that are 3 mod 4 and present in d.
num_3_mod_4 = 0
for p, e in a_factors.items():
if e % 2 == 1:
D[p] = 1
if p % 4 == 3:
num_3_mod_4 += 1
if e >= 3:
F[p] = (e - 1) // 2
else:
F[p] = e // 2
# Second pass: if d is cong. to 2 or 3 mod 4, then we must steal away
# another factor of 4 from f**2 and give it to d.
even = 2 in D
if even or num_3_mod_4 % 2 == 1:
e2 = F[2]
assert e2 > 0
if e2 == 1:
del F[2]
else:
F[2] = e2 - 1
D[2] = 3 if even else 2
return D, F
@public
class AlgIntPowers:
r"""
Compute the powers of an algebraic integer.
Explanation
===========
Given an algebraic integer $\theta$ by its monic irreducible polynomial
``T`` over :ref:`ZZ`, this class computes representations of arbitrarily
high powers of $\theta$, as :ref:`ZZ`-linear combinations over
$\{1, \theta, \ldots, \theta^{n-1}\}$, where $n = \deg(T)$.
The representations are computed using the linear recurrence relations for
powers of $\theta$, derived from the polynomial ``T``. See [1], Sec. 4.2.2.
Optionally, the representations may be reduced with respect to a modulus.
Examples
========
>>> from sympy import Poly, cyclotomic_poly
>>> from sympy.polys.numberfields.utilities import AlgIntPowers
>>> T = Poly(cyclotomic_poly(5))
>>> zeta_pow = AlgIntPowers(T)
>>> print(zeta_pow[0])
[1, 0, 0, 0]
>>> print(zeta_pow[1])
[0, 1, 0, 0]
>>> print(zeta_pow[4]) # doctest: +SKIP
[-1, -1, -1, -1]
>>> print(zeta_pow[24]) # doctest: +SKIP
[-1, -1, -1, -1]
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
"""
def __init__(self, T, modulus=None):
"""
Parameters
==========
T : :py:class:`~.Poly`
The monic irreducible polynomial over :ref:`ZZ` defining the
algebraic integer.
modulus : int, None, optional
If not ``None``, all representations will be reduced w.r.t. this.
"""
self.T = T
self.modulus = modulus
self.n = T.degree()
self.powers_n_and_up = [[-c % self for c in reversed(T.rep.to_list())][:-1]]
self.max_so_far = self.n
def red(self, exp):
return exp if self.modulus is None else exp % self.modulus
def __rmod__(self, other):
return self.red(other)
def compute_up_through(self, e):
m = self.max_so_far
if e <= m: return
n = self.n
r = self.powers_n_and_up
c = r[0]
for k in range(m+1, e+1):
b = r[k-1-n][n-1]
r.append(
[c[0]*b % self] + [
(r[k-1-n][i-1] + c[i]*b) % self for i in range(1, n)
]
)
self.max_so_far = e
def get(self, e):
n = self.n
if e < 0:
raise ValueError('Exponent must be non-negative.')
elif e < n:
return [1 if i == e else 0 for i in range(n)]
else:
self.compute_up_through(e)
return self.powers_n_and_up[e - n]
def __getitem__(self, item):
return self.get(item)
@public
def coeff_search(m, R):
r"""
Generate coefficients for searching through polynomials.
Explanation
===========
Lead coeff is always non-negative. Explore all combinations with coeffs
bounded in absolute value before increasing the bound. Skip the all-zero
list, and skip any repeats. See examples.
Examples
========
>>> from sympy.polys.numberfields.utilities import coeff_search
>>> cs = coeff_search(2, 1)
>>> C = [next(cs) for i in range(13)]
>>> print(C)
[[1, 1], [1, 0], [1, -1], [0, 1], [2, 2], [2, 1], [2, 0], [2, -1], [2, -2],
[1, 2], [1, -2], [0, 2], [3, 3]]
Parameters
==========
m : int
Length of coeff list.
R : int
Initial max abs val for coeffs (will increase as search proceeds).
Returns
=======
generator
Infinite generator of lists of coefficients.
"""
R0 = R
c = [R] * m
while True:
if R == R0 or R in c or -R in c:
yield c[:]
j = m - 1
while c[j] == -R:
j -= 1
c[j] -= 1
for i in range(j + 1, m):
c[i] = R
for j in range(m):
if c[j] != 0:
break
else:
R += 1
c = [R] * m
def supplement_a_subspace(M):
r"""
Extend a basis for a subspace to a basis for the whole space.
Explanation
===========
Given an $n \times r$ matrix *M* of rank $r$ (so $r \leq n$), this function
computes an invertible $n \times n$ matrix $B$ such that the first $r$
columns of $B$ equal *M*.
This operation can be interpreted as a way of extending a basis for a
subspace, to give a basis for the whole space.
To be precise, suppose you have an $n$-dimensional vector space $V$, with
basis $\{v_1, v_2, \ldots, v_n\}$, and an $r$-dimensional subspace $W$ of
$V$, spanned by a basis $\{w_1, w_2, \ldots, w_r\}$, where the $w_j$ are
given as linear combinations of the $v_i$. If the columns of *M* represent
the $w_j$ as such linear combinations, then the columns of the matrix $B$
computed by this function give a new basis $\{u_1, u_2, \ldots, u_n\}$ for
$V$, again relative to the $\{v_i\}$ basis, and such that $u_j = w_j$
for $1 \leq j \leq r$.
Examples
========
Note: The function works in terms of columns, so in these examples we
print matrix transposes in order to make the columns easier to inspect.
>>> from sympy.polys.matrices import DM
>>> from sympy import QQ, FF
>>> from sympy.polys.numberfields.utilities import supplement_a_subspace
>>> M = DM([[1, 7, 0], [2, 3, 4]], QQ).transpose()
>>> print(supplement_a_subspace(M).to_Matrix().transpose())
Matrix([[1, 7, 0], [2, 3, 4], [1, 0, 0]])
>>> M2 = M.convert_to(FF(7))
>>> print(M2.to_Matrix().transpose())
Matrix([[1, 0, 0], [2, 3, -3]])
>>> print(supplement_a_subspace(M2).to_Matrix().transpose())
Matrix([[1, 0, 0], [2, 3, -3], [0, 1, 0]])
Parameters
==========
M : :py:class:`~.DomainMatrix`
The columns give the basis for the subspace.
Returns
=======
:py:class:`~.DomainMatrix`
This matrix is invertible and its first $r$ columns equal *M*.
Raises
======
DMRankError
If *M* was not of maximal rank.
References
==========
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*
(See Sec. 2.3.2.)
"""
n, r = M.shape
# Let In be the n x n identity matrix.
# Form the augmented matrix [M | In] and compute RREF.
Maug = M.hstack(M.eye(n, M.domain))
R, pivots = Maug.rref()
if pivots[:r] != tuple(range(r)):
raise DMRankError('M was not of maximal rank')
# Let J be the n x r matrix equal to the first r columns of In.
# Since M is of rank r, RREF reduces [M | In] to [J | A], where A is the product of
# elementary matrices Ei corresp. to the row ops performed by RREF. Since the Ei are
# invertible, so is A. Let B = A^(-1).
A = R[:, r:]
B = A.inv()
# Then B is the desired matrix. It is invertible, since B^(-1) == A.
# And A * [M | In] == [J | A]
# => A * M == J
# => M == B * J == the first r columns of B.
return B
@public
def isolate(alg, eps=None, fast=False):
"""
Find a rational isolating interval for a real algebraic number.
Examples
========
>>> from sympy import isolate, sqrt, Rational
>>> print(isolate(sqrt(2))) # doctest: +SKIP
(1, 2)
>>> print(isolate(sqrt(2), eps=Rational(1, 100)))
(24/17, 17/12)
Parameters
==========
alg : str, int, :py:class:`~.Expr`
The algebraic number to be isolated. Must be a real number, to use this
particular function. However, see also :py:meth:`.Poly.intervals`,
which isolates complex roots when you pass ``all=True``.
eps : positive element of :ref:`QQ`, None, optional (default=None)
Precision to be passed to :py:meth:`.Poly.refine_root`
fast : boolean, optional (default=False)
Say whether fast refinement procedure should be used.
(Will be passed to :py:meth:`.Poly.refine_root`.)
Returns
=======
Pair of rational numbers defining an isolating interval for the given
algebraic number.
See Also
========
.Poly.intervals
"""
alg = sympify(alg)
if alg.is_Rational:
return (alg, alg)
elif not alg.is_real:
raise NotImplementedError(
"complex algebraic numbers are not supported")
func = lambdify((), alg, modules="mpmath", printer=IntervalPrinter())
poly = minpoly(alg, polys=True)
intervals = poly.intervals(sqf=True)
dps, done = mp.dps, False
try:
while not done:
alg = func()
for a, b in intervals:
if a <= alg.a and alg.b <= b:
done = True
break
else:
mp.dps *= 2
finally:
mp.dps = dps
if eps is not None:
a, b = poly.refine_root(a, b, eps=eps, fast=fast)
return (a, b)