AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning
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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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',
|
||||
]
|
||||
@@ -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))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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])]
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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