switching to high quality piper tts and added label translations
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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',
|
||||
]
|
||||
+676
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,882 @@
|
||||
"""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):
|
||||
return all(f.is_Rational or f.is_Pow and
|
||||
f.base.is_Rational and (2*f.exp).is_Integer and f.is_extended_real
|
||||
for t in Add.make_args(p) for f in Mul.make_args(t))
|
||||
|
||||
|
||||
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
|
||||
v = ex
|
||||
ex -= x
|
||||
while 1:
|
||||
ex1 = _separate_sq(ex)
|
||||
if ex1 is ex:
|
||||
return _choose_factor(factor_list(ex)[1], x, v)
|
||||
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
@@ -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
|
||||
@@ -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)
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
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
|
||||
|
||||
def _canonicalize(f):
|
||||
_, f = f.primitive()
|
||||
if f.LC() < 0:
|
||||
f = -f
|
||||
return f
|
||||
|
||||
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)
|
||||
|
||||
g = _canonicalize(g)
|
||||
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]
|
||||
|
||||
f = _canonicalize(f)
|
||||
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))
|
||||
@@ -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])]
|
||||
+143
@@ -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
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
"""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.ntheory.generate import nextprime
|
||||
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_issue_26903():
|
||||
p1 = nextprime(10**16) # greater than 10**15
|
||||
p2 = nextprime(p1)
|
||||
assert sqrt(p1**2*p2).is_Pow # square not extracted
|
||||
zero = sqrt(p1**2*p2) - p1*sqrt(p2)
|
||||
assert minimal_polynomial(zero, x) == x
|
||||
assert minimal_polynomial(sqrt(2) - zero, x) == x**2 - 2
|
||||
|
||||
|
||||
def test_issue_8353():
|
||||
assert minimal_polynomial(exp(3*I*pi, evaluate=False), x) == x + 1
|
||||
assert minimal_polynomial(Pow(8, S(1)/3, evaluate=False), x
|
||||
) == x - 2
|
||||
|
||||
|
||||
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))
|
||||
+752
@@ -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))
|
||||
+202
@@ -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)
|
||||
+296
@@ -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)
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
"""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.domains import QQ
|
||||
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]
|
||||
|
||||
|
||||
def test_issue_27798():
|
||||
# https://github.com/sympy/sympy/issues/27798
|
||||
a, b = CRootOf(49*x**3 - 49*x**2 + 14*x - 1, 2), CRootOf(49*x**3 - 49*x**2 + 14*x - 1, 0)
|
||||
assert primitive_element([a, b], polys=True)[0].primitive()[0] == 1
|
||||
assert primitive_element([a, b], polys=True, ex=True)[0].primitive()[0] == 1
|
||||
|
||||
f1, f2 = QQ.algebraic_field(a), QQ.algebraic_field(b)
|
||||
f3 = f1.unify(f2)
|
||||
assert f3.mod.primitive()[0] == 1
|
||||
assert Poly(x, x, domain=f1) + Poly(x, x, domain=f2) == Poly(2*x, x, domain=f3)
|
||||
+113
@@ -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))
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user