785 lines
23 KiB
Python
785 lines
23 KiB
Python
"""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, 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 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.rep))
|
|
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
|