r"""
Truncated Multivariate polynomials
EXAMPLES::
sage: from msinvar import TMPoly
sage: R=TMPoly(QQ,2,'x',prec=(2,2))
sage: R.inject_variables(verbose=False)
sage: (x0+x1).Exp()
1 + x0 + x1 + x0^2 + x0*x1 + x1^2 + x0^2*x1 + x0*x1^2 + x0^2*x1^2
"""
# *****************************************************************************
# Copyright (C) 2021 Sergey Mozgovoy <mozhov@gmail.com>
#
# Distributed under the terms of the GNU General Public License (GPL)
# http://www.gnu.org/licenses/
# *****************************************************************************
from sage.rings.polynomial.multi_polynomial_element import MPolynomial_polydict
from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict
from sage.arith.misc import moebius
from sage.rings.rational_field import QQ
from msinvar.lambda_rings import LambdaRings, adams
from msinvar.utils import isiterable, vec
[docs]class TMPolynomial(MPolynomial_polydict):
"""
The element class for truncated multivariate polynomials.
The parent class is :class:`TMPolynomialRing`.
Implementation details:
1. The parent should have a method :meth:`le_prec` to test if a given degree should be present.
2. Truncation is performed in the :meth:`__init__` method.
3. Arithmetic operations are inherited from :class:`MPolynomial_polydict`.
4. We implement Adams operation adams, exp, log and plethystic Exp, Log.
5. Function for the twisted multilpication is given by self.parent().prod_twist
"""
def __init__(self, parent, x):
self._parent = parent
if isinstance(x, dict):
d = x
else:
super().__init__(parent, x)
d = self.dict()
d = {e: c for e, c in d.items() if parent.le_prec(e)}
super().__init__(parent, d)
def _new_element(self, d):
"""Construct new poly from a dictionary d"""
return self.__class__(self.parent(), d)
def _new_constant_poly(self, x, P):
# overriden; needed to make 1-x[0] of type MPT
return self._new_element({self.parent().zero_tuple(): x})
[docs] def le_prec(self, v): # <= precision
return self.parent().le_prec(v)
[docs] def prec_num(self):
return self.parent().prec_num()
[docs] def exp(self):
N = self.prec_num()
if N == None:
raise ValueError("Series is untruncated")
if self.constant_coefficient() != 0:
raise ValueError("The constant coefficient should be 0")
s = self.parent().one()
f, x = 1, self
for i in range(1, N+1):
f = x*f/i
if f == 0:
break
s = s+f
return s
[docs] def log(self):
N = self.prec_num()
if N == None:
raise ValueError("Series is untruncated")
if self.constant_coefficient() != 1:
raise ValueError("The constant coefficient should be 1")
s = self.parent().zero()
f, x = 1, 1-self # need _new_constasnt_poly to have 1-self of type MPT!
for i in range(1, N+1):
f = x*f
if f == 0:
break
s = s-f/i
return s
[docs] def invert(self):
"""
For simplicity we invert only poly with constant coefficient 1
"""
N = self.prec_num()
if N == None:
raise ValueError("Series is untruncated")
if self.constant_coefficient() != 1:
raise ValueError("The constant coefficient should be 1")
s = self.parent().one()
f, x = 1, 1-self
for i in range(1, N+1):
f = x*f
if f == 0:
break
s = s + f
return s
def _div_(self, right):
return self*right.invert()
[docs] def Psi(self):
N = self.prec_num()
if N == None:
raise ValueError("Error: series is untruncated")
if self.constant_coefficient() != 0:
raise ValueError("The constant coefficient should be 0")
s = self.parent().zero()
for i in range(1, N+2):
f = self.adams(i)
if f == 0:
break
s = s+f/i
return s
[docs] def IPsi(self):
N = self.prec_num()
if N == None:
raise ValueError("Error: series is untruncated")
if self.constant_coefficient() != 0:
raise ValueError("The constant coefficient should be 0")
s = self.parent().zero()
for i in range(1, N+2):
f = self.adams(i)
if f == 0:
break
s = s+f*moebius(i)/i
return s
[docs] def Exp(self):
return self.Psi().exp()
[docs] def Log(self):
return self.log().IPsi()
[docs] def term_twist(self, f):
"""Multiply every term c*x^v by f(v)"""
d = {e: c*f(e) for e, c in self.dict().items()}
return self._new_element(d)
def _mul_(self, right):
f = self.parent().prod_twist
if f is None:
return super()._mul_(right)
if len(self.dict()) == 0: # product is zero anyways
return self
if right in self.parent().base_ring():
return self*self._new_constant_poly(right, self.parent())
d = {}
for e0, c0 in self.dict().items():
for e1, c1 in right.dict().items():
e = e0.eadd(e1)
if not self.le_prec(e):
continue
c = c0*c1*f(e0, e1)
if e in d:
d[e] = d[e]+c
else:
d[e] = c
return self._new_element(d)
[docs] def coeff(self, e):
"""
Return the coefficient of degree e monomial as an element of
the base_ring.
Note that **coefficient** returns an element of the polynomial ring.
"""
try:
return self.element()[list(e)]
except:
return 0
[docs] def subs_base(self, **kw):
dct = {}
for e, c in self.dict().items():
try:
c = c.subs(**kw)
except:
pass
dct[e] = c
return self._new_element(dct)
[docs] def subs(self, **kw):
f = super().subs(**kw)
if f != self:
return f
return self.subs_base(**kw)
[docs] def restrict(self, z, slope):
"""Restrict polynomial to dimension vectors d such that z(d)=slope,
where ``z`` is a Stability (or the corresponding vector)."""
from msinvar.stability import Stability
z = Stability.check(z)
dct = {}
for e, c in self.dict().items():
if vec.iszero(e) or z.has_slope(e, slope):
dct[e] = c
return self._new_element(dct)
[docs] def invar(self):
from msinvar.invariants import Invariant
return Invariant(self)
[docs] def pLog(self):
"""
Plethystic logarithm along every ray.
"""
return self.invar().pLog().poly()
[docs] def pExp(self):
"""
Plethystic logarithm along every ray.
"""
return self.invar().pExp().poly()
[docs]class TMPolynomialRing(MPolynomialRing_polydict):
"""
Truncated Multivariate polynomial ring.
Multivariate polynomials up to degree ``prec``,
where ``prec`` is a degree vector or an integer (total degree).
Monomials of degree ``prec`` are included.
We implement Adams operations, plethystic Exp and Log
(we introduce the category of lambda rings
:class:`msinvar.lambda_rings.LambdaRings` and embed our ring into it).
This is the parent class for truncated multivariate polynomials.
It has an alias :class:`TMPoly`.
The element class is :class:`TMPolynomial`.
PARAMETERS:
1. ``base_ring`` -- the base of our polynomial algebra.
2. ``n`` -- the number of vairables.
3. ``names`` -- names of variables; can be just 'x' or 'x,y,..'.
4. ``prec`` - precision vector (or integer, or None) for truncation
5. ``order`` -- the order of variables.
6. ``prod_twist`` - function that maps exponents (d,e) to the base_ring. The new product is x^d*x^e=prod_twist(d,e)*x^(d+e)
EXAMPLES::
sage: from msinvar import TMPoly
sage: R=TMPoly(QQ,2,prec=(2,2)); R
Multivariate Polynomial Ring in x0, x1 over Rational Field truncated at degree (2, 2)
sage: x=R.gens(); (x[0]+x[1])**3
3*x0^2*x1 + 3*x0*x1^2
sage: QR=Frac(PolynomialRing(QQ,'y,t'))
sage: S=TMPoly(QR,2,'x',prec=(2,2))
sage: y,t=QR.gens(); x=S.gens()
sage: (y*x[0]+x[1]).adams(2)
y^2*x0^2 + x1^2
sage: (y*x[0]).Exp()
1 + y*x0 + y^2*x0^2
sage: R=TMPoly(QQ,1,prec=2, prod_twist=lambda a,b:2)
sage: x=R.gen(); x*x
2*x^2
"""
Element = TMPolynomial
def __init__(self, base_ring=QQ, n=1, names='x', order='negdegrevlex',
prec=None, prod_twist=None):
# Parent.__init__(self, category=LambdaRings())#not needed anymore
self._prec = prec
self.prod_twist = prod_twist
super().__init__(base_ring, n, names, order)
LambdaRings.add_ring(self)
[docs] def adams(self, a, n):
d = {e.emul(n): adams(c, n) for e, c in a.dict().items()
if self.le_prec(e.emul(n))}
return self.Element(self, d)
def __call__(self, x): # needed so that the result is MPT
if isinstance(x, list) or isinstance(x, tuple):
return self.Element(self, {tuple(x): 1})
x = super().__call__(x)
return self.Element(self, x.dict())
def _poly_class(self): # needed so that gens() are of type MPT
return self.Element
def _repr_(self):
s = 'total degree ' if isinstance(self._prec, int) else 'degree '
return super()._repr_()+' truncated at '+s+str(self._prec)
[docs] def zero_tuple(self):
return self._zero_tuple
[docs] def prec(self, d=None):
if d is not None:
self._prec = d
return
return self._prec
[docs] def prec_num(self):
d = self._prec
if d == None:
return None
if isiterable(d):
return sum(d)
return d
[docs] def le_prec(self, v):
d = self._prec
if d == None:
return True
if isiterable(d):
return all(i <= j for i, j in zip(v, d))
return sum(v) <= d
TMPoly = TMPolynomialRing
[docs]def Exp(f):
return f.Exp()
[docs]def Log(f):
return f.Log()
[docs]def Psi(f):
return f.Psi()
[docs]def IPsi(f):
return f.IPsi()