# sage_setup: distribution = sagemath-polyhedra
# sage.doctest: needs sage.geometry.polyhedron sage.graphs sage.libs.singular
r"""
Toric ideals

A toric ideal (associated to an integer matrix `A`) is an ideal of the
form

.. MATH::

    I_A = \left<
        x^u - x^v
        : u,v \in \ZZ_\geq^n
        , u-v \in \ker(A)
        \right>

In other words, it is an ideal generated by irreducible "binomials",
that is, differences of monomials without a common factor. Since the
Buchberger algorithm preserves this property, any Groebner basis is
then also generated by binomials.

EXAMPLES::

    sage: A = matrix([[1,1,1], [0,1,2]])
    sage: IA = ToricIdeal(A)
    sage: IA.ker()
    Free module of degree 3 and rank 1 over Integer Ring
    User basis matrix:
    [-1  2 -1]
    sage: IA
    Ideal (-z1^2 + z0*z2) of Multivariate Polynomial
    Ring in z0, z1, z2 over Rational Field

Here, the "naive" ideal generated by `z_0 z_2 - z_1^2` does already
equal the toric ideal. But that is not true in general! For example,
this toric ideal ([Stu1997]_, Example 1.2) is the twisted
cubic and cannot be generated by `2=\dim \ker(A)` polynomials::

    sage: A = matrix([[3,2,1,0], [0,1,2,3]])
    sage: IA = ToricIdeal(A)
    sage: IA.ker()
    Free module of degree 4 and rank 2 over Integer Ring
    User basis matrix:
    [-1  1  1 -1]
    [-1  2 -1  0]
    sage: IA
    Ideal (-z1*z2 + z0*z3, -z1^2 + z0*z2, z2^2 - z1*z3) of
    Multivariate Polynomial Ring in z0, z1, z2, z3 over Rational Field

The following family of toric ideals is from Example 4.4 of
[Stu1997]_. One can show that `I_d` is generated by one
quadric and `d` binomials of degree `d`::

    sage: def I(d):
    ....:     return ToricIdeal(matrix([[1,1,1,1,1], [0,1,1,0,0], [0,0,1,1,d]]))
    sage: I(2)
    Ideal (-z3^2 + z0*z4,
           z0*z2 - z1*z3,
           z2*z3 - z1*z4) of
    Multivariate Polynomial Ring in z0, z1, z2, z3, z4 over Rational Field
    sage: I(3)
    Ideal (-z3^3 + z0^2*z4,
           z0*z2 - z1*z3,
           z2*z3^2 - z0*z1*z4,
           z2^2*z3 - z1^2*z4) of
    Multivariate Polynomial Ring in z0, z1, z2, z3, z4 over Rational Field
    sage: I(4)
    Ideal (-z3^4 + z0^3*z4,
           z0*z2 - z1*z3,
           z2*z3^3 - z0^2*z1*z4,
           z2^2*z3^2 - z0*z1^2*z4,
           z2^3*z3 - z1^3*z4) of
    Multivariate Polynomial Ring in z0, z1, z2, z3, z4 over Rational Field

Finally, the example in [SH1995b]_ ::

    sage: A = matrix(ZZ, [ [15,  4, 14, 19,  2,  1, 10, 17],
    ....:                  [18, 11, 13,  5, 16, 16,  8, 19],
    ....:                  [11,  7,  8, 19, 15, 18, 14,  6],
    ....:                  [17, 10, 13, 17, 16, 14, 15, 18] ])
    sage: IA = ToricIdeal(A)     # long time
    sage: IA.ngens()             # long time
    213

TESTS::

    sage: A = matrix(ZZ, [[1, 1, 0, 0, -1,  0,  0, -1],
    ....:                 [0, 0, 1, 1,  0, -1, -1,  0],
    ....:                 [1, 0, 0, 1,  1,  1,  0,  0],
    ....:                 [1, 0, 0, 1,  0,  0, -1, -1]])
    sage: IA = ToricIdeal(A)
    sage: R = IA.ring()
    sage: R.inject_variables()
    Defining z0, z1, z2, z3, z4, z5, z6, z7
    sage: IA == R.ideal([z4*z6-z5*z7, z2*z5-z3*z6, -z3*z7+z2*z4,
    ....:     -z2*z6+z1*z7, z1*z4-z3*z6, z0*z7-z3*z6, -z1*z5+z0*z6, -z3*z5+z0*z4,
    ....:     z0*z2-z1*z3])  # Computed with Maple 12
    True

The next example first appeared in Example 12.7 in [Stu1995]_. It is also
used by the Maple 12 help system as example::

    sage: A = matrix(ZZ, [[1, 2, 3, 4, 0, 1, 4, 5],
    ....:                 [2, 3, 4, 1, 1, 4, 5, 0],
    ....:                 [3, 4, 1, 2, 4, 5, 0, 1],
    ....:                 [4, 1, 2, 3, 5, 0, 1, 4]])
    sage: IA = ToricIdeal(A, 'z1, z2, z3, z4, z5, z6, z7, z8')
    sage: R = IA.ring()
    sage: R.inject_variables()
    Defining z1, z2, z3, z4, z5, z6, z7, z8
    sage: IA == R.ideal([z4^4-z6*z8^3, z3^4-z5*z7^3, -z4^3+z2*z8^2,
    ....:     z2*z4-z6*z8, -z4^2*z6+z2^2*z8, -z4*z6^2+z2^3, -z3^3+z1*z7^2,
    ....:     z1*z3-z5*z7, -z3^2*z5+z1^2*z7, z1^3-z3*z5^2])
    True

AUTHORS:

- Volker Braun (2011-01-03): Initial version
"""

# ****************************************************************************
#       Copyright (C) 2010 Volker Braun <vbraun.name@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  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/
# ****************************************************************************

# TODO:
#   * Implement the Di Biase & Urbanke algorithm
#   * Implement the Conti & Traverso algorithm (for educational purposes)
#   * Cythonize the Buchberger algorithm for toric ideals
#   * Use the (multiple) weighted homogeneity during Groebner basis computations


from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.misc.misc_c import prod
from sage.matrix.constructor import matrix
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal


class ToricIdeal(MPolynomialIdeal):
    r"""
    This class represents a toric ideal defined by an integral matrix.

    INPUT:

    - ``A`` -- integer matrix; the defining matrix of the toric ideal

    - ``names`` -- string (optional); names for the variables. By
      default, this is ``'z'`` and the variables will be named ``z0``,
      ``z1``, ...

    - ``base_ring`` -- a ring (default: `\QQ`); the base
      ring of the ideal. A toric ideal uses only coefficients `\pm 1`.

    - ``polynomial_ring`` -- a polynomial ring (optional); the
      polynomial ring to construct the ideal in

      You may specify the ambient polynomial ring via the
      ``polynomial_ring`` parameter or via the ``names`` and
      ``base_ring`` parameter. A :exc:`ValueError` is raised if you
      specify both.

    - ``algorithm`` -- string (optional); the algorithm to use. For
      now, must be ``'HostenSturmfels'`` which is the algorithm
      proposed by Hosten and Sturmfels in [SH1995b]_.

    EXAMPLES::

        sage: A = matrix([[1,1,1], [0,1,2]])
        sage: ToricIdeal(A)
        Ideal (-z1^2 + z0*z2) of Multivariate Polynomial Ring
        in z0, z1, z2 over Rational Field

    First way of specifying the polynomial ring::

        sage: ToricIdeal(A, names='x,y,z', base_ring=ZZ)
        Ideal (-y^2 + x*z) of Multivariate Polynomial Ring
        in x, y, z over Integer Ring

    Second way of specifying the polynomial ring::

        sage: R.<x,y,z> = ZZ[]
        sage: ToricIdeal(A, polynomial_ring=R)
        Ideal (-y^2 + x*z) of Multivariate Polynomial Ring
        in x, y, z over Integer Ring

    It is an error to specify both::

        sage: ToricIdeal(A, names='x,y,z', polynomial_ring=R)
        Traceback (most recent call last):
        ...
        ValueError: you must not specify both variable names and a polynomial ring
    """

    def __init__(self, A,
                 names='z', base_ring=QQ,
                 polynomial_ring=None,
                 algorithm='HostenSturmfels'):
        r"""
        Create an ideal and a multivariate polynomial ring containing it.

        See the :mod:`module documentation
        <sage.schemes.toric.ideal>` for an introduction to
        toric ideals.

        INPUT:

        See the :class:`class-level documentation <ToricIdeal>` for
        input values.

        EXAMPLES::

            sage: A = matrix([[1,1,1], [0,1,2]])
            sage: ToricIdeal(A)
            Ideal (-z1^2 + z0*z2) of Multivariate Polynomial Ring
            in z0, z1, z2 over Rational Field
            sage: ToricIdeal(A, names='x', base_ring=GF(101))
            Ideal (-x1^2 + x0*x2) of Multivariate Polynomial Ring
            in x0, x1, x2 over Finite Field of size 101
            sage: ToricIdeal(A, names='x', base_ring=FractionField(QQ['t']))
            Ideal (-x1^2 + x0*x2) of
            Multivariate Polynomial Ring in x0, x1, x2 over
             Fraction Field of Univariate Polynomial Ring in t over Rational Field
        """
        self._A = matrix(ZZ, A)
        if polynomial_ring:
            if names != 'z' or base_ring is not QQ:
                raise ValueError('you must not specify both variable names and a polynomial ring')
            self._names = [str(g) for g in polynomial_ring.gens()]
            self._base_ring = polynomial_ring.base_ring()
            ring = polynomial_ring
        else:
            self._names = names
            self._base_ring = base_ring
            ring = self._init_ring('degrevlex')

        if algorithm == 'HostenSturmfels':
            ideal = self._ideal_HostenSturmfels()
        else:
            raise ValueError(f'algorithm = {algorithm} is not known')

        gens = [ring(x) for x in ideal.gens()]
        MPolynomialIdeal.__init__(self, ring, gens, coerce=False)

    def A(self):
        """
        Return the defining matrix.

        OUTPUT: integer matrix

        EXAMPLES::

            sage: A = matrix([[1,1,1], [0,1,2]])
            sage: IA = ToricIdeal(A)
            sage: IA.A()
            [1 1 1]
            [0 1 2]
        """
        return self._A

    def ker(self):
        """
        Return the kernel of the defining matrix.

        OUTPUT: the kernel of ``self.A()``.

        EXAMPLES::

            sage: A = matrix([[1,1,1], [0,1,2]])
            sage: IA = ToricIdeal(A)
            sage: IA.ker()
            Free module of degree 3 and rank 1 over Integer Ring
             User basis matrix: [-1  2 -1]
        """

        if '_ker' in self.__dict__:
            return self._ker
        self._ker = self.A().right_kernel(basis='LLL')
        return self._ker

    def nvariables(self):
        r"""
        Return the number of variables of the ambient polynomial ring.

        OUTPUT: integer; the number of columns of the defining matrix :meth:`A`

        EXAMPLES::

            sage: A = matrix([[1,1,1], [0,1,2]])
            sage: IA = ToricIdeal(A)
            sage: IA.nvariables()
            3
        """
        return self.A().ncols()

    def _init_ring(self, term_order):
        r"""
        Construct the ambient polynomial ring.

        INPUT:

        - ``term_order`` -- string; the order of the variables, for
          example ``'neglex'`` and ``'degrevlex'``

        OUTPUT: a polynomial ring with the given term order

        .. NOTE::

            Reverse lexicographic ordering is equivalent to negative
            lexicographic order with the reversed list of
            variables. We are using the latter in the implementation
            of the Hosten/Sturmfels algorithm.

        EXAMPLES::

            sage: A = matrix([[1,1,1], [0,1,2]])
            sage: IA = ToricIdeal(A)
            sage: R = IA._init_ring('neglex');  R
            Multivariate Polynomial Ring in z0, z1, z2 over Rational Field
            sage: R.term_order()
            Negative lexicographic term order
            sage: R.inject_variables()
            Defining z0, z1, z2
            sage: z0 < z1 and z1 < z2
            True
        """
        return PolynomialRing(self._base_ring, self._names,
                              self.nvariables(), order=term_order)

    def _naive_ideal(self, ring):
        r"""
        Return the "naive" subideal.

        INPUT:

        - ``ring`` -- the ambient ring of the ideal

        OUTPUT: a subideal of the toric ideal in the polynomial ring ``ring``

        EXAMPLES::

            sage: A = matrix([[1,1,1], [0,1,2]])
            sage: IA = ToricIdeal(A)
            sage: IA.ker()
            Free module of degree 3 and rank 1 over Integer Ring
             User basis matrix: [-1  2 -1]
            sage: IA._naive_ideal(IA.ring())
            Ideal (z1^2 - z0*z2) of Multivariate Polynomial Ring in z0, z1, z2 over Rational Field
        """
        x = ring.gens()
        binomials = []
        for row in self.ker().matrix().rows():
            xpos = prod(x[i]**max(row[i], 0) for i in range(len(x)))
            xneg = prod(x[i]**max(-row[i], 0) for i in range(len(x)))
            binomials.append(xpos - xneg)
        return ring.ideal(binomials)

    def _ideal_quotient_by_variable(self, ring, ideal, n):
        r"""
        Return the ideal quotient `(J:x_n^\infty)`.

        INPUT:

        - ``ring`` -- the ambient polynomial ring in neglex order

        - ``ideal`` -- the ideal `J`

        - ``n`` -- integer; the index of the next variable to divide by

        OUTPUT: the ideal quotient `(J:x_n^\infty)`

        ALGORITHM:

        Proposition 4 of [SH1995b]_.

        EXAMPLES::

            sage: A = lambda d: matrix([[1,1,1,1,1],[0,1,1,0,0],[0,0,1,1,d]])
            sage: IA = ToricIdeal(A(3))
            sage: R = PolynomialRing(QQ, 5, 'z', order='neglex')
            sage: J0 = IA._naive_ideal(R)
            sage: IA._ideal_quotient_by_variable(R, J0, 0)
            Ideal (z2*z3^2 - z0*z1*z4, z1*z3 - z0*z2,
                   z2^2*z3 - z1^2*z4, z1^3*z4 - z0*z2^3)
             of Multivariate Polynomial Ring in z0, z1, z2, z3, z4 over Rational Field
        """
        N = self.nvariables()
        y = list(ring.gens())
        x = [y[i - n] for i in range(N)]
        y_to_x = dict(zip(x, y))
        x_to_y = dict(zip(y, x))
        # swap variables such that the n-th variable becomes the last one
        J = ideal.subs(y_to_x)

        # TODO: Can we use the weighted homogeneity with respect to
        # the rows of self.A() when computing the Groebner basis, see
        # [SH1995b]?
        basis = J.groebner_basis()

        # x_n = y[0]   # the cheapest variable in the revlex order
        def subtract(e, power):
            l = list(e)
            return tuple([l[0] - power] + l[1:])

        def divide_by_x_n(p):
            d_old = p.monomial_coefficients()
            power = min(e[0] for e in d_old)
            d_new = {subtract(exponent, power): coefficient
                     for exponent, coefficient in d_old.items()}
            return p.parent()(d_new)
        basis = [divide_by_x_n(b) for b in basis]
        quotient = ring.ideal(basis)
        return quotient.subs(x_to_y)

    def _ideal_HostenSturmfels(self):
        r"""
        Compute the toric ideal by Hosten and Sturmfels' algorithm.

        OUTPUT:

        The toric ideal as an ideal in the polynomial ring
        ``self.ring()``.

        EXAMPLES::

            sage: A = matrix([[3,2,1,0], [0,1,2,3]])
            sage: IA = ToricIdeal(A);  IA
            Ideal (-z1*z2 + z0*z3, -z1^2 + z0*z2, z2^2 - z1*z3)
             of Multivariate Polynomial Ring in z0, z1, z2, z3 over Rational Field
            sage: R = IA.ring()
            sage: IA == IA._ideal_HostenSturmfels()
            True

        TESTS::

            sage: I_2x2 = identity_matrix(ZZ, 2)
            sage: ToricIdeal(I_2x2)
            Ideal (0) of Multivariate Polynomial Ring in z0, z1 over Rational Field
        """
        ring = self._init_ring('neglex')
        J = self._naive_ideal(ring)
        if J.is_zero():
            return J
        for i in range(self.nvariables()):
            J = self._ideal_quotient_by_variable(ring, J, i)
        return J
