r"""
Fake p-adic completions
=======================

**p-adic fields as completions of absolute number fields**


This module realizes a class ``FakepAdicCompletion`` which represents a `p`-adic
number field `K`. Internally, the `p`-adic number field is represented by
a number field `K_0` together with a discrete valuation `v_K` on `K_0` which
extends the `p`-adic valuation `v_p` on `\mathbb{Q}`, such that `K` is the
completion of `K_0` at `v_K`.

Our main goal is to be able to compute *weak* `p`-*adic Galois extensions* of
`p`-adic number fields of large degree. We want to:

- compute efficiently with general extensions of `\mathbb{Q}_p` of high
  ramification index (up to several hundreds)
- obtain provably correct results.

Both objectives seem difficult to reach with the existing functionality of
``Sage``. Therefore, we decided to represent a `p`-adic number field `K`
by a pair `(K_0, v_K)`, where `K_0` is an *absolute* number field and `v_K` is a discrete
valuation on `K_0` extending the`p`-adic valuation `v_p` on `\mathbb{Q}`, for some
prime `p`. A more systematic realization of this idea, Julian Rueth's Sage package
``completion``,  should soon be part of Sage.

The first advantage of our approach (which adresses the first point above) is
that arithmetic in absolute number fields is reasonably fast in ``Sage``.
Moreover, since there are many pairs `(K_0, v_K)` which have the same completion,
we can choose `K_0` in such a way that certain operations we use heavily (like
evaluation of `v_K`) can be done very efficiently. At the moment, we choose
`(K_0,v_K)` such that

- `v_K` is the unique extension of `v_p` to `K_0`,
- `K_0/\mathbb{Q}` is generated by a uniformizer `\pi_K` of `v_K`.

For instance, these assumptions allow us to easily write down a `p`-integral
basis for `K_0/\mathbb{Q}` which is also an integral basis for `K/\mathbb{Q}_p`.
We can also choose `K_0` such that the defining polynomial has small coefficients.

The second advantage is that, since for most of our needs we do not really need
the `p`-adic number field `K` explicitly, we can instead work with the
*henselization* `K^h` of `(K_0,v_K)` which is an *exact* field: an element of `K^h`
is uniquely determined by a finite amount of data, namely its minimal polynomial
over `\mathbb{Q}` and a sufficiently precise approximation by an element of `K_0`.
Moreover, `K^h` only depends on the `p`-adic number field `K` but not
on our particular choice of `K_0`.

The main drawback of our approach is that morphisms between two `p`-adic number
fields `K` and `L` are somewhat difficult to realize: the problem is
that for our particular choice of the underlying number fields `K_0` and `L_0`,
there may not exist a nonzero morphism `K_0\to L_0` even if `K` is a subfield of
`L`.



AUTHORS:

- Stefan Wewers (2017-08-24): initial version


EXAMPLES:



TO DO:

- one should try to always find a totally ramified extension (but it is not
  so clear how to do that)



"""

#*****************************************************************************
#       Copyright (C) 2017 Stefan Wewers <stefan.wewers@uni-ulm.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#                  https://www.gnu.org/licenses/
#*****************************************************************************


from sage.all import SageObject, ZZ, QQ, NumberField, GaussValuation, PolynomialRing, Polynomial, Integer, matrix, IntegerModRing, mod, Infinity, prod, lcm, vector, GF
from sage.geometry.newton_polygon import NewtonPolygon
from sage.rings.valuation.limit_valuation import LimitValuation


class FakepAdicCompletion(SageObject):
    r"""
    Return the completion of a number field at a `p`-adic valuation.

    INPUT:

    - ``K0`` -- an absolute number field,
    - ``vK`` -- a `p`-adic valuation on ``K0``.

    We assume that `v_K` is the unique extension of the `p`-adic extension `v_p`
    on `\mathbb{Q}` to `K_0`, and that the standard generator `\pi_K` of
    `K_0/\mathbb{Q}` is a uniformizer for `v_K`. If this is not the case, an
    error is raised.

    """
    def __init__(self, K0, vK):
        assert K0.is_absolute(), "K0 must be an absolute number field"
        assert vK.domain() == K0, "K0 must be the domain of vK"
        p = vK.p()
        # we want vK to be normalized such that vK(p)=1
        vK = vK/vK(p)
        assert vK(p) == 1
        R = PolynomialRing(K0, 'x')
        x = R.gen()
        n = K0.degree()
        if n > 1:
            piK = K0.gen()
            e = ZZ(1/vK(vK.uniformizer()))
            F = vK.residue_field() # should be a finite field, therefore:
            f = F.cardinality().log(F.characteristic()) # should be the absolute degree of F
            assert vK(piK) == 1/e, "the generator of K must be a uniformizer for vK"
            P = K0.polynomial()
            G = P(x+piK).shift(-1)
            S = matrix(QQ, n)
            for i in range(n):
                S[i,i] = p**(i/e).floor()
                # the matrix S has the property that S^(-1)*a.matrix()*S is p-integral,
                # for an element a in O_K; this follows from the fact that
                # pi^i/p^(i/e).floor(), i=0,..,n-1, is a p-integral basis
            is_Qp = False
        else:
            K0 = QQ
            piK = K0(1)
            e = ZZ(1)
            f = ZZ(1)
            P = x-1
            G = R(1)
            S = matrix(K0, 1, [1])
            is_Qp = True
        assert n == e*f
        assert n == P.degree()
        self._number_field = K0
        self._v_p = QQ.valuation(p)
        self._valuation = vK
        self._p = p
        self._uniformizer = piK
        self._degree = n
        self._ramification_degree = e
        self._inertia_degree = f
        self._polynomial = P
        self._G = G
        self._S = S
        self._is_Qp = is_Qp

    def __repr__(self):
        return "%s-adic completion of %s"%(self.p(), self.number_field())


    def number_field(self):
        r"""
        Return the number field representing this p-adic extension.
        """
        return self._number_field


    def base_valuation(self):
        r"""
        Return the `p`-adic valuation.
        """
        return self._v_p


    def valuation(self):
        r"""
        Return the valuation on the underlying number field of this p-adic extension.
        """
        return self._valuation


    def normalized_valuation(self):
        r"""
        Return the normalized valuation on this p-adic field.

        Here *normalized* means that the valuation takes the value `1` on a
        uniformizer.

        """
        v = self.valuation().scale(self.absolute_ramification_degree())
        assert v(v.uniformizer()) == 1
        return v


    def p(self):
        r"""
        Return the prime `p`.
        """
        return self._p


    def uniformizer(self):
        r"""
        Return the standard unifomizer of this p-adic extension.
        """
        return self._uniformizer


    def generator(self):
        r"""
        Return the standard generator of this p-adic extension.
        """
        return self._generator


    def absolute_degree(self):
        r"""
        Return the degree of this p-adic field as an extension of `\mathbb{Q}_p`.
        """
        return self._degree


    def degree(self):
        r"""
        Return the degree of this p-adic field as an extension of `\mathbb{Q}_p`.
        """
        return self._degree


    def ramification_degree(self):
        r"""
        Return the absolute ramification degree of this p-adic extension.
        """
        return self._ramification_degree


    def absolute_ramification_degree(self):
        r"""
        Return the absolute ramification degree of this p-adic extension.
        """
        return self._ramification_degree


    def absolute_inertia_degree(self):
        r"""
        Return the absolute inertia degree of this p-adic extension.
        """
        return self._inertia_degree


    def inertia_degree(self):
        r"""
        Return the absolute inertia degree of this p-adic extension.
        """
        return self._inertia_degree


    def polynomial(self):
        r"""
        Return the minimal polynomial of the standard generator of `K`.
        """
        return self._polynomial


    def is_Qp(self):
        r"""
        Return ``True`` if this is the padic-completion of the field of rational numbers.
        """
        return self._is_Qp


    def extension(self, f, embedding=False):
        r"""
        Return a `p`-adic extension such that ``f`` has a root in an unramified
        extension of it.

        INPUT:

        - ``f`` -- a monic univariate polynomial over the number field `K_0`
          underlying this p-adic extension `K`, which is irreducible over `K`.
        - ``embedding`` -- a boolean (default: ``False``)

        OUTPUT:

        A `p`-adic extension `L` of `K` such that ``f`` has a root
        in an unramified extension of `L`, or (if ``embedding`` is ``True``)
        the pair `(L,\phi)`, where `\phi` is the canonical embedding of
        `K` into `L`.

        If `K_0` is the underlying number field, then `f\in K_0[x]` is irreducible
        over the completion `K`; the resulting finite extension `L/K`
        is a subextension of the extension of `K`

        .. MATH::

                K[x]/(f).

        However, the number field `L_0` underlying `L` is in general not
        equal to `K_0[x]/(f)`, and there may not exist any embedding of `K_0`
        into `L_0`.

        """
        # print("entering extension with ")
        # print("K = ", self)
        # print("f = ", f)
        # print()

        K0 = self.number_field()
        assert K0.has_coerce_map_from(f.parent().base_ring())
        f = f.change_ring(K0)

        vK = self.valuation()
        if embedding:
            raise NotImplementedError

        V = vK.mac_lane_approximants(f)
        assert len(V) == 1, "f is not irreducible"
        v = LimitValuation(V[0], f)
        pix = v.uniformizer()
        r = v(pix)
        for m in range(2,10):
            pix1 = pix.map_coefficients(lambda c:self.reduce(c, m))
            if v(pix1) == r:
                break
        assert m < 9, "m too small"
        # now pix1 is a polynomial over `K_0` which maps to a uniformizer for the
        # unique extension of vK to the relative extension L=K[x]/(f)/K
        g = self.characteristic_polynomial(f, pix1)
        # now g is the relative characteristic polynomial of the image pi of pix1 in L
        # we need the minimal polynomial, so we do a square free factorization
        g = g.squarefree_decomposition()[0][0]
        # now we compute the absolute characteristic polynomial of a root
        # of g

        done = False
        N = m+5
        while not done:
            Pmod = self.characteristic_polynomial_mod(g, N)
            # this is very heuristic; there should be a conclusive test whether
            # the result is correct, or the choice of the precision should be made correctly and
            # optimal from the start
            P = Pmod.map_coefficients(lambda c:c.lift(), QQ)
            P = P.squarefree_decomposition()[0][0]
            L0 = NumberField(P, 'pi%s'%P.degree())
            V = QQ.valuation(self.p()).extensions(L0)
            vL = V[0]
            e = ZZ(1/vL(vL.uniformizer()))
            f = vL.residue_field().degree()
            if 1/e == r:
                if len(V) == 1:
                    return FakepAdicCompletion(L0, vL)
                else:
                    QQp = FakepAdicCompletion(QQ, self.base_valuation())
                    g = QQp.approximate_irreducible_factor(P)
                    return QQp.extension(g)
            N = N + 5
            # this is now very cumbersome and should we reworked at some point


    def ramified_extension(self, n, embedding=False):
        r"""
        Return a purely ramified extension with given ramification degree.

        INPUT:

        - ``n`` -- a positive integer
        - ``embedding`` -- a boolean (default: ``False``)

        OUTPUT:

        A finite extension `L` of this p-adic field `K` such that `L/K` is
        purely ramified, of degree ``n``. If ``embedding`` is ``True`` then the
        pair `(L,\phi)` is returned, where `\phi:K\to L` is the canonical embedding.

        """
        P = self.polynomial()
        x = P.parent().gen()
        if self.is_Qp():
            P = x**n - self.p()
        else:
            P = P(x**n)
        L0 = NumberField(P, 'pi'+str(P.degree()), maximize_at_primes=[])
        vL = self.base_valuation().extension(L0)
        return FakepAdicCompletion(L0, vL)


    def weak_splitting_field(self, F):
        r"""
        Return the weak splitting field of a list of polynomials.

        INPUT:

        - ``F`` -- a polynomial over the underlying number field `K_0`,
          or a list of polynomials

        OUTPUT:

        A weak splitting field `L/K` of ``F``.

        This means that `F` splits over an unramified extension of `L`.

        Note:

        This function works at the moment only for the base field `\mathbb{Q}_p`.

        TODO:

        The following trick should give a massive improvement for large examples:
        Instead of calling ``K.approximate_factorization(F)`` one should calling
        a new version of ``K.approximate_irreducible_factor(F, *options*)``.

        The function ``approximate_irreducible_factor(..)`` would do a
        MacLane approximation with ``require_maximal_degree=True``. From the
        resulting v's one can then select (according to the options) one which,
        after enough MacLane steps, gives an approximate irreducible factor.

        The point is that the test for being an approximate factor seems to be
        the most time consuming for large examples. A further speedup could be
        obtained by

        - either finding a more clever way to test,
        - or by simply doing several MacLane steps before testing.

        The options should be set such that factors are chosen accoring to the
        following rules:

        1. one prefers purely ramified factors over factors with inertia
        2. one prefers purely wild factors over mixed factors
        3. one prefers mixed factors over tame factors

        Note that for tame factors the inertia may be ignored, hence they
        may be considered as purely ramified. However, it is better to leave
        them until the end, because doing a tame extension is almost trivial.
        It is not clear to me whether a purely wild factor with inertia is better
        than a mixed unramified factor.

        EXAMPLES:

        The following example created an error in a previous version ::

            sage: from mclf import *
            sage: v_2 = QQ.valuation(2)
            sage: Q2 = FakepAdicCompletion(QQ, v_2)
            sage: R.<x> = QQ[]
            sage: f = x^12 + 192*x^9 - 5352*x^6 + 33344*x^3 - 293568
            sage: L = Q2.weak_splitting_field(f)
            sage: L.ramification_degree()
            4

        Check that non-integral polynomials are allowed as well ::

            sage: Q2.weak_splitting_field(2*x^2 + 1)
            2-adic completion of Number Field in pi2 with defining polynomial x^2 + 2

        """
        if not isinstance(F, Polynomial):
            F = prod(F).radical()
        else:
            F = F.radical()
        K = self
        assert F.parent().base_ring() == QQ, "For the time being, F has to be defined over QQ"
        if F.is_constant():
            return K
        v0 = GaussValuation(F.parent(), self.valuation())
        F = v0.monic_integral_model(F)[0](F).monic()
        first_step = True
        while True:
            g = K.approximate_irreducible_factor(F)
            # here the base field of F needs to coerce into the number
            # field K0 underlying K. When we replace K by an extension, we can
            # guaranteee that this holds only if the base field of F is QQ
            if isinstance(g, Integer):
                e = g
                # e is the tame ramification index needed to obtain a weak
                # splitting field of F
                if e == 1:
                    return K
                else:
                    return K.ramified_extension(e)
            else:
                # now g is an approximate irreducible factor of F
                # which will give a wildly ramified extension of K
                if first_step and F.degree() == g.degree():
                    # it is often advantagous to replace F by the defining
                    # polynomial of the extension generated by F, because it
                    # is close to being Eisenstein
                    K = K.extension(g)
                    F = K.polynomial()
                else:
                    K = K.extension(g)
            first_step = False

#-----------------------------------------------------------------------------

#                   auxiliary functions

    def reduce(self, a, N):
        r"""
        Return an approximation of ``a`` which is reduced modulo `p^N`.

        INPUT:

        - ``a`` -- an element of the underlying number field `K`
        - ``N`` -- a positive Integer

        OUTPUT: an element `\tilde{a}` of `K` which is congruent to `a` modulo `p^N`,
        and whose representation in terms of the canonical integral basis of
        `K` has coefficents of the form `c/p^m`, with `0\leq  c < p^N`
        and `m\geq 0`.

        """
        v_p = self.base_valuation()
        p = self.p()

        if self.is_Qp():
            # a should be a rational number
            m = max(-v_p(a), 0)
            b = a*p**m
            q = p**(N+m)
            return QQ(mod(b, q).lift()*p**(-m))

        v = self.vector(a)
        vt = []
        for i in range(len(v)):
            a = v[i]
            m = max(-v_p(a), 0)
            b = a*p**m
            q = p**(N+m)
            at = mod(b, q).lift()*p**(-m)
            vt.append(at)
        return self.element_from_vector(vector(vt))


    def reduce_polynomial(self, f, N):

        R = f.parent()
        return R([self.reduce(f[i], N) for i in range(f.degree()+1)])


    def reduce_rational_number(self, a, N):
        r"""
        Return an approximation of ``a`` which is reduced modulo `p^N`.

        INPUT:

        - ``a`` -- a rational number
        - ``N`` -- a positive Integer

        OUTPUT: an element `\tilde{a}` of `\mathbb{Q}` which is congruent to
        `a` modulo `p^N`, of the form `c/p^m`, with `0\leq  c < p^N`
        and `m\geq 0`.

        """
        v_p = self.base_valuation()
        p = self.p()
        assert a in QQ
        # a should be a rational number
        m = max(-v_p(a), 0)
        b = a*p**m
        q = p**(N+m)
        return QQ(mod(b, q).lift()*p**(-m))


    def matrix(self, a, integral_basis="standard"):
        r"""
        Return the matrix representing the element ``a``.

        INPUT:

        - ``a`` -- an element of the underlying number field of this p-adic extension
        - ``integral_basis`` -- a string (default: "standard")

        OUTPUT:

        The matrix representing the element `a` of the underlying number field
        `K`, with respect to the canonical `p`-integral basis.

        """
        assert a in self.number_field(), "a must be an element of the underlying number field"
        if self.is_Qp():
            return matrix([QQ(a)])
        else:
            S = self.base_change_matrix(integral_basis)
            a = self.number_field()(a)
            return S**(-1)*a.matrix()*S


    def vector(self, a, integral_basis="standard"):
        r"""
        Return the vector corresponding to an element of the underlying number field.

        INPUT:

        - ``a`` -- an element of the number field `K_0` underlying this `p`-adic number field
        - ``integral_basis`` -- a string (default: "standard")

        OUTPUT:

        the vector of coefficients corresponding to the representation
        of `a` as a linear combination of an integral basis of `K`.

        If ``integral_basis`` is "standard" then we use the integral basis

        .. MATH::

            p^\lceil i/e \rceil \pi^i, i=0,\ldots,n,

        where `\pi` is the standard uniformizer of `L`, `e` is the absolute
        ramification degree and `n` the absolute degree of `K`.

        If it is ``mixed`` then we use  the integral basis

        .. MATH::

                \pi^i \alpha_j,   i=0,\ldots,e-1, j=0,\ldots,n/e-1,

        where `\alpha_j` is an approximation of an integral basis of the maximal
        unramified subfield.

        """
        if self.is_Qp():
            return vector([QQ(a)])
        else:
            a = self.number_field()(a)
            S = self.base_change_matrix(integral_basis)
            return S*a.vector()


    def element_from_vector(self, v, integral_basis="standard"):
        r"""
        Return the element corresponding to a given vector.

        INPUT:

        - ``v`` -- a vector of rational numbers with length equal to the degree of `K`
        - ``integral_basis`` -- a string (default: "standard")

        OUTPUT:

        the linear combination of the canonical integral basis of `K`
        corresponding to `v`.

        """
        S = self.base_change_matrix(integral_basis)
        return self.number_field()(list(S**(-1)*v))


    def base_change_matrix(self, integral_basis="standard", precision=20):
        r"""
        Return the base change matrix to an integral basis.

        INPUT:

        - ``integral_basis`` -- a string (default: "standard")
        - ``precision`` -- a positive integer

        OUTPUT:

        An invertible `(n,n)`-matrix `S` over `\mathbb{Q}` with the following
        property: for an element `a` of `K_0`, the vector ``S*a.vector()``
        gives the representation of `a` as a linear combination of ``integral_basis``.

        TODO:

        * clarify the role of  ``precision`` in this function

        """
        if integral_basis == "standard":
            return self._S

        n = self.degree()
        e = self.ramification_degree()
        m = self.inertia_degree()
        S = matrix(QQ, n)
        pi = self.uniformizer()
        alpha = self.integral_basis_of_unramified_subfield(precision)
        for i in range(e):
            for j in range(m):
                k = i + e*j
                alpha_k = (pi**i*alpha[j]).vector()
                for l in range(n):
                    S[k, l] = alpha_k[l]
        T = S**(-1)
        T_reduced = matrix(QQ, n)
        for i in range(n):
            for j in range(n):
                T_reduced[i,j] = self.reduce_rational_number(T[i,j], precision)
        assert T_reduced.is_invertible(), "T_reduced= %s\nmust be invertible; T = %s\nprecision = %s"%(T_reduced, T, precision)
        return T_reduced


    def integral_basis_of_unramified_subfield(self, precision=5):
        r"""
        Return an (approximate) integral basis of the maximal unramified subfield.

        INPUT:

        - ``precison`` -- a positive integer

        OUTPUT:

        A list `\alpha=(\alpha_0,\ldots,\alpha_{m-1})` of elements of `K_0`
        which approximate (with precision `p^N` an integral basis of the
        maximal unramified subfield.

        """
        if not hasattr(self, "_integral_basis_of_unramified_subfield") or precision > 5:
            m = self.inertia_degree()
            if m == 1:
                return [QQ.one()]

            p = self.p()
            fb = GF(p**m, 'zeta').polynomial()
            f = fb.change_ring(self.number_field())
            fx = f.derivative()
            vK = self.valuation()
            k = vK.residue_field()
            zetab = fb.change_ring(k).roots()[0][0]
            assert fb(zetab) == 0
            zeta = vK.lift(zetab)
            while vK(f(zeta)) <= precision:
                zeta = zeta - f(zeta)/fx(zeta)
                zeta = self.reduce(zeta, precision +1)
            # now zeta is an approximate generator of the maximal unramified subfield
            self._integral_basis_of_unramified_subfield = [self.reduce(zeta**i, precision +1) for i in range(m)]
        return self._integral_basis_of_unramified_subfield


    def minpoly_over_unramified_subextension(self, N):
        r"""
        Return the minimal polynomial of the standard uniformizer of this `p`-adic
        number field `K`, relative to the maximal unramified subfield,
        as a polynomial over `K` itself.

        INPUT:

        - ``N`` -- a positive integer

        OUTPUT:

        A polynomial `P` over the number field `K_0` underlying `K`. `P` is an
        approximation (with precision ``N``) of the minimal polynomial
        of the standard uniformizer `\pi` of `K`, relative to the maximal
        unramified subfield `K_{nr}` of `K`. Moreover, `P(\pi)=0` holds exactly.

        If the approximation is sufficient (note: this still has to be made
        precise) then `P` has a root `\pi_1` in `K` which is also a uniformizer of `K`, and `K = K_{nr}[\pi_1]`.

        NOTE:

        To check that `P` is ok, it should suffice to see that `P` is "Eisenstein
        over `K_nr`" and has a root in `K`. Unfortunately, the coefficient will
        likely not lie in `K_{nr}` exactly, and so this criterion probably makes
        no sense.

        """
        K0 = self.number_field()
        R = PolynomialRing(K0, 'x')
        x = R.gen()
        e = self.ramification_degree()
        m = self.inertia_degree()
        if m == 1:
            return self.polynomial().change_ring(K0)

        zeta = self.integral_basis_of_unramified_subfield(N)
        S = self.base_change_matrix(precision=N, integral_basis="mixed")
        P = x**e - sum( sum(S[e,i+e*j]*zeta[j] for j in range(m))*x**i for i in range(e))
        return self.reduce_polynomial(P, N)


#------------------------------------------------------------------------------

    def simplify_irreducible_polynomial(self, f):
        r"""
        Return a simpler polynomial generating the same extension.

        INPUT:

        - ``f`` -- an univariate polynomial over the underlying number field `K`
          which is integral and irreducible over `\hat{K}`

        OUTPUT:

        A polynomial `g` over `K` which is irreducible over `\hat{K}`,
        and which generates the same extension of `\hat{K}` as `f`.

        """
        R = f.parent()
        x = R.gen()
        assert R.base_ring() == self.number_field(), "f must be defined over K"
        # first we see if we can normalize f such that the unique slope of the
        # Newton polygon is >-1 and <=0.
        vK = self.valuation()
        NP = NewtonPolygon([(i, vK(f[i])) for i in range(f.degree()+1)])
        slopes = NP.slopes(repetition=False)
        assert len(slopes) == 1, "f is not irreducible over the completion!"
        s = slopes[0]
        assert s <= 0, "f is not integral"
        if s <= -1:
            m = (-s).floor()
            pi = self.uniformizer()
            f = f(pi**m*x).monic()
        # Now we simplify the coefficients of f
        N = vK(f[0]).ceil() + 1
        n = f.degree()
        while True:
            g = R([self.reduce(f[i], N) for i in range(n+1)])
            if g.is_squarefree() and self.is_approximate_irreducible_factor(g, f):
                return g
            else:
                N = N + 1


    def is_approximate_irreducible_factor(self, g, f, v=None):
        r"""
        Check whether ``g`` is an approximate irreducible factor of ``f``.

        INPUT:

        - ``g``: univariate polynomial over the underlying number field `K_0`
        - ``f``: univariate polynomial over `K_0`
        - ``v``: a MacLane valuation on `K_0[x]` approximating ``g``, or ``None``;
          here *approximating* means that ``LimitValuation(v, g)`` is well-defined.

        Output: True if ``g`` is an approximate irreducible factor of f, i.e.
        if g is irreducible over `K` and Krasner's condition is satified,
        If true, the stem field of ``g`` over `K` is a subfield of the
        splitting field of ``f`` over `K`.

        Her we say that *Krasner's Condition* holds if for some root `\alpha`
        of `g` there exists a root `\beta` of `f` such that `\alpha` is `p`-adically
        closer to `\beta` than to any other root of `g`.

        Note that if `\deg(g)=1` then the condition is nontrivial, even though
        the conclusion from Krasner's Lemma is trivial.

        """
        K = self.number_field()
        vK = self.valuation()
        assert K.has_coerce_map_from(f.parent().base_ring())
        f = f.change_ring(K)
        R = f.parent()
        assert K.has_coerce_map_from(g.parent().base_ring())
        g = R(g)
        assert g.is_monic(), 'g has to be monic'
        f = f.monic()
        if g == f:
            return True
        x = R.gen()

        if g.degree() == 1:             # the case deg(g)=1 is different
            alpha = -g[0]               # the unique root of g
            F = f(x+alpha)
            if F[0] == 0:               # alpha is an exact root of f
                return True
            np_f = NewtonPolygon([(i, vK(F[i])) for i in range(F.degree()+1)])
            # the slopes correspond to vK(alpha-beta), beta the roots of f
            return ( len(np_f.vertices())>1 and np_f.vertices()[1][0]==1 and
                    np_f.slopes()[0]<0 )

        # now deg(g)>1
        if v == None:
            V = vK.mac_lane_approximants(g)
            if len(V) != 1:
                return False   # g is not irreducible
            v = V[0]
        v = LimitValuation(v, g)
        # if v(f) == Infinity:
        if (v._G).divides(f):
            return True
        # the valuation v has the property
        #        v(h)=vK(h(alpha))
        # for all h in K[x], where alpha is a root of g

        S = PolynomialRing(R, 'T')
        G = g(x + S.gen()).shift(-1)
        np_g = NewtonPolygon([(i, v(G[i])) for i in range(G.degree()+1)])
        # the slopes of np_g correspond to the valuations of
        # alpha-alpha_i, where alpha_i runs over all roots of
        # g distinct from alpha

        F = f(x + S.gen())
        np_f = NewtonPolygon([(i, v(F[i])) for i in range(F.degree()+1)])
        # the slopes of np_f correspond to the valuations of
        # alpha-beta, where beta runs over all roots of f

        result = min(np_g.slopes()) > min(np_f.slopes())
        # this is true if there is a root beta of f
        # such that vK(alpha-beta)>vK(alpha-alpha_i) for all i
        return result


    def approximate_factorization(self, f, only_ramified_factors=False):
        r"""
        Return a list of approximate irreducible factors of ``f``.

        INPUT:

        - ``f`` -- a squarefree univariate polynomial over `K_0`, the number field
          underlying this p-adic field `K`
        - ``only_ramified_factors`` -- boolean (default: ``False``)

        OUTPUT: a list of pairs `(g,e)`, where `g` is an approximate irreducible
        factor of `f` over `K` and `e>=1`.
        The pairs `(g,e)` are in bijection with the irreducible factors of `f` over
        `K`. If `(g,e)` corresponds to the irreducible factor `f_i' then `g`
        and `f_i` generate the same extension of `K`. In particular, `f_i` and
        `g` have the same degree. Moreover `e` is the ramification degree of the
        extension of `K` generated by `g` (or `f_i`).

        If ``only_ramified_factors`` is ``True`` then all pairs `(g,e)` with
        `e=1` are omitted.

        At the moment we have to assume that `f` is monic and integral
        with respect to `v_K` (and hence all roots have nonnegative valuation).

        """
        assert not f.is_constant(), 'f must not be constant'
        K = self.number_field()
        assert K.has_coerce_map_from(f.parent().base_ring())
        f = f.change_ring(K)
        vK = self.valuation()
        V = vK.mac_lane_approximants(f, assume_squarefree=True, require_maximal_degree=True)
        if only_ramified_factors:
            V = [v for v in V if v.E() > 1]
        V = [LimitValuation(v, f) for v in V]
        factor_list = []
        for v in V:
            while True:
                g = v._approximation.phi()
                if self.is_approximate_irreducible_factor(g, f, v._approximation):
                    break
                else:
                    v._improve_approximation()
            g = self.simplify_irreducible_polynomial(g)
            e = v._approximation.E()
            factor_list.append((g, e))
        return factor_list


    def approximate_irreducible_factor(self, f):
        r"""
        Return one approximate irreducible factor of a given polynomial.

        INPUT:

        - ``f`` -- a squarefree and integral polynomial over the number field
          ``K_0`` underlying this `p`-adic number field `K`.

        Output:

        Either a monic poynomial `g` over `K_0` which is an approximate irreducible
        factor of `f`, or a positive integer `e`.

        The polynomial `g` is then irreducible over `K` and the extension of `K`
        generated by adjoining a root of `g` is contained in the splitting field
        of `f`.

        If an integer `e` is retuned, then any tame extension of `K` of ramification
        degree `e` is a weak splitting field of `f` over `K`.

        TODO:

        One should also allow a list of polynomials as input.

        """
        assert not f.is_constant(), 'f must not be constant'
        K = self.number_field()
        assert K.has_coerce_map_from(f.parent().base_ring())
        f = f.change_ring(K)
        vK = self.valuation()
        p = self.p()
        V = vK.mac_lane_approximants(f, assume_squarefree=True, require_maximal_degree=True)
        wild_and_totally_ramified = [v for v in V
            if p.divides(v.E()) and v.E().is_prime_power() and v.F() == 1]
        if wild_and_totally_ramified != []:
            # these are the best ones
            ram_indices = [v.E() for v in wild_and_totally_ramified]
            i = ram_indices.index(max(ram_indices))  # the index of the largest
            v = wild_and_totally_ramified[i]
        else:
            mixed_and_totally_ramified = [v for v in V
                if p.divides(v.E()) and v.F() == 1]
            if mixed_and_totally_ramified != []:
                # these are the second best ones
                ram_indices = [v.E() for v in mixed_and_totally_ramified]
                i = ram_indices.index(max(ram_indices))  # the index of the largest
                v = mixed_and_totally_ramified[i]
            else:
                wild_factors = [v for v in V if p.divides(v.E())]
                if wild_factors != []:
                    ram_indices = [v.E() for v in wild_factors]
                    i = ram_indices.index(max(ram_indices))  # the index of the largest
                    v = wild_factors[i]
                else:
                    # now there are only tame factors
                    # we return the lcm of the ramification indices
                    return lcm([v.E() for v in V])
        # now v approximates an irreducible factor of f which will give a
        # wildly ramified extension of K
        # we have to improve v until v.phi() is an approximate factor
        while True:
            for i in range(3):
                # 3 is just a guess; I should experiment a bit if another
                # value is better
                if v.mu() == Infinity:
                    return v.phi()
                else:
                    v = v.mac_lane_step(f, assume_squarefree=True)[0]
            g = v.phi()
            if v.mu() == Infinity or self.is_approximate_irreducible_factor(g, f, v):
                return g


    def characteristic_polynomial(self, f, g):
        r"""
        Return the characteristic polynomial of an element of a simple extension
        of `K`.

        INPUT:

        - ``f``, ``g`` -- univariate monic polynomials over `K`

        OUTPUT:

        the characteristic polynomial of the element `\beta=g(\alpha)` with
        respect to the relative extension `L=K[\alpha]/K`, where `\alpha` is
        a formal root of `f`. Of course, `L` is a field only if `f` i irreducible,
        but this is not checked, and no error would occur within this function if
        `f` is reducible.

        """

        # construct the matrix representing alpha as endo of L/K
        K = self.number_field()
        m = f.degree()
        A = matrix(K, m)
        for i in range(m-1):
            A[i+1, i] = K(1)
        for i in range(m):
            A[i, m-1] = -f[i]
        B = g(A)
        # now B represents beta
        return B.charpoly()


    def characteristic_polynomial_mod(self, f, N):
        r"""
        Return the absolute characteristic polynomial of the root of a given
        polynomial, modulo `p^N`.

        INPUT:

        - ``f`` -- a monic `p`-integral and irreducible polynomial over the
          underlying number field `K`
        - ``N`` -- a positive integer

        OUTPUT:

        the absolute characteristic polynomial of a root of `f`, modulo `p^N`.

        """
        # construct the rational representation of a(formal) root alpha of f
        m = f.degree()
        K = self.number_field()
        vK = self.valuation()
        B = matrix(K, m)
        for i in range(m-1):
            B[i+1, i] = K(1)
        for i in range(m):
            B[i, m-1] = -f[i]
        # construct BB as the matrix representing alpha as endo of L/QQ, modulo p^N
        # note the the function self.matrix gives the representation with respect
        # to a p-integral basis of L/QQ, hence BB should be p-integral
        n = K.absolute_degree()
        R = IntegerModRing(self.p()**N)
        BB = matrix(R, n*m)
        for i in range(m):
            for j in range(m):
                B_ij = self.matrix(B[i,j], "mixed")
                # this had been changed, and it is not sufficiently tested
                for k in range(n):
                    for l in range(n):
                        if vK(B_ij[k,l]) < 0:
                            print("B[i,j] = ", B[i,j])
                            print("vK(..) = ", vK(B[i,j]))
                            print("B_ij[k,l] = ", B_ij[k,l])
                            raise ValueError
                        BB[i*n+k, j*n+l] = R(B_ij[k,l])
        return BB.charpoly()


    def subfield(self, alpha, e):
        r"""
        Return a subfield approximated by a given element.

        INPUT:

        - ``alpha`` -- an element of the number field `K_0` underlying `K`
        - ``e`` -- a divisor of the absolute degree of `K_0`

        OUTPUT:

        A `p`-adic number field `L` with ramification index `e` which has an
        embedding into `K`, or ``None`` if no such field can be found.

        If `L` is a subfield of `K` with ramification index `e` and `\alpha_i`
        is a sequence of element of `K_0` converging to a generator of `L`, then
        calling ``K.subfield(alpha_i,e)`` will find the subfield `L` for `i`
        sufficiently large.

        TODO:

        One easy improvement could be to not try to embed L into K after every
        MacLane step, but only for the last step before the degree of v jumps.

        """
        from mclf.padic_extensions.fake_padic_embeddings import FakepAdicEmbedding
        K = self
        v_p = K.base_valuation()
        if e == 1:
            return FakepAdicCompletion(QQ, v_p)
        n = K.degree()
        assert e.divides(n), "e must be a divisor of the degree of K"
        alpha = K.number_field()(alpha)
        f = alpha.minpoly()
        R = f.parent()
        v = GaussValuation(R, v_p)
        while True:
            d = v.phi().degree()
            v1 = v
            while v1.phi().degree() == d and v.mu() < Infinity:
                v = v1
                if v.mu() < Infinity:
                    V = v.mac_lane_step(f, assume_squarefree=True, check=False)
                    if len(V) > 1:
                        # print("len(V) > 1")
                        return None
                    v1 = V[0]
            # now v.phi().degree() = d, and either v1.phi().degree() > d
            # or v.mu() = Infinity
            g = v.phi()
            if g.degree() > 1:
                L = FakepAdicCompletion(QQ, v_p).extension(g)
                if L.ramification_degree() == e:
                    try:
                        FakepAdicEmbedding(L, K)
                        # this can be very slow, but so far it is the only
                        # conclusive test I know
                        # print("found subfield of degree %s and ramification degree %s"%(L.degree(), L.ramification_degree()))
                        return L
                    except AssertionError:
                        pass
                        # print("no embedding into L!")
            if v.mu() == Infinity:
                # print("v.mu() = Infinity")
                return None
            else:
                v = v1  # we go to a larger degree
