123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- """Computing integral bases for number fields. """
- from sympy.polys.polytools import Poly
- from sympy.polys.domains.integerring import ZZ
- from sympy.polys.domains.rationalfield import QQ
- from sympy.polys.polyerrors import CoercionFailed
- 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 a monic irreducible
- polynomial *T* over :ref:`ZZ`. This computes an integral basis and the
- discriminant for the field $K = \mathbb{Q}[x]/(T(x))$.
- 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, theta
- >>> T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
- >>> K = QQ.algebraic_field((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/2 + theta/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`
- The irreducible monic polynomial over :ref:`ZZ` defining the number
- field.
- 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.*
- """
- if T.domain == QQ:
- try:
- T = Poly(T, domain=ZZ)
- except CoercionFailed:
- pass # Let the error be raised by the next clause.
- if ( not T.is_univariate
- or not T.is_irreducible
- or not T.is_monic
- or not T.domain == ZZ):
- raise ValueError('Round 2 requires a monic irreducible univariate polynomial over ZZ.')
- 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(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
|