unlisted
ubuntu2004# -*- coding: utf-8 -*-1r"""2Tautological subring of the cohomology ring of the moduli space of curves.3"""45import numbers67from sage.misc.cachefunc import cached_method8from sage.misc.misc_c import prod910from sage.structure.unique_representation import UniqueRepresentation11from sage.structure.richcmp import op_EQ, op_NE12from sage.structure.element import ModuleElement, parent13from sage.structure.all import coercion_model141516from sage.categories.functor import Functor17from sage.categories.pushout import ConstructionFunctor18from sage.categories.algebras import Algebras19from sage.categories.rings import Rings2021from sage.arith.all import factorial, bernoulli, multinomial22from sage.combinat.all import Partitions23from sage.combinat.integer_vector import IntegerVectors24from sage.rings.ring import Algebra25from sage.rings.integer_ring import ZZ26from sage.rings.rational_field import QQ27from sage.modules.free_module_element import vector28from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing29from sage.rings.polynomial.term_order import TermOrder30from sage.combinat.integer_vector_weighted import WeightedIntegerVectors31from sage.matrix.constructor import matrix3233from .moduli import _moduli_to_str, _str_to_moduli, MODULI_TL, get_moduli, socle_degree34from .admcycles import decstratum35from .stable_graph import StableGraph36from .identify_classes import identify_class373839_CommutativeRings = Rings().Commutative()404142# NOTE: replaces admcycles.tautclass43class TautologicalClass(ModuleElement):44r"""45An element of a tautological ring.4647Internally, it is represented by a list ``terms`` of objects of type48:class:`admcycles.admcycles.decstratum`. Such element should never49be constructed directly by calling :class:`TautologicalClass`. Instead50use the parent class :class:`TautologicalRing` or dedicated functions51as in the examples below5253EXAMPLES::5455sage: from admcycles import *56sage: R = TautologicalRing(3,1)57sage: gamma = StableGraph([1,2],[[1,2],[3]],[(2,3)])58sage: ds1 = R(gamma, kappa=[[1],[]])59sage: ds160Graph : [1, 2] [[1, 2], [3]] [(2, 3)]61Polynomial : (kappa_1)_062sage: ds2 = R(gamma, kappa=[[],[1]])63sage: ds264Graph : [1, 2] [[1, 2], [3]] [(2, 3)]65Polynomial : (kappa_1)_166sage: t = ds1 + ds267sage: (t - R(gamma) * R.kappa(1)).is_zero()68True6970Constructing a tautological class from dedicated functions::7172sage: psiclass(1, 2, 1) # psi_1 on M_{2,1}73Graph : [2] [[1]] []74Polynomial : psi_175"""7677def __init__(self, parent, terms, clean=True):78r"""79INPUT:8081- parent : a class:`TautologicalRing`82- terms : a list of class:`~admcycles.admcycles.decstratum`83- clean: boolean (default ``True``)84whether to apply ``dimension_filter`` and ``consolidate`` on the input85"""86ModuleElement.__init__(self, parent)8788if isinstance(terms, (tuple, list)):89self._terms = {}90for t in terms:91if not isinstance(t, decstratum):92raise TypeError93if t.gamma.is_mutable():94raise ValueError('mutable stable graph in a decstratum when building TautologicalClass')95if t.gamma.vanishes(parent._moduli):96continue97if clean:98t.dimension_filter(parent._moduli)99t.consolidate()100if t.gamma in self._terms:101self._terms[t.gamma].poly += t.poly102if not self._terms[t.gamma]:103del self._terms[t.gamma]104else:105self._terms[t.gamma] = t106elif isinstance(terms, dict):107if clean:108self._terms = {}109for gamma, term in terms.items():110if gamma != term.gamma:111raise ValueError112if gamma.vanishes(parent._moduli):113continue114term.dimension_filter(parent._moduli)115term.consolidate()116if term:117if gamma in self._terms:118self._terms[gamma] += term119else:120self._terms[gamma] = term121else:122self._terms = terms123else:124raise TypeError125126def copy(self):127P = self.parent()128return P.element_class(P, {g: t.copy() for g, t in self._terms.items()})129130# TODO: change the name131# TODO: this should depend on the moduli (though this global check is deactivated in decstratum.dimension_filter)132def dimension_filter(self):133for g in list(self._terms):134self._terms[g].dimension_filter()135if not self._terms[g]:136del self._terms[g]137138def _repr_(self):139if not self._terms:140return '0'141return '\n\n'.join(repr(self._terms[g]) for g in sorted(self._terms))142143def _unicode_art_(self):144r"""145Return unicode art for the tautological class.146147EXAMPLES::148149sage: from admcycles import *150sage: D = DR_cycle(1,(2,-2))151sage: unicode_art(D)152Graph :153<BLANKLINE>154╭──╮155│1 │156╰┬┬╯15712158<BLANKLINE>159Polynomial : 2*ψ₁ + 2*ψ₂160Graph :161╭╮16245163╭┴┴╮164│0 │165╰┬┬╯16612167<BLANKLINE>168Polynomial : -1/24169"""170from sage.typeset.unicode_art import unicode_art, UnicodeArt171if not self._terms:172return unicode_art('0')173return prod((self._terms[g]._unicode_art_() for g in sorted(self._terms)), UnicodeArt())174175def is_empty(self):176return not self._terms177178def is_nilpotent(self):179r"""180Return whether this element is nilpotent.181182EXAMPLES::183184sage: from admcycles import TautologicalRing185sage: R = TautologicalRing(1, 1)186sage: R.one().is_nilpotent()187False188sage: R.psi(1).is_nilpotent()189True190"""191return self.constant_coefficient().is_nilpotent()192193def is_unit(self):194r"""195Return whether this element is a unit.196197EXAMPLES::198199sage: from admcycles import TautologicalRing200sage: R = TautologicalRing(1, 1)201sage: R.one().is_unit()202True203sage: R.psi(1).is_unit()204False205"""206return self.constant_coefficient().is_unit()207208def inverse_of_unit(self):209r"""210Return the inverse of this element.211212EXAMPLES::213214sage: from admcycles import TautologicalRing215sage: R = TautologicalRing(0, 5)216sage: (R.one() + R.psi(1)).inverse_of_unit()217Graph : [0] [[1, 2, 3, 4, 5]] []218Polynomial : 1 - psi_1 + psi_1^2219"""220try:221from sage.rings.polynomial.misc import inverse_of_unit222except ImportError:223# NOTE: this is only in beta versions of sage224# see https://trac.sagemath.org/ticket/33499225from .misc import inverse_of_unit226return inverse_of_unit(self)227228# TODO: shouldn't this be called homogeneous_component?229def degree_part(self, d):230r"""231Return the homogeneous component of degree ``d``.232233EXAMPLES::234235sage: from admcycles import TautologicalRing236sage: R = TautologicalRing(1, 2)237sage: f = (1 - 2 * R.psi(1) + 3 * R.psi(2)**2)**2238sage: f.degree_part(0)239Graph : [1] [[1, 2]] []240Polynomial : 1241sage: f.degree_part(1)242Graph : [1] [[1, 2]] []243Polynomial : -4*psi_1244sage: f.degree_part(2)245Graph : [1] [[1, 2]] []246Polynomial : 6*psi_2^2 + 4*psi_1^2247"""248P = self.parent()249new_terms = {}250for g, term in self._terms.items():251term = term.degree_part(d)252if term:253new_terms[g] = term254return P.element_class(P, new_terms)255256def constant_coefficient(self):257r"""258Return the coefficient in degree zero as an element in the base ring.259260EXAMPLES::261262sage: from admcycles import TautologicalRing263sage: R = TautologicalRing(2, 1)264sage: (1/3 - R.psi(1) + R.kappa(2)).constant_coefficient()2651/3266267sage: A.<x, y> = QQ[]268sage: R = TautologicalRing(2, 1, base_ring=A)269sage: f = (1 - x * R.psi(1)) * (1/3 + y * R.kappa(2))270sage: f.constant_coefficient()2711/3272273TESTS::274275sage: from admcycles import TautologicalRing, StableGraph276sage: R = TautologicalRing(3,2)277sage: Gamma = StableGraph([3],[[2,1]],[]); Gamma278[3] [[2, 1]] []279sage: a = R.one() + R(Gamma); a280Graph : [3] [[1, 2]] []281Polynomial : 1282<BLANKLINE>283Graph : [3] [[2, 1]] []284Polynomial : 1285sage: a.constant_coefficient()2862287"""288return self.fund_evaluate()289290def degree_cap(self, dmax):291r"""292Return the class where we drop components of degree above ``dmax``.293294EXAMPLES::295296sage: from admcycles import TautologicalRing297sage: R = TautologicalRing(1, 2)298sage: f = (1 - 2 * R.psi(1) + 3 * R.psi(2)**2)**2299sage: f.degree_cap(1)300Graph : [1] [[1, 2]] []301Polynomial : 1 - 4*psi_1302"""303P = self.parent()304new_terms = {}305for g, term in self._terms.items():306term = term.degree_cap(dmax)307if term:308new_terms[g] = term309return P.element_class(P, new_terms)310311def subs(self, *args, **kwds):312r"""313Perform substitution on coefficients.314315EXAMPLES::316317sage: from admcycles import TautologicalRing318sage: A.<a1, a2> = PolynomialRing(QQ,2)319sage: R = TautologicalRing(2, 2, base_ring=A)320sage: t = a1 * R.psi(1) + a2 * R.psi(2)321sage: t322Graph : [2] [[1, 2]] []323Polynomial : a1*psi_1 + a2*psi_2324sage: t.subs({a2: 1 - a1})325Graph : [2] [[1, 2]] []326Polynomial : a1*psi_1 + (-a1 + 1)*psi_2327sage: t328Graph : [2] [[1, 2]] []329Polynomial : a1*psi_1 + a2*psi_2330331sage: t = (a1 - a2) * R.psi(1) + (a1 + a2) * R.psi(2)332sage: t.subs({a1: a2})333Graph : [2] [[1, 2]] []334Polynomial : 2*a2*psi_2335sage: t.subs({a1: 0, a2: 0})3360337sage: t338Graph : [2] [[1, 2]] []339Polynomial : (a1 - a2)*psi_1 + (a1 + a2)*psi_2340"""341new_terms = {}342for g, term in self._terms.items():343term = term.copy(mutable=False)344coeffs = term.poly.coeff345monom = term.poly.monom346i = 0347while i < len(coeffs):348coeffs[i] = coeffs[i].subs(*args, **kwds)349if not coeffs[i]:350coeffs.pop(i)351monom.pop(i)352else:353i += 1354if i:355new_terms[g] = term356P = self.parent()357return P.element_class(P, new_terms)358359# TODO: change the name, maybe simplify_full?360# TODO: the argument r is very bad as it mutates the tautological class361def FZsimplify(self, r=None):362r"""363Return representation of self as a tautclass formed by a linear combination of364the preferred tautological basis.365366If ``r`` is given, only take degree r part.367368EXAMPLES::369370sage: from admcycles import TautologicalRing371sage: R = TautologicalRing(0, 4)372sage: t = 7 + R.psi(1) + 3 * R.psi(2) - R.psi(3)373sage: t.FZsimplify()374Graph : [0] [[1, 2, 3, 4]] []375Polynomial : 7 + 3*(kappa_1)_0376377sage: t.FZsimplify(r=0)378Graph : [0] [[1, 2, 3, 4]] []379Polynomial : 7380"""381if self.is_empty():382return self383384R = self.parent()385if r is not None:386return R.from_basis_vector(self.basis_vector(r), r)387else:388result = R.zero()389for r in self.degree_list():390result += R.from_basis_vector(self.basis_vector(r), r)391return result392393# TODO: change the name?394def toprodtautclass(self, g=None, n=None):395P = self.parent()396if g is not None or n is not None:397from .superseded import deprecation398deprecation(109, "the arguments 'g' and 'n' of TautologicalClass.toprodtautclass are deprecated.")399if (g is not None and g != P._g) or (n is not None and n != P._n):400raise ValueError('invalid (g,n) (got ({},{}) instead of ({},{}))'.format(g, n, P._g, P._n))401402from .admcycles import prodtautclass403return prodtautclass(P.trivial_graph(), [[t.copy()] for t in self._terms.values()])404405# TODO: the method tautclass.simplify used to have more arguments simplify(self,g=None,n=None,r=None)406# TODO: should we really deprecate the argument r? (one can always do degree_cap followed by a simplify)407def simplify(self, g=None, n=None, r=None):408r"""409Simplifies self by combining terms with same tautological generator, returns self.410411EXAMPLES::412413sage: from admcycles import TautologicalRing414sage: R = TautologicalRing(2, 1)415sage: t = R.psi(1) + 11*R.psi(1)416sage: t417Graph : [2] [[1]] []418Polynomial : 12*psi_1419sage: t.simplify()420Graph : [2] [[1]] []421Polynomial : 12*psi_1422"""423P = self.parent()424if g is not None or n is not None or r is not None:425from .superseded import deprecation426deprecation(109, "the arguments 'g, n, r' of TautologicalClass.simplify are deprecated.")427if r is not None:428D = [r]429else:430D = self.degree_list()431432if self.is_empty():433# TODO: if elements were immutable we could return self434return self.copy()435result = P.zero()436for d in D:437result += P.from_vector(self.vector(d), d)438self._terms = result._terms439return self440441def simplified(self, g=None, n=None, r=None):442r"""443Return a simplified version of self by combining terms with same tautological generator.444"""445P = self.parent()446if g is not None or n is not None:447from .superseded import deprecation448deprecation(109, "the arguments 'g' and 'n' of TautologicalClass.simplified are deprecated.")449if (g is not None and g != P._g) or (n is not None and n != P._n):450raise ValueError('invalid g,n (got ({},{}) instead of ({},{}))'.format(g, n, P._g, P._n))451if self.is_empty():452# TODO: if elements were immutable we could return self453return self.copy()454if r is not None:455return P.from_vector(self.vector(r), r)456self.simplify()457return self458459def __neg__(self):460if self.is_empty():461# TODO: if elements were immutable we could return self462return self.copy()463P = self.parent()464return P.element_class(P, {g: -term for g, term in self._terms.items()}, clean=False)465466def _add_(self, other):467r"""468TESTS::469470sage: from admcycles import TautologicalRing471sage: R = TautologicalRing(1, 1)472sage: R(1) + 1473Graph : [1] [[1]] []474Polynomial : 2475sage: 1 + R(1)476Graph : [1] [[1]] []477Polynomial : 2478"""479if self.is_empty():480# TODO: if elements were immutable we could return other481return other.copy()482if other.is_empty():483# TODO: if elements were immutable we could return self484return self.copy()485P = self.parent()486new_terms = {g: t.copy() for g, t in self._terms.items()}487for g, term in other._terms.items():488if g in new_terms:489new_terms[g].poly += term.poly490if not new_terms[g]:491del new_terms[g]492else:493new_terms[g] = term494return P.element_class(P, new_terms, clean=False)495496def _lmul_(self, other):497r"""498Scalar multiplication by ``other``.499500TESTS::501502sage: from admcycles import TautologicalRing503sage: R = TautologicalRing(2, 2)504sage: -3/5 * R.psi(1)505Graph : [2] [[1, 2]] []506Polynomial : -3/5*psi_1507sage: 0 * R.psi(1) == 1 * R.zero() == R.zero()508True509sage: assert (0 * R.psi(1)).parent() is R510sage: assert (1 * R.zero()).parent() is R511"""512P = self.parent()513assert parent(other) is self.base_ring()514if other.is_zero():515return P.zero()516elif other.is_one():517# TODO: if elements were immutable we could return self518return self.copy()519new_terms = {g: other * term for g, term in self._terms.items()}520return P.element_class(P, new_terms, clean=False)521522def _mul_(self, other):523r"""524TESTS::525526sage: from admcycles import TautologicalRing527sage: R = TautologicalRing(2, 2)528sage: R.psi(1) * R.psi(2)529Graph : [2] [[1, 2]] []530Polynomial : psi_1*psi_2531532sage: R.generators(1)[3] * R.generators(2)[23]533Graph : [0, 1, 1] [[1, 2, 4], [5, 6], [7]] [(4, 5), (6, 7)]534Polynomial : (kappa_1)_2535536sage: (R.zero() * R.one()) == (R.one() * R.zero()) == R.zero()537True538sage: R.one() * R.one() == R.one()539True540541Multiplication with non-standard markings::542543sage: from admcycles import StableGraph544sage: R2 = TautologicalRing(1, [4, 7])545sage: g2 = StableGraph([0], [[1,2,4,7]], [(1,2)])546sage: R2(g2) * (1 + R2.psi(4)) * (1 + R2.psi(7))547Graph : [0] [[1, 2, 4, 7]] [(1, 2)]548Polynomial : 1 + psi_7 + psi_4549"""550if self.is_empty() or other.is_empty():551return self.parent().zero()552P = self.parent()553if P._markings and P._markings[-1] != P._n:554# TODO: maybe we want something more efficient for non-standard markings?555dic_to_std = {j: i + 1 for i, j in enumerate(P._markings)}556dic_from_std = {i + 1: j for i, j in enumerate(P._markings)}557return (self.rename_legs(dic_to_std, inplace=False) * other.rename_legs(dic_to_std, inplace=False)).rename_legs(dic_from_std, inplace=False)558559new_terms = {}560for t1 in self._terms.values():561for t2 in other._terms.values():562for g, term in t1.multiply(t2, P)._terms.items():563if g.vanishes(P._moduli):564continue565term.dimension_filter(moduli=P._moduli)566term.poly.consolidate(force=False)567if term:568if g in new_terms:569new_terms[g].poly += term.poly570else:571new_terms[g] = term572for g in list(new_terms):573if not new_terms[g]:574del new_terms[g]575return P.element_class(P, new_terms, clean=False)576577def exp(self):578r"""579Return the exponential of this tautological class.580581The exponential is defined as ``exp(x) = 1 + x + 1/2*x^2 + ... ``.582It is well defined as long as ``x`` has no degree zero term.583584EXAMPLES::585586sage: from admcycles import TautologicalRing587sage: R = TautologicalRing(1, 2)588sage: R.psi(1).exp()589Graph : [1] [[1, 2]] []590Polynomial : 1 + psi_1 + 1/2*psi_1^2591592TESTS::593594sage: from admcycles import TautologicalRing595sage: R = TautologicalRing(0, 3)596sage: R.zero().exp() == R.one()597True598"""599if self.vector(0):600raise ValueError('non-zero degree zero term')601P = self.parent()602f = P.one()603dmax = P.socle_degree()604if dmax == 0:605return f606result = f + self607y = self608for k in range(2, dmax + 1):609y = y * self610result += QQ((1, factorial(k))) * y611return result612613# TODO: full support for all moduli614def evaluate(self, moduli=None):615r"""616Computes integral against the fundamental class of the corresponding moduli space,617e.g. the degree of the zero-cycle part of the tautological class (for moduli='st').618619For non-standard moduli, this computes the socle-evaluation, which is given by620the integral against621622* lambda_g for moduli='ct'623* lambda_g * lambda_{g-1} for moduli='rt' (and moduli='sm' for n=0)624625EXAMPLES::626627sage: from admcycles import TautologicalRing628sage: R = TautologicalRing(1, 1)629sage: R.kappa(1).evaluate()6301/24631632Some non-standard moduli::633634sage: from admcycles import *635sage: R = TautologicalRing(2,1,moduli='ct')636sage: R.kappa(2).evaluate()6377/5760638sage: (kappaclass(2,2,1)*lambdaclass(2,2,1)).evaluate()6397/5760640sage: R.kappa(2).evaluate(moduli='st') # wrong degree6410642643TESTS::644645sage: R = TautologicalRing(2,1,moduli='ct')646sage: Rst = TautologicalRing(2,1)647sage: L = lambdaclass(2,2,1)648sage: all((Rst(t)*L).evaluate() == t.evaluate() for t in R.generators(2))649True650sage: R = TautologicalRing(2,2,moduli='rt')651sage: Rst = TautologicalRing(2,2)652sage: LL = Rst.lambdaclass(2)*Rst.lambdaclass(1)653sage: all((Rst(t)*LL).evaluate() == t.evaluate() for t in R.generators(2))654True655"""656P = self.parent()657if moduli is None:658moduli = _moduli_to_str[P._moduli]659if moduli == 'tl':660raise ValueError('no well-defined socle evaluation for space of treelike curves')661if P._moduli != get_moduli(moduli):662R = TautologicalRing(P._g, P._markings, base_ring=P.base_ring(), moduli=moduli)663return R(self).evaluate()664return sum((t.evaluate(moduli=moduli) for t in self._terms.values()), P.base_ring().zero())665666# TODO: change the name667def fund_evaluate(self):668r"""669Computes degree zero part of the class as multiple of the fundamental class.670671EXAMPLES::672673sage: from admcycles import TautologicalRing674sage: R = TautologicalRing(2, 1)675sage: t = R.psi(1)676sage: s = t.forgetful_pushforward([1])677sage: s678Graph : [2] [[]] []679Polynomial : 2680sage: s.fund_evaluate()6812682"""683return self.vector(0)[0]684685def _richcmp_(self, other, op):686r"""687Implementation of comparisons (``==`` and ``!=``).688689TESTS::690691sage: from admcycles import TautologicalRing692sage: R = TautologicalRing(2,2)693sage: R.zero() == R.zero()694True695sage: R.zero() == R.psi(1)696False697sage: R.psi(1) == R.zero()698False699sage: R.psi(1) == R.psi(1)700True701sage: R.psi(1) == R.psi(2)702False703704sage: R.zero() != R.zero()705False706sage: R.zero() != R.psi(1)707True708sage: R.psi(1) != R.zero()709True710sage: R.psi(1) != R.psi(1)711False712sage: R.psi(1) != R.psi(2)713True714sage: R = TautologicalRing(1,1)715sage: A = R(1) + R.psi(1) - R.kappa(1)716sage: A == R.zero()717False718sage: A == R.one()719True720"""721if op != op_EQ and op != op_NE:722raise TypeError('incomparable')723724# TODO: is there a smarter choice of ordering?725D = set(self.degree_list())726D.update(other.degree_list())727D = sorted(D)728729# NOTE: .vector(d) is much cheaper than .basis_vector(d) so we do that first730equal = all((self.vector(d) == other.vector(d) or self.basis_vector(d) == other.basis_vector(d)) for d in D)731return equal == (op == op_EQ)732733def __bool__(self):734r"""735TESTS::736737sage: from admcycles import TautologicalRing738sage: R = TautologicalRing(1, 1)739sage: bool(R.psi(1))740True741sage: bool(R.zero())742False743"""744# NOTE: .vector(d) is much cheaper than .basis_vector(d) so we do that first745return not all(self.vector(d).is_zero() or self.basis_vector(d).is_zero() for d in self.degree_list())746747def is_zero(self, moduli=None):748r"""749Return whether this class is a known tautological relation (using750Pixton's implementation of the generalized Faber-Zagier relations).751752If optional argument `moduli` is given, it checks the vanishing on753an open subset of the current moduli of stable curves.754755EXAMPLES::756757sage: from admcycles import TautologicalRing758sage: R = TautologicalRing(3, 0)759sage: diff = R.kappa(1) - 12 * R.lambdaclass(1)760sage: diff.is_zero()761False762763sage: S = TautologicalRing(3, 0, moduli='sm')764sage: S(diff).is_zero()765True766sage: diff.is_zero(moduli='sm')767True768sage: S(diff).is_zero(moduli='st')769Traceback (most recent call last):770...771ValueError: moduli='st' is larger than the moduli 'sm' of the parent772"""773P = self.parent()774moduli = get_moduli(moduli, P._moduli)775if moduli > P._moduli:776raise ValueError("moduli={!r} is larger than the moduli {!r} of the parent".format(777_moduli_to_str[moduli], _moduli_to_str[P._moduli]))778if moduli != P._moduli:779R = TautologicalRing(P._g, P._markings, moduli, base_ring=P.base_ring())780return not R(self)781else:782return not self783784def degree_list(self):785r"""786EXAMPLES::787788sage: from admcycles import TautologicalRing789sage: R = TautologicalRing(2, 3)790sage: R.psi(1).degree_list()791[1]792sage: (1 + R.psi(1)**2).degree_list()793[0, 2]794sage: ((1 + R.psi(1))**2).degree_list()795[0, 1, 2]796"""797return sorted(set(d for t in self._terms.values() for g, n, d in t.gnr_list()))798799def forgetful_pullback(self, forget=None):800r"""801Return the pullback of this tautological class under a forgetful map802`\bar M_{g, n'} --> \bar M_{g,n}`803804INPUT:805806forget : list of integers807legs that are forgotten by the map forgetful map808809EXAMPLES::810811sage: from admcycles import TautologicalRing812813sage: R = TautologicalRing(1, 2)814sage: b = R.psi(2).forgetful_pullback([3])815sage: b816Graph : [1] [[1, 2, 3]] []817Polynomial : psi_2818<BLANKLINE>819Graph : [1, 0] [[1, 4], [2, 3, 5]] [(4, 5)]820Polynomial : -1821sage: b.parent()822TautologicalRing(g=1, n=3, moduli='st') over Rational Field823"""824P = self.parent()825if forget is None:826# TODO: if elements were immutable we could return self827return self828elif isinstance(forget, numbers.Integral):829k = ZZ(forget)830if k < 0:831raise ValueError("forget can only be non-negative")832elif isinstance(forget, (tuple, list)):833k = ZZ(len(forget))834if set(forget) != set(range(P._n + 1, P._n + k + 1)):835raise ValueError('can only forget the highest markings, got forget={} inside {}'.format(forget, P))836if k == 0:837return self838839R = TautologicalRing(P._g, P._n + k, moduli=P._moduli, base_ring=P.base_ring())840return sum((R.element_class(R, c.forgetful_pullback_list(forget)) for c in self._terms.values()), R.zero())841842def forgetful_pushforward(self, forget=None):843r"""844Return the pushforward of a given tautological class under a forgetful845map `\bar M_{g,n} \to \bar M_{g,n'}`.846847INPUT:848849forget : list of integers850legs that are forgotten by the map forgetful map851852EXAMPLES::853854sage: from admcycles import TautologicalRing855856sage: R = TautologicalRing(1, 3)857sage: s1 = R.psi(3)^2858sage: s1.forgetful_pushforward([3])859Graph : [1] [[1, 2]] []860Polynomial : (kappa_1)_0861sage: s1.forgetful_pushforward([2,3])862Graph : [1] [[1]] []863Polynomial : 1864865sage: R = TautologicalRing(1, [2, 5, 6])866sage: s1 = R.psi(5)^2867sage: s1.forgetful_pushforward([5])868Graph : [1] [[2, 6]] []869Polynomial : (kappa_1)_0870sage: s1.forgetful_pushforward([5]).parent()871TautologicalRing(g=1, n=(2, 6), moduli='st') over Rational Field872873sage: R = TautologicalRing(2, 2)874sage: gens1 = R.generators(1)875sage: t = gens1[1] + 2*gens1[3]876sage: t.forgetful_pushforward([2])877Graph : [2] [[1]] []878Polynomial : 3879sage: t.forgetful_pushforward([2]).parent()880TautologicalRing(g=2, n=1, moduli='st') over Rational Field881882TESTS::883884sage: from admcycles import TautologicalRing885sage: TautologicalRing(1, [2, 4]).psi(4).forgetful_pushforward([5])886Traceback (most recent call last):887...888ValueError: invalid marking 5 in forget889sage: TautologicalRing(1, [2, 4]).psi(4).forgetful_pushforward(2)890Traceback (most recent call last):891...892ValueError: unstable pair (g,n) = (1, 0)893sage: TautologicalRing(1, [2, 4]).psi(4).forgetful_pushforward(3)894Traceback (most recent call last):895...896ValueError: forget must be between 0 and the number of markings897"""898P = self.parent()899if forget is None:900# TODO: if elements were immutable we could return self901return self.copy()902elif isinstance(forget, numbers.Integral):903k = ZZ(forget)904if k < 0 or k > P._n:905raise ValueError("forget must be between 0 and the number of markings")906if k == 0:907return self908forget = P._markings[-k:]909elif isinstance(forget, (tuple, list)):910k = ZZ(len(forget))911forget = sorted(forget)912for i in range(len(forget) - 1):913if forget[i] == forget[i + 1]:914raise ValueError('multiple marking {} in forget'.format(forget[i]))915for i in forget:916if i not in P._markings:917raise ValueError('invalid marking {} in forget'.format(i))918else:919raise TypeError('invalid input forget')920921if k == 0:922return self923924new_markings = [i for i in P._markings if i not in forget]925R = TautologicalRing(P._g, new_markings, moduli=P._moduli, base_ring=P.base_ring())926return R.element_class(R, [term.forgetful_pushforward(forget) for term in self._terms.values()])927928# TODO: we should not do inplace operation if there is no support for mutable/immutable929# (this needs some cleaning on the StableGraph side as well)930# TODO: should it really be called rename_legs? it is a bit misleading are we are just931# relabelling the markings in this function (same in decstratum and StableGraph)932def rename_legs(self, dic, rename=None, inplace=True):933r"""934Rename the legs according to the dictionary ``dic``.935936EXAMPLES::937938sage: from admcycles import TautologicalRing939940sage: R = TautologicalRing(1, 2)941sage: cl = R.psi(1) * R.psi(2)942sage: cl.rename_legs({2:5}, inplace=False)943Graph : [1] [[1, 5]] []944Polynomial : psi_1*psi_5945sage: parent(cl.rename_legs({2:5}, inplace=False))946TautologicalRing(g=1, n=(1, 5), moduli='st') over Rational Field947948Note that inplace operation is forbidden if the relabellings of the markings949is not a bijection::950951sage: _ = cl.rename_legs({2:5}, inplace=True)952Traceback (most recent call last):953...954ValueError: invalid inplace operation: change of markings (1, 2) -> (1, 5)955956TESTS::957958sage: from admcycles import *959sage: G1 = StableGraph([1,1],[[1,2,4],[3,5]],[(4,5)])960sage: A = G1.to_tautological_class()961sage: _ = A.rename_legs({1:3,3:1})962sage: list(A._terms)963[[1, 1] [[2, 3, 4], [1, 5]] [(4, 5)]]964sage: for g, t in A._terms.items():965....: assert g == t.gamma966"""967if rename is not None:968from .superseded import deprecation969deprecation(109, 'the rename keyword in TautologicalClass.rename_legs is deprecated. Please set rename=None')970971P = self.parent()972clean_dic = {i: dic.get(i, i) for i in P._markings}973new_markings = tuple(sorted(clean_dic.values()))974if new_markings != P._markings:975if inplace:976raise ValueError(977'invalid inplace operation: change of markings {} -> {}'.format(P._markings, new_markings))978R = TautologicalRing(P._g, new_markings, P._moduli, base_ring=P.base_ring())979else:980R = P981982if inplace:983l = list(self._terms.values())984self._terms.clear()985for t in l:986t.rename_legs(dic, inplace=True)987self._terms[t.gamma] = t988return self989else:990return R([t.rename_legs(dic, inplace=False) for t in self._terms.values()])991992def permutation_action(self, g):993r"""994Applies the permutation ``g`` to the markings.995996INPUT:997998g: permutation in the symmetric group `S_n`9991000EXAMPLES::10011002sage: from admcycles import TautologicalRing1003sage: R = TautologicalRing(1, 2)1004sage: G = SymmetricGroup(2)1005sage: g = G('(1,2)')1006sage: R.psi(1).permutation_action(g) == R.psi(2)1007True1008"""1009return self.rename_legs(g.dict(), inplace=False)10101011def is_symmetric(self, G=None):1012r"""1013Return whether this tautological class is symmetric under the action of G.10141015INPUT:10161017G: (optional) subgroup of symmetric group1018if no argument is given take Sn10191020EXAMPLES::10211022sage: from admcycles import TautologicalRing1023sage: R = TautologicalRing(1, 2)1024sage: (R.psi(1) * R.psi(2)).is_symmetric()1025True1026sage: R.psi(1).is_symmetric() # psi(1) = psi(2) in H^*(M_{1,2})1027True10281029sage: R = TautologicalRing(2, 2)1030sage: R.psi(1).is_symmetric()1031False10321033sage: from admcycles import TautologicalRing1034sage: R = TautologicalRing(2, 3)1035sage: G = PermutationGroup([(1,2)])1036sage: a = R.psi(1) + R.psi(2)1037sage: a.is_symmetric(G)1038True1039sage: a.is_symmetric()1040False1041"""1042if not self._terms: # if tautclass is 0, return True1043return True1044num_leg = self.parent()._n1045if G is None:1046from sage.groups.perm_gps.permgroup_named import SymmetricGroup1047G = SymmetricGroup(num_leg)1048for g in G.gens():1049if self.permutation_action(g) != self:1050return False1051return True10521053def symmetrize(self, G=None):1054r"""1055Return the symmetrization of self under the action of ``G``.10561057INPUT:10581059G: (optional) subgroup of symmetric group1060if no argument is given take Sn10611062EXAMPLES::10631064sage: from admcycles import TautologicalRing1065sage: R = TautologicalRing(2, 2)1066sage: R.psi(1).symmetrize()1067Graph : [2] [[1, 2]] []1068Polynomial : 1/2*psi_1 + 1/2*psi_21069"""1070P = self.parent()1071res = P.zero()1072if not self._terms: # if tautclass is 0, return 01073return res1074if G is None:1075from sage.groups.perm_gps.permgroup_named import SymmetricGroup1076G = SymmetricGroup(P._n)10771078return P.sum(self.permutation_action(g) for g in G) / G.cardinality()10791080def standard_markings(self):1081r"""1082Return an isomorphic tautological class where standard markings `{1, 2, ..., n}`1083are used.10841085EXAMPLES::10861087sage: from admcycles import StableGraph, TautologicalRing1088sage: R = TautologicalRing(4, [4,7])1089sage: g = StableGraph([3], [[4,7,1,2]], [(1,2)])1090sage: a = R(g, psi={4:1}) + R(g, psi={7:2}) + R(g, kappa=[[0,0,1]])1091sage: a.standard_markings()1092Graph : [3] [[1, 2, 4, 7]] [(4, 7)]1093Polynomial : psi_1 + psi_2^2 + (kappa_3)_01094sage: a.standard_markings().parent()1095TautologicalRing(g=4, n=2, moduli='st') over Rational Field1096"""1097P = self.parent()1098if P._markings and P._markings[-1] == P._n:1099return self1100return self.rename_legs({j: i + 1 for i, j in enumerate(P._markings)}, inplace=False)11011102def vector(self, r=None):1103r"""1104EXAMPLES:11051106We consider the special case of the moduli space ``g=1`` and ``n=2``::11071108sage: from admcycles import TautologicalRing, StableGraph1109sage: R = TautologicalRing(1, 2)11101111Degree zero::11121113sage: R(2/3).vector()1114(2/3)1115sage: R.from_vector([2/3], 0)1116Graph : [1] [[1, 2]] []1117Polynomial : 2/311181119Degree one examples::11201121sage: kappa1 = R.kappa(1)1122sage: kappa1.vector()1123(1, 0, 0, 0, 0)1124sage: R.from_vector([1,0,0,0,0], 1)1125Graph : [1] [[1, 2]] []1126Polynomial : (kappa_1)_011271128sage: psi1 = R.psi(1)1129sage: psi1.vector()1130(0, 1, 0, 0, 0)1131sage: gamma2 = StableGraph([0], [[1,2,3,4]], [(3,4)])1132sage: R(gamma2).vector()1133(0, 0, 0, 0, 1)11341135Some degree two examples::11361137sage: kappa2 = R.kappa(2)1138sage: kappa2.vector()1139(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)1140sage: (kappa1*psi1).vector()1141(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)1142sage: R(gamma2, kappa=[[1],[]]).vector()1143(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0)1144sage: gamma3 = StableGraph([0,0], [[1,2,3],[4,5,6]], [(3,4),(5,6)])1145sage: R(gamma3).vector()1146(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)11471148Empty class::11491150sage: R(0).vector(1)1151(0, 0, 0, 0, 0)11521153Non-standard markings::11541155sage: R = TautologicalRing(1, [4, 7])1156sage: R.psi(4).vector()1157(0, 1, 0, 0, 0)1158sage: R.from_vector((0, 1, 0, 0, 0), 1)1159Graph : [1] [[4, 7]] []1160Polynomial : psi_411611162sage: TautologicalRing(1, 1).zero().vector()1163Traceback (most recent call last):1164...1165ValueError: specify degree to obtain vector of empty class11661167TESTS:11681169Non-standard moduli::11701171sage: R = TautologicalRing(2,2,moduli='ct')1172sage: all(u.vector() == vector(QQ,23,{i:1}) for i,u in enumerate(R.generators(2)))1173True1174"""1175self = self.standard_markings()1176P = self.parent()1177if self.is_empty():1178if r is None:1179raise ValueError('specify degree to obtain vector of empty class')1180return vector(QQ, P.num_gens(r))1181if r is None:1182# assume it is homogeneous1183r = self.degree_list()1184if len(r) != 1:1185raise ValueError('for non-homogeneous term, set r to the desired degree')1186r = r[0]1187from .admcycles import converttoTautvect1188return sum(converttoTautvect(term, P._g, P._n, r, P._moduli) for term in self._terms.values())11891190def basis_vector(self, r=None, moduli=None):1191r"""1192Return a vector expressing the class in the basis of the tautological ring.11931194The corresponding basis is obtained from1195:meth:~`TautologicalRing.basis`.11961197EXAMPLES::11981199sage: from admcycles import TautologicalRing, StableGraph1200sage: R = TautologicalRing(2, 1)1201sage: gamma = StableGraph([1],[[1,2,3]],[(2,3)])1202sage: b = R(gamma)1203sage: b.basis_vector()1204(10, -10, -14)12051206sage: R.from_basis_vector((10, -10, -14), 1) == b1207True12081209sage: Rct = TautologicalRing(2, 1, moduli='ct')1210sage: Rct(b).basis_vector(1)1211(0, 0)1212sage: Rct(b).basis_vector(1,moduli='tl')1213Traceback (most recent call last):1214...1215ValueError: moduli='tl' is larger than the moduli 'ct' of the parent12161217sage: R = TautologicalRing(2, 2)1218sage: c = R.psi(1)**21219sage: for moduli in ('st', 'tl', 'ct', 'rt'):1220....: Rmod = TautologicalRing(2, 2, moduli=moduli)1221....: print(Rmod(c).basis_vector())1222(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)1223(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)1224(5/6, -1/6, 1/3, 0, 0)1225(1)1226sage: Rsm = TautologicalRing(2, 2, moduli='sm')1227sage: Rsm(c).basis_vector(2)1228()12291230Compatibility test with non-standard markings::12311232sage: R1 = TautologicalRing(1, 2)1233sage: g1 = StableGraph([0], [[1,2,3,4]], [(3,4)])1234sage: a1 = R1(g1, psi={1:1}) + R1(g1, psi={2:2}) + R1(g1, kappa=[[0,0,1]])12351236sage: R2 = TautologicalRing(1, [4,7])1237sage: g2 = StableGraph([0], [[4,7,1,2]], [(1,2)])1238sage: a2 = R2(g2, psi={4:1}) + R2(g2, psi={7:2}) + R2(g2, kappa=[[0,0,1]])12391240sage: a1.basis_vector(0) == a2.basis_vector(0)1241True1242sage: a1.basis_vector(1) == a2.basis_vector(1)1243True1244sage: a1.basis_vector(2) == a2.basis_vector(2)1245True1246sage: a1.basis_vector(3) == a2.basis_vector(3)1247True1248"""1249self = self.standard_markings()1250P = self.parent()1251moduli = get_moduli(moduli, P._moduli)1252if moduli > P._moduli:1253raise ValueError("moduli={!r} is larger than the moduli {!r} of the parent".format(1254_moduli_to_str[moduli], _moduli_to_str[P._moduli]))12551256if r is None:1257r = self.degree_list()1258if len(r) != 1:1259raise ValueError('for non-homogeneous term, set r to the desired degree')1260r = r[0]12611262if moduli != P._moduli:1263P = self.parent()1264R = TautologicalRing(P._g, P._markings, moduli, base_ring=P.base_ring())1265return R(self).basis_vector(r)12661267P = self.parent()1268from .admcycles import Tautvecttobasis1269# TODO: Tautvecttobasis uses the 'string' version of moduli1270return Tautvecttobasis(self.vector(r), P._g, P._n, r, _moduli_to_str[P._moduli])12711272###########################################################################1273# Deprecated methods from the former tautclass #1274# See https://gitlab.com/modulispaces/admcycles/-/merge_requests/109 #1275###########################################################################1276def toTautvect(self, g=None, n=None, r=None):1277from .superseded import deprecation1278deprecation(109, 'toTautvect is deprecated. Please use vector instead.')1279P = self.parent()1280if (g is not None and g != P._g) or (n is not None and n != P._n):1281raise ValueError('invalid g,n')1282return self.vector(r)12831284def toTautbasis(self, g=None, n=None, r=None, moduli='st'):1285from .superseded import deprecation1286deprecation(109, 'toTautbasis is deprecated. Please use basis_vector instead.')1287P = self.parent()1288if (g is not None and g != P._g) or (n is not None and n != P._n):1289raise ValueError('invalid g,n')1290if _str_to_moduli[moduli] != P._moduli:1291P = TautologicalRing(P._g, P._n, moduli, base_ring=P.base_ring())1292self = P(self)1293return self.basis_vector(r)12941295def gnr_list(self):1296from .superseded import deprecation1297deprecation(109, 'gnr_list is deprecated. Please use degree_list instead.')1298P = self.parent()1299return [(P._g, P._n, d) for d in self.degree_list()]13001301def coeff_subs(self, *args, **kwds):1302from .superseded import deprecation1303deprecation(109, "coeff_subs is deprecated. Please use subs instead.")1304result = self.subs(*args, **kwds)1305self._terms = result._terms1306return self130713081309class TautologicalRing(UniqueRepresentation, Algebra):1310r"""1311The tautological subgring of the (even degree) cohomology of the moduli space of curves.13121313EXAMPLES::13141315sage: from admcycles import TautologicalRing1316sage: TautologicalRing(2)1317TautologicalRing(g=2, n=0, moduli='st') over Rational Field1318sage: TautologicalRing(1, 2)1319TautologicalRing(g=1, n=2, moduli='st') over Rational Field1320sage: TautologicalRing(1, 1, 'tl')1321TautologicalRing(g=1, n=1, moduli='tl') over Rational Field13221323sage: from admcycles.moduli import MODULI_ST1324sage: TautologicalRing(0, 5, MODULI_ST, QQ)1325TautologicalRing(g=0, n=5, moduli='st') over Rational Field13261327Since tautological rings are commutative algebras one can use them as basic building1328blocks of further algebraic constructors in SageMath::13291330sage: R = TautologicalRing(2, 0)13311332sage: Rxy.<x,y> = PolynomialRing(R, ['x', 'y'])1333sage: Rxy1334Multivariate Polynomial Ring in x, y over TautologicalRing(g=2, n=0, moduli='st') over Rational Field1335sage: R.kappa(1) * x + R.kappa(2) * y # TODO: horrible display1336(Graph : [2] [[]] []1337Polynomial : (kappa_1)_0)*x + (Graph : [2] [[]] []1338Polynomial : (kappa_2)_0)*y13391340sage: V = FreeModule(R, 2)1341sage: V1342Ambient free module of rank 2 over TautologicalRing(g=2, n=0, moduli='st') over Rational Field1343sage: V((R.kappa(1), R.kappa(2))) # TODO: horrible display1344(Graph : [2] [[]] []1345Polynomial : (kappa_1)_0, Graph : [2] [[]] []1346Polynomial : (kappa_2)_0)13471348TESTS::13491350sage: from admcycles import TautologicalRing1351sage: TestSuite(TautologicalRing(1, 1)).run()1352sage: TestSuite(TautologicalRing(1, [3])).run()1353sage: TestSuite(TautologicalRing(1, 1, 'tl', QQ['x,y'])).run()13541355sage: cm = get_coercion_model()1356sage: cm.get_action(QQ, TautologicalRing(2,0))1357Left scalar multiplication by Rational Field on TautologicalRing(g=2, n=0, moduli='st') over Rational Field13581359Test for https://gitlab.com/modulispaces/admcycles/-/issues/70::13601361sage: from admcycles import TautologicalRing1362sage: W = TautologicalRing(1,1)1363sage: V = PolynomialRing(W, 'x,y')1364sage: V.one()1365Graph : [1] [[1]] []1366Polynomial : 113671368Test for https://gitlab.com/modulispaces/admcycles/-/issues/79::13691370sage: from admcycles import TautologicalRing1371sage: TautologicalRing(2, 0).gens()1372(...)1373"""13741375Element = TautologicalClass13761377@staticmethod1378def __classcall__(cls, *args, **kwds):1379g = kwds.pop('g', None)1380n = kwds.pop('n', None)1381moduli = kwds.pop('moduli', None)1382base_ring = kwds.pop('base_ring', None)1383if kwds:1384raise ValueError('unknown arguments {}'.format(list(kwds.keys())))1385if len(args) >= 1:1386if g is not None:1387raise ValueError('genus g specified twice')1388g = args[0]1389if len(args) >= 2:1390# g and n1391if n is not None:1392raise ValueError('number of marked points n specified twice')1393n = args[1]1394if len(args) >= 3:1395if moduli is not None:1396raise ValueError('moduli specified twice')1397moduli = args[2]1398if len(args) >= 4:1399if base_ring is not None:1400raise ValueError('base_ring specified twice')1401base_ring = args[3]1402if len(args) > 4:1403raise ValueError('too many arguments for TautologicalRing: {}'.format(args))14041405moduli = get_moduli(moduli)14061407if not isinstance(g, numbers.Integral) or g < 0:1408raise ValueError('g must be a non-negative integer')1409g = ZZ(g)1410if n is None:1411n = ZZ.zero()1412markings = ()1413elif isinstance(n, (tuple, list)):1414markings = [ZZ(i) for i in sorted(n)]1415n = len(markings)1416markings.sort()1417if markings and markings[0] <= 0:1418raise ValueError('markings must be positive integers')1419if any(markings[i] == markings[i + 1] for i in range(n - 1)):1420raise ValueError('repeated marking')1421markings = tuple(markings)1422elif isinstance(n, numbers.Integral):1423if n < 0:1424raise ValueError('n must be a non-negative integer')1425n = ZZ(n)1426markings = tuple(range(1, n + 1))1427else:1428raise TypeError('invalid input')14291430if base_ring is None:1431base_ring = QQ1432elif base_ring not in _CommutativeRings:1433raise ValueError('base_ring (={}) must be a commutative ring'.format(base_ring))14341435if (g, n) in [(0, 0), (0, 1), (0, 2), (1, 0)]:1436raise ValueError('unstable pair (g,n) = ({}, {})'.format(g, n))14371438return super().__classcall__(cls, g, markings, moduli, base_ring)14391440def __init__(self, g, markings, moduli, base_ring):1441r"""1442INPUT:14431444g : integer1445genus1446markings : tuple of distinct positive integers1447list of markings1448moduli : integer1449code for the moduli1450base_ring : ring1451base ring1452"""1453Algebra.__init__(self, base_ring, category=Algebras(base_ring).Commutative().FiniteDimensional())1454self._g = g1455self._markings = markings1456self._n = len(markings)1457self._moduli = moduli14581459# NOTE: even though the category contains the axiom Commutative(), is_commutative is reimplemented in sage.rings.ring.Ring1460# see https://gitlab.com/modulispaces/admcycles/-/issues/701461# see https://trac.sagemath.org/ticket/328101462def is_commutative(self):1463return True14641465@cached_method1466def standard_markings(self):1467r"""1468Return an equivalent moduli space with the standard markings `{1, 2, \ldots, n}`.14691470EXAMPLES::14711472sage: from admcycles import TautologicalRing1473sage: R = TautologicalRing(2, [4, 7])1474sage: R1475TautologicalRing(g=2, n=(4, 7), moduli='st') over Rational Field1476sage: R.standard_markings()1477TautologicalRing(g=2, n=2, moduli='st') over Rational Field1478"""1479if not self._markings or self._markings[-1] == self._n:1480return self1481return TautologicalRing(self._g, self._n, moduli=self._moduli, base_ring=self.base_ring())14821483def is_integral_domain(self):1484r"""1485Return ``False`` unless the tautological ring is supported in degree 0 and the base ring1486is an integral domain.14871488This uses that any element of positive order is torsion.14891490EXAMPLES::14911492sage: from admcycles import TautologicalRing1493sage: TautologicalRing(1,3,moduli='ct').socle_degree()149421495sage: TautologicalRing(1,3,moduli='ct').is_integral_domain()1496False1497sage: TautologicalRing(0,3).is_integral_domain()1498True1499sage: TautologicalRing(0,3,base_ring=ZZ.quotient(8)).is_integral_domain()1500False1501"""1502return self.socle_degree() == 0 and self.base_ring().is_integral_domain()15031504def is_field(self):1505r"""1506Return ``False`` unless the tautological ring is supported in degree 0 and the base ring1507is a field.15081509This uses that any element of positive order is torsion.15101511EXAMPLES::15121513sage: from admcycles import TautologicalRing1514sage: TautologicalRing(1,3,moduli='ct').socle_degree()151521516sage: TautologicalRing(1,3,moduli='ct').is_field()1517False1518sage: TautologicalRing(0,3).is_field()1519True1520sage: TautologicalRing(0,3,base_ring=ZZ.quotient(5)).is_field()1521True1522"""1523return self.socle_degree() == 0 and self.base_ring().is_field()15241525def is_prime_field(self):1526r"""1527Return ``False`` unless the tautological ring is supported in degree 0 and the base ring1528is a prime field.15291530This uses that any element of positive order is torsion.15311532EXAMPLES::15331534sage: from admcycles import TautologicalRing1535sage: TautologicalRing(1,3,moduli='ct', base_ring=GF(5)).is_prime_field()1536False1537sage: TautologicalRing(0,3,base_ring=QQ).is_prime_field()1538True1539sage: TautologicalRing(0,3,base_ring=GF(5)).is_prime_field()1540True1541"""1542return self.socle_degree() == 0 and self.base_ring().is_prime_field()15431544def _coerce_map_from_(self, other):1545r"""1546TESTS::15471548sage: from admcycles import TautologicalRing1549sage: TautologicalRing(1, 1, base_ring=QQ['x','y']).has_coerce_map_from(TautologicalRing(1, 1, base_ring=QQ))1550True15511552sage: M11st = TautologicalRing(1, 1, moduli='st')1553sage: M11ct = TautologicalRing(1, 1, moduli='ct')1554sage: M11st.has_coerce_map_from(M11ct)1555False1556sage: M11ct.has_coerce_map_from(M11st)1557True1558"""1559if isinstance(other, TautologicalRing) and \1560self._g == other._g and \1561self._markings == other._markings and \1562self._moduli <= other._moduli and \1563self.base_ring().has_coerce_map_from(other.base_ring()):1564return True15651566def construction(self):1567r"""1568Return a functorial construction (when applied to a base ring).15691570This function is mostly intended to make the tautological ring behave nicely1571with Sage ecosystem.15721573EXAMPLES::15741575sage: from admcycles import TautologicalRing1576sage: R = TautologicalRing(1, 1)1577sage: QQ['x'].gen() * R.psi(1) # indirect doctest1578Graph : [1] [[1]] []1579Polynomial : x*psi_115801581sage: A = TautologicalRing(3, 2, moduli='st', base_ring=QQ['x'])1582sage: B = TautologicalRing(3, 2, moduli='tl', base_ring=QQ)1583sage: A.psi(1) + B.psi(2) # indirect doctest1584Graph : [3] [[1, 2]] []1585Polynomial : psi_1 + psi_21586sage: (A.psi(1) + B.psi(2)).parent()1587TautologicalRing(g=3, n=2, moduli='tl') over Univariate Polynomial Ring in x over Rational Field1588"""1589return TautologicalRingFunctor(self._g, self._markings, self._moduli), self.base_ring()15901591def _repr_(self):1592if self._markings == tuple(range(1, self._n + 1)):1593return 'TautologicalRing(g={}, n={}, moduli={!r}) over {}'.format(self._g, self._n, _moduli_to_str[self._moduli], self.base_ring())1594else:1595return 'TautologicalRing(g={}, n={}, moduli={!r}) over {}'.format(self._g, self._markings, _moduli_to_str[self._moduli], self.base_ring())15961597def socle_degree(self):1598r"""1599Return the socle degree of this tautological ring.16001601The socle degree is the maximal degree whose corresponding graded1602component is non-empty. It is currently not implemented for the1603moduli of tree like curves.16041605The formulas were computed in:16061607- [GrVa01]_, [GrVa05]_ and [FaPa05]_ for compact type1608- [Lo95]_, [Fa99]_ and [FaPa00]_ for rational tail1609- [Lo95]_, [Io02]_ for smooth curves16101611EXAMPLES::16121613sage: from admcycles import TautologicalRing16141615We display below the socle degree for various `(g, n)` for the moduli of1616smooth curves, rational tails, compact types and stable curves::16171618sage: for (g,n) in [(0,3), (0,4), (0, 5), (1,1), (1,2), (2,0), (2, 1)]:1619....: dims = []1620....: for moduli in ['sm', 'rt', 'ct', 'st']:1621....: R = TautologicalRing(g, n, moduli=moduli)1622....: dims.append(R.socle_degree())1623....: print("g={} n={}: {}".format(g, n, " ".join(map(str, dims))))1624g=0 n=3: 0 0 0 01625g=0 n=4: 0 1 1 11626g=0 n=5: 0 2 2 21627g=1 n=1: 0 0 0 11628g=1 n=2: 0 1 1 21629g=2 n=0: 0 0 1 31630g=2 n=1: 1 1 2 416311632The socle degree is overestimated for tree-like::16331634sage: R = TautologicalRing(2, moduli='tl')1635sage: R.socle_degree()163631637sage: R.kappa(3).is_zero() # generator for \bar M_21638True1639"""1640return socle_degree(self._g, self._n, self._moduli)16411642@cached_method1643def trivial_graph(self):1644r"""1645Return the stable graph corresponding to the full stratum.16461647EXAMPLES::16481649sage: from admcycles import TautologicalRing1650sage: TautologicalRing(2, [1, 3, 4]).trivial_graph()1651[2] [[1, 3, 4]] []1652"""1653return StableGraph([self._g], [list(self._markings)], [], mutable=False)16541655def _an_element_(self):1656return self.one()16571658def some_elements(self):1659r"""1660Return some elements in this tautological ring.16611662This is mostly used for sage test system.16631664EXAMPLES::16651666sage: from admcycles import TautologicalRing1667sage: _ = TautologicalRing(0, [4, 5, 7]).some_elements()1668sage: _ = TautologicalRing(2, [4, 7]).some_elements()1669"""1670base_elts = [self(s) for s in self.base_ring().some_elements()]1671elts = base_elts[:2] + base_elts[-2:]1672elts.append(self.irreducible_boundary_divisor())1673if self._markings:1674elts.append(self.psi(self._markings[0]))1675if self._g > 0:1676elts.append(self.kappa(1))1677return elts16781679# TODO: if immutable, we could cache the method1680# @cached_method1681def zero(self):1682r"""1683Return the zero element.16841685EXAMPLES::16861687sage: from admcycles import TautologicalRing1688sage: TautologicalRing(0, 4).zero()168901690"""1691return self.element_class(self, [])16921693def sum(self, classes):1694r"""1695Return the sum of ``classes``.16961697EXAMPLES::16981699sage: from admcycles import TautologicalRing, psiclass1700sage: R = TautologicalRing(1, 2)1701sage: R.sum([R(1),R(1),R(0),R(1)])1702Graph : [1] [[1, 2]] []1703Polynomial : 31704sage: R.sum([psiclass(1,1,2),psiclass(2,1,2)]) == psiclass(1,1,2) + psiclass(2,1,2)1705True1706sage: R.sum([1,1])1707Graph : [1] [[1, 2]] []1708Polynomial : 217091710Your elements must be coercible into the tautological ring::17111712sage: R.sum(['a', 'b', 'c'])1713Traceback (most recent call last):1714...1715TypeError: no canonical coercion from <... 'str'> to TautologicalRing(g=1, n=2, moduli='st') over Rational Field1716"""1717result = self.zero()1718for t in classes:1719t = self.coerce(t)1720for g, term in t._terms.items():1721# TODO: implement a sum for list of KappaPsiPolynomial1722if g in result._terms:1723result._terms[g].poly += term.poly1724if not result._terms[g]:1725del result._terms[g]1726else:1727result._terms[g] = term.copy()1728return result17291730# TODO: if immutable, we could cache the method1731# @cached_method1732def fundamental_class(self):1733r"""1734Return the fundamental class as a cohomology class.17351736The fundamental class is the unit of the ring.17371738EXAMPLES::17391740sage: from admcycles import TautologicalRing1741sage: R = TautologicalRing(2, 1)1742sage: R.fundamental_class()1743Graph : [2] [[1]] []1744Polynomial : 117451746sage: R.fundamental_class() ** 2 == R.fundamental_class()1747True1748"""1749return self(self.trivial_graph())17501751one = fundamental_class17521753def psi(self, i):1754r"""1755Return the class `\psi_i` on `\bar M_{g,n}`.17561757Alternatively, you could use the function :func:`~admcycles.admcycles.psiclass`.17581759INPUT:17601761i : integer1762the leg number associated to the psi class17631764EXAMPLES::17651766sage: from admcycles import TautologicalRing, StableGraph17671768sage: R = TautologicalRing(2, 3)1769sage: R.psi(2)1770Graph : [2] [[1, 2, 3]] []1771Polynomial : psi_21772sage: R.psi(3)1773Graph : [2] [[1, 2, 3]] []1774Polynomial : psi_317751776sage: R = TautologicalRing(3, 2)1777sage: R.psi(1)1778Graph : [3] [[1, 2]] []1779Polynomial : psi_117801781TESTS::17821783sage: from admcycles import TautologicalRing17841785sage: R = TautologicalRing(3, 2)1786sage: R.psi(3)1787Traceback (most recent call last):1788...1789ValueError: unknown marking 3 for psi1790"""1791return self(self.trivial_graph(), psi={i: 1})17921793def kappa(self, a):1794r"""1795Return the (Arbarello-Cornalba) kappa-class `\kappa_a` on `\bar M_{g,n}` defined by17961797`\kappa_a= \pi_*(\psi_{n+1}^{a+1})`17981799where `pi` is the morphism `\bar M_{g,n+1} \to \bar M_{g,n}`.18001801INPUT:18021803a : integer1804the degree a of the kappa class18051806EXAMPLES::18071808sage: from admcycles import TautologicalRing18091810sage: R = TautologicalRing(3, 1)1811sage: R.kappa(2)1812Graph : [3] [[1]] []1813Polynomial : (kappa_2)_018141815sage: R = TautologicalRing(3, 2)1816sage: R.kappa(1)1817Graph : [3] [[1, 2]] []1818Polynomial : (kappa_1)_01819"""1820if a == 0:1821return (2 * self._g - 2 + self._n) * self.one()1822elif a < 0:1823return self.zero()1824else:1825return self(self.trivial_graph(), kappa=[[0] * (a - 1) + [1]])18261827def lambdaclass(self, d, pull=True):1828r"""1829Return the tautological class `\lambda_d` on `\bar M_{g,n}`.18301831The `\lambda_d` class is defined as the d-th Chern class18321833`\lambda_d = c_d(E)`18341835of the Hodge bundle `E`. The result is represented as a sum of stable1836graphs with kappa and psi classes.18371838INPUT:18391840d : integer1841the degree1842pull : boolean (optional, default to ``True``)1843whether the class is computed as pullback from `\bar M_{g}`18441845EXAMPLES::18461847sage: from admcycles import TautologicalRing18481849sage: R = TautologicalRing(2, 0)1850sage: R.lambdaclass(1)1851Graph : [2] [[]] []1852Polynomial : 1/12*(kappa_1)_01853<BLANKLINE>1854Graph : [1] [[2, 3]] [(2, 3)]1855Polynomial : 1/241856<BLANKLINE>1857Graph : [1, 1] [[2], [3]] [(2, 3)]1858Polynomial : 1/2418591860sage: R = TautologicalRing(1, 1)1861sage: R.lambdaclass(1)1862Graph : [1] [[1]] []1863Polynomial : 1/12*(kappa_1)_0 - 1/12*psi_11864<BLANKLINE>1865Graph : [0] [[3, 4, 1]] [(3, 4)]1866Polynomial : 1/2418671868TESTS::18691870sage: from admcycles import lambdaclass1871sage: inputs = [(0,0,4), (1,1,3), (1,2,1), (2,2,1), (3,2,1), (-1,2,1), (2,3,2)]1872sage: for d,g,n in inputs:1873....: R = TautologicalRing(g, n)1874....: assert (R.lambdaclass(d) - R.lambdaclass(d,pull=False)).is_zero()18751876Check for https://gitlab.com/modulispaces/admcycles/issues/58::18771878sage: R = TautologicalRing(4,0)1879sage: L = R.lambdaclass(4)1880sage: L == R.double_ramification_cycle(())1881True1882sage: L.basis_vector()1883(-229/4, 28/3, 55/24, -13/12, 1/24, 0, 0, 0, 0, 0, 0, 0, -1/2, 1/6, 1, -1/3, -1/3, 2/3, 2/3, -4/3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4/3, 1/3, -5/3, 20/3, 16/9, -4/3, 2/9, 1/6, 0, 0, 0, 0, 0, 0, 0, 0, 0)1884"""1885g = self._g1886n = self._n1887if d > g or d < 0:1888return self.zero()18891890if n > 0 and pull:1891if g == 0:1892return self.fundamental_class()1893elif g == 1:1894newmarks = list(range(2, n + 1))1895R = TautologicalRing(1, 1, moduli=self._moduli)1896return R.lambdaclass(d, pull=False).forgetful_pullback(newmarks)1897else:1898newmarks = list(range(1, n + 1))1899R = TautologicalRing(g, 0, moduli=self._moduli)1900return R.lambdaclass(d, pull=False).forgetful_pullback(newmarks)19011902return self.chern_character_to_class(d, self.hodge_chern_character)19031904def irreducible_boundary_divisor(self):1905r"""1906Return the pushforward of the fundamental class under the irreducible1907boundary gluing map `\bar M_{g-1,n+2} \to \bar M_{g,n}`.19081909EXAMPLES::19101911sage: from admcycles import *19121913sage: R = TautologicalRing(2, 5)1914sage: R.irreducible_boundary_divisor()1915Graph : [1] [[1, 2, 3, 4, 5, 6, 7]] [(6, 7)]1916Polynomial : 119171918sage: R = TautologicalRing(3, 0)1919sage: R.irreducible_boundary_divisor()1920Graph : [2] [[1, 2]] [(1, 2)]1921Polynomial : 11922"""1923if self._g == 0:1924return self.zero()1925g = self._g1926last = self._markings[-1] if self._markings else 01927G = StableGraph([g - 1], [list(self._markings) + [last + 1, last + 2]], [(last + 1, last + 2)])1928return self(G)19291930# alias1931irrbdiv = irreducible_boundary_divisor19321933def separable_boundary_divisor(self, h, A):1934r"""1935Return the pushforward of the fundamental class under the boundary1936gluing map `\bar M_{h,A} \times \bar M_{g-h,{1,...,n} \ A} \to \bar M_{g,n}`.19371938INPUT:19391940h : integer1941genus of the first vertex1942A : list1943list of markings on the first vertex19441945EXAMPLES::19461947sage: from admcycles import *19481949sage: R = TautologicalRing(2, 5)1950sage: R.separable_boundary_divisor(1, (1,3,4))1951Graph : [1, 1] [[1, 3, 4, 6], [2, 5, 7]] [(6, 7)]1952Polynomial : 119531954sage: R = TautologicalRing(3, 3)1955sage: R.separable_boundary_divisor(1, (2,))1956Graph : [1, 2] [[2, 4], [1, 3, 5]] [(4, 5)]1957Polynomial : 119581959sage: R = TautologicalRing(2, [4, 6])1960sage: R.separable_boundary_divisor(1, (6,))1961Graph : [1, 1] [[6, 7], [4, 8]] [(7, 8)]1962Polynomial : 11963"""1964g = self._g1965last = self._markings[-1] if self._markings else 11966G = StableGraph([h, g - h], [list(A) + [last + 1], sorted(set(self._markings) -1967set(A)) + [last + 2]], [(last + 1, last + 2)])1968return self(G)19691970# alias1971sepbdiv = separable_boundary_divisor19721973# TODO: if immutable, we could cache the result1974# (this was wrongly cached before merge request !109)1975# @cached_method1976def hodge_chern_character(self, d):1977r"""1978Return the chern character `ch_d(E)` of the Hodge bundle `E` on `\bar1979M_{g,n}`.19801981This function implements the formula from [Mu83]_.19821983INPUT:19841985d : integer1986The degree of the Chern character.19871988EXAMPLES::19891990sage: from admcycles import TautologicalRing1991sage: R = TautologicalRing(1, 2)1992sage: R.hodge_chern_character(1)1993Graph : [1] [[1, 2]] []1994Polynomial : 1/12*(kappa_1)_0 - 1/12*psi_1 - 1/12*psi_21995<BLANKLINE>1996Graph : [0] [[1, 2, 3, 4]] [(3, 4)]1997Polynomial : 1/241998<BLANKLINE>1999Graph : [0, 1] [[1, 2, 3], [4]] [(3, 4)]2000Polynomial : 1/122001"""2002if self._markings and self._markings[-1] != len(self._markings):2003raise NotImplementedError('hodge_chern_character unsupported with non-standard markings')20042005g = self._g2006n = self._n2007if g == 0:2008return self.zero()2009from .admcycles import list_strata2010bdries = list_strata(g, n, 1)2011irrbdry = bdries.pop(0)20122013d = ZZ(d)20142015if d == 0:2016return g * self.fundamental_class()2017elif d % 2 == 0 or d < 0:2018return self.zero()20192020from .admcycles import psicl2021psipsisum_onevert = sum(((-1)**i) * (psicl(n + 1, 1)**i) * (psicl(n + 2, 1)**(d - 1 - i)) for i in range(d))2022psipsisum_twovert = sum(((-1)**i) * (psicl(n + 1, 2)**i) * (psicl(n + 2, 2)**(d - 1 - i)) for i in range(d))20232024contrib = self.kappa(d) - sum(self.psi(i)**d for i in range(1, n + 1))20252026# old: contrib=kappaclass(d,g,n)-sum([psiclass(i,g,n) for i in range(1,n+1)])2027contrib += (QQ(1) / 2) * self(irrbdry, poly=psipsisum_onevert)2028contrib += sum(QQ((1, ind.automorphism_number())) * self(ind, poly=psipsisum_twovert) for ind in bdries)20292030contrib.dimension_filter()20312032return bernoulli(d + 1) / factorial(d + 1) * contrib20332034# TODO2035# The function below is useful in some computations and does not need to know about the parent TautologicalRing2036# I think it makes more sense as a standalone function e.g. in admcycles.py2037def chern_character_to_class(self, t, char):2038r"""2039Return the Chern class associated to the Chern character.20402041INPUT:20422043t : integer2044degree of the output Chern class2045char : tautological class or a function2046Chern character, either represented as a (mixed-degree) tautological class or as2047a function m -> ch_m20482049EXAMPLES::20502051sage: from admcycles import TautologicalRing2052sage: R = TautologicalRing(1, 1)2053sage: R.chern_character_to_class(1, R.hodge_chern_character)2054Graph : [1] [[1]] []2055Polynomial : 1/12*(kappa_1)_0 - 1/12*psi_12056<BLANKLINE>2057Graph : [0] [[3, 4, 1]] [(3, 4)]2058Polynomial : 1/2420592060Note that the output of generalized_hodge_chern takes the form of a chern character::20612062sage: from admcycles import *2063sage: from admcycles.GRRcomp import *2064sage: g=2;n=2;l=0;d=[1,-1];a=[[1,[1],-1]]2065sage: chern_char_to_class(1,generalized_hodge_chern(l,d,a,1,g,n)) # known bug2066Graph : [2] [[1, 2]] []2067Polynomial : 1/12*(kappa_1^1 )_02068<BLANKLINE>2069Graph : [2] [[1, 2]] []2070Polynomial : (-13/12)*psi_1^12071<BLANKLINE>2072Graph : [2] [[1, 2]] []2073Polynomial : (-1/12)*psi_2^12074<BLANKLINE>2075Graph : [0, 2] [[1, 2, 4], [5]] [(4, 5)]2076Polynomial : 1/12*2077<BLANKLINE>2078Graph : [1, 1] [[4], [1, 2, 5]] [(4, 5)]2079Polynomial : 1/12*2080<BLANKLINE>2081Graph : [1, 1] [[2, 4], [1, 5]] [(4, 5)]2082Polynomial : 13/12*2083<BLANKLINE>2084Graph : [1] [[4, 5, 1, 2]] [(4, 5)]2085Polynomial : 1/24*2086"""2087if isinstance(char, TautologicalClass):2088arg = [(-1)**(s - 1) * factorial(s - 1) * char.simplified(r=s) for s in range(1, t + 1)]2089else:2090arg = [(-1)**(s - 1) * factorial(s - 1) * char(s) for s in range(1, t + 1)]20912092exp = sum(multinomial(s.to_exp()) / factorial(len(s)) * prod(arg[k - 1] for k in s) for s in Partitions(t))2093if t == 0:2094return exp * self.fundamental_class()2095return exp.simplified(r=t)20962097def _check_stable_graph(self, stg):2098if stg.g() != self._g or stg.n() != self._n:2099raise ValueError('invalid stable graph (has g={}, n={} instead of g={}, n={})'.format(2100stg.g(), stg.n(), self._g, self._n))2101markings = tuple(sorted(stg.list_markings()))2102if markings != self._markings:2103raise ValueError('invalid stable graph (has markings={} instead of {})'.format(markings, self._markings))21042105def _element_constructor_(self, arg, kappa=None, psi=None, poly=None):2106r"""2107TESTS::21082109sage: from admcycles import TautologicalRing, StableGraph2110sage: R = TautologicalRing(2,1)2111sage: R(2/3)2112Graph : [2] [[1]] []2113Polynomial : 2/321142115From a stable graph with decorations::21162117sage: g = StableGraph([1], [[1,2,3]], [(2,3)])2118sage: R(g)2119Graph : [1] [[1, 2, 3]] [(2, 3)]2120Polynomial : 121212122A stable graph outside of the open set defines by the moduli gives zero::21232124sage: Rct = TautologicalRing(2, 1, moduli='ct')2125sage: Rct(g)2126021272128From a sequence of decorated strata::21292130sage: from admcycles.admcycles import decstratum2131sage: gg = StableGraph([2], [[1]], [])2132sage: arg = [decstratum(g), decstratum(gg, kappa=[[1]])]2133sage: R(arg)2134Graph : [2] [[1]] []2135Polynomial : (kappa_1)_02136<BLANKLINE>2137Graph : [1] [[1, 2, 3]] [(2, 3)]2138Polynomial : 121392140Empty argument::21412142sage: from admcycles import TautologicalRing2143sage: TautologicalRing(1, 1)([])214402145"""2146if isinstance(arg, TautologicalClass):2147P = arg.parent()2148if P._g != self._g or P._n != self._n:2149raise ValueError('incompatible moduli spaces')2150return self.element_class(self, {g: term.copy() for g, term in arg._terms.items()})2151elif not arg:2152return self.zero()2153elif isinstance(arg, StableGraph):2154self._check_stable_graph(arg)2155if arg.vanishes(self._moduli):2156return self.zero()2157if psi is not None:2158if not isinstance(psi, dict):2159raise ValueError('psi must be a dictionary')2160for i in psi:2161if not any(i in arg._legs[v] for v in range(arg.num_verts())):2162raise ValueError('unknown marking {} for psi'.format(i))2163dec = decstratum(arg, kappa=kappa, psi=psi, poly=poly)2164return self.element_class(self, [dec])2165elif isinstance(arg, (tuple, list)):2166# a sequence of decstratum2167for term in arg:2168if not isinstance(term, decstratum):2169raise TypeError('must be a sequence of decstrata')2170self._check_stable_graph(term.gamma)2171return self.element_class(self, arg)2172else:2173raise NotImplementedError('unknown argument of type arg={}'.format(arg))21742175# NOTE: replaces admcycles.Tautv_to_tautclass2176def from_vector(self, v, r):2177r"""2178Return the tautological class associated to the vector ``v`` and degree ``r`` on this tautological ring.21792180See also :meth:`~TautlogicalRing.generators` and :meth:`~TautologicalRing.from_basis_vector`.21812182EXAMPLES::21832184sage: from admcycles import TautologicalRing2185sage: R = TautologicalRing(1, 2)2186sage: R.from_vector((0, -1, 0, 1, 0), 1)2187Graph : [1] [[1, 2]] []2188Polynomial : -psi_12189<BLANKLINE>2190Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]2191Polynomial : 121922193sage: R = TautologicalRing(1, [4, 7])2194sage: R.from_vector((0, -1, 0, 1, 0), 1)2195Graph : [1] [[4, 7]] []2196Polynomial : -psi_42197<BLANKLINE>2198Graph : [0, 1] [[1, 4, 7], [5]] [(1, 5)]2199Polynomial : 122002201This also works with non-standard moduli::22022203sage: R = TautologicalRing(2,0,moduli='ct')2204sage: R.generators(1)2205[Graph : [2] [[]] []2206Polynomial : (kappa_1)_0,2207Graph : [1, 1] [[2], [3]] [(2, 3)]2208Polynomial : 1]2209sage: R.from_vector((1,2),1)2210Graph : [2] [[]] []2211Polynomial : (kappa_1)_02212<BLANKLINE>2213Graph : [1, 1] [[2], [3]] [(2, 3)]2214Polynomial : 222152216TESTS::22172218sage: R = TautologicalRing(2,0)2219sage: x = polygen(ZZ)2220sage: v = vector((x,0,0,0,0,0,0,0))2221sage: R.from_vector(v, 2).parent().base_ring()2222Univariate Polynomial Ring in x over Rational Field2223"""2224from .admcycles import Graphtodecstratum2225from . import DR2226strata = DR.all_strata(self._g, r, tuple(range(1, self._n + 1)),2227moduli_type=get_moduli(self._moduli, DRpy=True))2228stratmod = []2229if len(v) != len(strata):2230raise ValueError('input v has wrong length')2231v = vector(v)2232BR = v.parent().base_ring()2233try:2234TR = coercion_model.common_parent(self, BR)2235except TypeError:2236TR = TautologicalRing(self._g, self._n, moduli=self._moduli, base_ring=BR)2237for i in range(len(v)):2238if not v[i]:2239continue2240currstrat = Graphtodecstratum(strata[i])2241currstrat.poly *= v[i]2242stratmod.append(currstrat)2243if self._markings and self._markings[-1] != self._n:2244dic = {i + 1: j for i, j in enumerate(self._markings)}2245R = TR.standard_markings()2246return R.element_class(R, stratmod).rename_legs(dic, inplace=False)2247else:2248return TR.element_class(TR, stratmod)22492250def dimension(self, r):2251r"""2252INPUT:22532254r : integer2255the degree22562257EXAMPLES::22582259sage: from admcycles import *2260sage: R = TautologicalRing(2, 1)2261sage: [R.dimension(r) for r in range(5)]2262[1, 3, 5, 3, 1]2263sage: R = TautologicalRing(2, 1, moduli='ct')2264sage: [R.dimension(r) for r in range(5)]2265[1, 2, 1, 0, 0]2266"""2267if r < 0 or r > self.socle_degree():2268return 022692270if self._moduli == MODULI_TL:2271raise NotImplementedError('dimension not implemented for the moduli of treelike curves')22722273# TODO: is there something smarter?2274from .admcycles import generating_indices2275return len(generating_indices(self._g, self._n, r, moduli=_moduli_to_str[self._moduli]))22762277# NOTE: replaces admcycles.Tautbv_to_tautclass2278# TODO: full support for all moduli2279def from_basis_vector(self, v, r):2280r"""2281Return the tautological class of degree ``r`` corresponding to the2282vector ``v`` expressing the coefficients in the basis.22832284See also :meth:`~TautologicalRing.basis` and :meth:`~TautologicalRing.from_vector`.22852286INPUT:22872288v : vector2289r : degree22902291EXAMPLES::22922293sage: from admcycles import TautologicalRing22942295sage: R = TautologicalRing(2, 1)2296sage: R.from_basis_vector((1,0,-2), 1)2297Graph : [2] [[1]] []2298Polynomial : (kappa_1)_02299<BLANKLINE>2300Graph : [1, 1] [[3], [1, 4]] [(3, 4)]2301Polynomial : -223022303sage: R = TautologicalRing(2, [3])2304sage: R.from_basis_vector((1,0,-2), 1)2305Graph : [2] [[3]] []2306Polynomial : (kappa_1)_02307<BLANKLINE>2308Graph : [1, 1] [[1], [3, 4]] [(1, 4)]2309Polynomial : -223102311TESTS::23122313sage: for mo in ['st', 'ct', 'rt', 'sm']:2314....: R = TautologicalRing(2,2,moduli=mo)2315....: for a in R.generators(2):2316....: assert a == R.from_basis_vector(a.basis_vector(),2)2317"""2318if self._moduli == MODULI_TL:2319raise NotImplementedError('from_basis not implemented for the moduli of treelike curves')2320from .admcycles import Graphtodecstratum, generating_indices2321from . import DR2322genind = generating_indices(self._g, self._n, r, moduli=_moduli_to_str[self._moduli])2323# TODO: maybe use the method TautologicalRing.generators?2324strata = DR.all_strata(self._g, r, tuple(range(1, self._n + 1)),2325moduli_type=get_moduli(self._moduli, DRpy=True))2326stratmod = []2327if len(v) != len(genind):2328from warnings import warn2329warn('vector v has wrong length {}; should have been {}'.format(len(v), len(strata)), DeprecationWarning)2330for i in range(len(v)):2331if not v[i]:2332continue2333currstrat = Graphtodecstratum(strata[genind[i]])2334currstrat.poly *= v[i]2335stratmod.append(currstrat)23362337if self._markings and self._markings[-1] != self._n:2338dic = {i + 1: j for i, j in enumerate(self._markings)}2339R = self.standard_markings()2340return R.element_class(R, stratmod).rename_legs(dic, inplace=False)2341else:2342return self.element_class(self, stratmod)23432344# TODO: if elements were immutable, this could be cached2345def generators(self, r=None):2346r"""2347INPUT:23482349r : integer (optional)2350the degree. If provided, only returns the generators of a given degree.23512352EXAMPLES::23532354sage: from admcycles import TautologicalRing2355sage: R = TautologicalRing(2,0)2356sage: R.generators(1)2357[Graph : [2] [[]] []2358Polynomial : (kappa_1)_0,2359Graph : [1, 1] [[2], [3]] [(2, 3)]2360Polynomial : 1,2361Graph : [1] [[2, 3]] [(2, 3)]2362Polynomial : 1]2363sage: R = TautologicalRing(2,0,moduli='ct')2364sage: R.generators(1)2365[Graph : [2] [[]] []2366Polynomial : (kappa_1)_0,2367Graph : [1, 1] [[2], [3]] [(2, 3)]2368Polynomial : 1]2369sage: R = TautologicalRing(2,0,moduli='sm')2370sage: R.generators(1)2371[]23722373TESTS::23742375sage: R = TautologicalRing(1, 2)2376sage: for v in R.generators(0): print(v.vector())2377(1)2378sage: for v in R.generators(1): print(v.vector())2379(1, 0, 0, 0, 0)2380(0, 1, 0, 0, 0)2381(0, 0, 1, 0, 0)2382(0, 0, 0, 1, 0)2383(0, 0, 0, 0, 1)2384sage: for v in R.generators(2): print(v.vector())2385(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)2386(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)2387(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)2388(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)2389(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)2390(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)2391(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)2392(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)2393(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)2394(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0)2395(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0)2396(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0)2397(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0)2398(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)2399(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)24002401Test compatibility with nonstandard markings::24022403sage: R = TautologicalRing(1,[2,4])2404sage: R.generators(1)2405[Graph : [1] [[2, 4]] []2406Polynomial : (kappa_1)_0, Graph : [1] [[2, 4]] []2407Polynomial : psi_2, Graph : [1] [[2, 4]] []2408Polynomial : psi_4, Graph : [0, 1] [[1, 2, 4], [5]] [(1, 5)]2409Polynomial : 1, Graph : [0] [[1, 2, 4, 5]] [(1, 5)]2410Polynomial : 1]2411"""2412if r is None:2413result = []2414for r in range(self.socle_degree() + 1):2415result.extend(self.generators(r))2416return result24172418r = ZZ(r)2419if r < 0 or r > self.socle_degree():2420return []24212422from .DR import all_strata2423from .admcycles import Graphtodecstratum2424res = []2425if self._markings and self._markings[-1] == self._n:2426for stratum in all_strata(self._g, r, tuple(range(1, self._n + 1)), moduli_type=get_moduli(self._moduli, DRpy=True)):2427res.append(self.element_class(self, [Graphtodecstratum(stratum)]))2428else:2429for stratum in all_strata(self._g, r, tuple(range(1, self._n + 1)), moduli_type=get_moduli(self._moduli, DRpy=True)):2430res.append(self.element_class(self, [Graphtodecstratum(stratum).rename_legs({i + 1: j for i, j in enumerate(self._markings)})]))2431return res24322433# NOTE: by convention we make gens return a tuple. We do not use the generic gens2434# which performs some caching and does not allow extra arguments.2435def gens(self, r=None):2436r"""2437Return a tuple whose entries are module generators for this tautological ring.24382439INPUT:24402441r: integer (optional)2442If provided, return generators in the given degree only.24432444EXAMPLES::24452446sage: from admcycles import TautologicalRing2447sage: R = TautologicalRing(2,0)2448sage: R.gens(1)2449(Graph : [2] [[]] []2450Polynomial : (kappa_1)_0,2451Graph : [1, 1] [[2], [3]] [(2, 3)]2452Polynomial : 1,2453Graph : [1] [[2, 3]] [(2, 3)]2454Polynomial : 1)2455"""2456return tuple(self.generators(r))24572458def ngens(self, r=None):2459r"""2460Return the number of generators.24612462INPUT:24632464r: integer (optional)2465If provided, return the number of generators of the given degree.24662467EXAMPLES::24682469sage: from admcycles import TautologicalRing2470sage: R = TautologicalRing(2, 0)2471sage: R.ngens()2472292473sage: R.ngens(2)2474824752476TESTS::24772478sage: R = TautologicalRing(2,0,moduli='ct')2479sage: R.socle_degree()248012481sage: R.ngens()248232483sage: len(R.generators(1))248422485"""2486if r is None:2487return sum(self.ngens(r) for r in range(self.socle_degree() + 1))24882489r = ZZ(r)2490if r < 0 or r > self.socle_degree():2491return ZZ.zero()24922493try:2494ngens = self._ngens2495except AttributeError:2496ngens = self._ngens = [None] * (self.socle_degree() + 1)24972498from .DR import num_strata2499if ngens[r] is None:2500ngens[r] = ZZ(num_strata(self._g, r, tuple(range(1, self._n + 1)), moduli_type=get_moduli(self._moduli, DRpy=True)))25012502return ngens[r]25032504def gen(self, i=0, r=None):2505r"""2506Return the ``i``-th generator.25072508INPUT:25092510i : integer (default ``0``)2511The number of the generator.25122513r : integer (optional)2514If provided, return the ``i``-th generator in degree ``r``.25152516EXAMPLES::25172518sage: from admcycles import TautologicalRing2519sage: R = TautologicalRing(2, 0)2520sage: R.gen(23)2521Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]2522Polynomial : (kappa_1)_12523sage: [R.ngens(i) for i in range(4)]2524[1, 3, 8, 17]2525sage: R.gen(11,3)2526Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]2527Polynomial : (kappa_1)_12528sage: R.gen(2, 3)2529Graph : [2] [[]] []2530Polynomial : (kappa_1^3)_025312532TESTS::25332534sage: from admcycles import TautologicalRing2535sage: R = TautologicalRing(2, 0)2536sage: R.generators() == [R.gen(i) for i in range(R.ngens())]2537True2538sage: R.generators(3) == [R.gen(i, 3) for i in range(R.ngens(3))]2539True2540"""2541if i < 0 or i > self.ngens(r):2542raise ValueError('undefined generator')25432544if r is None:2545r = 02546while i >= self.ngens(r):2547i -= self.ngens(r)2548r += 12549return self.generators(r)[i]25502551def list_generators(self, r):2552r"""2553Lists all tautological classes of degree r in the tautological ring.25542555INPUT:25562557r : integer2558The degree r of of the classes.25592560EXAMPLES::25612562sage: from admcycles import TautologicalRing2563sage: R = TautologicalRing(2,0)2564sage: R.list_generators(2)2565[0] : Graph : [2] [[]] []2566Polynomial : (kappa_2)_02567[1] : Graph : [2] [[]] []2568Polynomial : (kappa_1^2)_02569[2] : Graph : [1, 1] [[2], [3]] [(2, 3)]2570Polynomial : (kappa_1)_02571[3] : Graph : [1, 1] [[2], [3]] [(2, 3)]2572Polynomial : psi_22573[4] : Graph : [1] [[2, 3]] [(2, 3)]2574Polynomial : (kappa_1)_02575[5] : Graph : [1] [[2, 3]] [(2, 3)]2576Polynomial : psi_22577[6] : Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]2578Polynomial : 12579[7] : Graph : [0] [[3, 4, 5, 6]] [(3, 4), (5, 6)]2580Polynomial : 12581"""2582L = self.generators(r)2583for i in range(len(L)):2584print('[' + repr(i) + '] : ' + repr(L[i]))25852586# TODO: if elements were immutable, this could be cached2587# TODO: full support for all moduli2588def basis(self, d=None):2589r"""2590Return a basis.25912592INPUT:25932594d : ``None`` (default) or integer2595if ``None`` return a full basis if ``d`` is provided, return a basis2596of the homogeneous component of degree ``d``25972598EXAMPLES::25992600sage: from admcycles import TautologicalRing2601sage: R = TautologicalRing(2, 0)2602sage: R.basis(2)2603[Graph : [2] [[]] []2604Polynomial : (kappa_2)_0,2605Graph : [2] [[]] []2606Polynomial : (kappa_1^2)_0]2607sage: R2 = TautologicalRing(2, 1, moduli='ct')2608sage: R2.basis(1)2609[Graph : [2] [[1]] []2610Polynomial : (kappa_1)_0,2611Graph : [2] [[1]] []2612Polynomial : psi_1]2613"""2614if d is None:2615result = []2616for d in range(self.socle_degree() + 1):2617result.extend(self.basis(d))2618return result26192620if self._moduli == MODULI_TL:2621raise NotImplementedError('basis not implemented for treelike curves')26222623from .admcycles import generating_indices2624gens = self.generators(d)2625return [gens[i] for i in generating_indices(self._g, self._n, d, moduli=_moduli_to_str[self._moduli])]26262627def kappa_psi_polynomials(self, d, combout=False):2628r"""2629Iterator over the polynomials in kappa and psi classes of degree ``d``.26302631For combout=True it returns a generator of triples2632(kappalist, psidict, kppoly) of26332634* a list kappalist of exponents of kappa_i,2635* a dictionary psidict sending i to the exponent of psi_i,2636* the associated TautologicalClass for this monomial.26372638EXAMPLES::26392640sage: from admcycles import TautologicalRing2641sage: for a in TautologicalRing(0, 4).kappa_psi_polynomials(1): print(a)2642Graph : [0] [[1, 2, 3, 4]] []2643Polynomial : (kappa_1)_02644Graph : [0] [[1, 2, 3, 4]] []2645Polynomial : psi_12646Graph : [0] [[1, 2, 3, 4]] []2647Polynomial : psi_22648Graph : [0] [[1, 2, 3, 4]] []2649Polynomial : psi_32650Graph : [0] [[1, 2, 3, 4]] []2651Polynomial : psi_426522653sage: for a in TautologicalRing(1, [2,7]).kappa_psi_polynomials(1): print(a)2654Graph : [1] [[2, 7]] []2655Polynomial : (kappa_1)_02656Graph : [1] [[2, 7]] []2657Polynomial : psi_22658Graph : [1] [[2, 7]] []2659Polynomial : psi_726602661Here is the option where the combinatorial data is output separately::26622663sage: for a in TautologicalRing(1, [2,7]).kappa_psi_polynomials(1, True): print(a)2664([1], {}, Graph : [1] [[2, 7]] []2665Polynomial : (kappa_1)_0)2666([], {2: 1}, Graph : [1] [[2, 7]] []2667Polynomial : psi_2)2668([], {7: 1}, Graph : [1] [[2, 7]] []2669Polynomial : psi_7)2670"""2671triv = self.trivial_graph()2672for V in IntegerVectors(d, 1 + self._n):2673psi = {i: a for i, a in zip(self._markings, V[1:]) if a}2674for kV in Partitions(V[0]):2675kappa = []2676for i, a in enumerate(kV.to_exp()):2677if not a:2678continue2679kappa.extend([0] * (i + 1 - len(kappa)))2680kappa[i] = a2681cl = self(triv, psi=psi, kappa=[kappa])2682yield (kappa, psi, cl) if combout else cl26832684def num_gens(self, r):2685r"""2686INPUT:26872688r : integer2689degree26902691TESTS::26922693sage: from admcycles import *2694sage: R = TautologicalRing(1,2)2695sage: R.num_gens(1)269652697sage: len(R.generators(1))269852699sage: R = TautologicalRing(1,2,moduli='ct')2700sage: R.num_gens(1)270142702"""2703from .DR import num_strata2704return num_strata(self._g, r, tuple(range(1, self._n + 1)), self._moduli)27052706@cached_method2707def pairing_matrix(self, d, basis=False, ind_d=None, ind_dcomp=None):2708r"""2709Computes the matrix of the intersection pairing of generators in2710degree d (rows) against generators of opposite degree (columns).27112712INPUT:27132714d : integer2715degree of the classes associated to rows27162717basis : bool (default: False)2718compute pairing of basis elements in the two degrees27192720ind_d, ind_dcomp: tuple (optional)2721lists of indices of generators in degrees d and its complementary degree27222723NOTE:27242725The matrix is returned as an immutable object, to allow caching.27262727EXAMPLES::27282729sage: from admcycles import TautologicalRing2730sage: R = TautologicalRing(1,2)2731sage: R.pairing_matrix(1)2732[ 1/8 1/12 1/12 1/24 1]2733[ 1/12 1/24 1/24 0 1]2734[ 1/12 1/24 1/24 0 1]2735[ 1/24 0 0 -1/24 1]2736[ 1 1 1 1 0]2737sage: R.pairing_matrix(1, basis=True)2738[ 1/8 1/12]2739[1/12 1/24]2740sage: R.pairing_matrix(1, ind_d=(0,1), ind_dcomp=(1,2,3))2741[1/12 1/12 1/24]2742[1/24 1/24 0]2743"""2744dcomp = self.socle_degree() - d27452746if ind_d is not None:2747allgens = self.generators(d)2748gens = [allgens[i] for i in ind_d]2749elif basis:2750gens = self.basis(d)2751else:2752gens = self.generators(d)27532754if ind_dcomp is not None:2755allcogens = self.generators(dcomp)2756cogens = [allcogens[i] for i in ind_dcomp]2757elif basis:2758cogens = self.basis(dcomp)2759else:2760cogens = self.generators(dcomp)27612762# TODO: for d = dcomp we only need to compute half of these numbers2763M = matrix(QQ, [[(a * b).evaluate() for b in cogens] for a in gens])2764M.set_immutable()2765return M27662767def double_ramification_cycle(self, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False, chiodo_coeff=False, r_coeff=None):2768r"""2769Return the k-twisted double ramification cycle in genus g and codimension d2770for the partition Avector of k*(2g-2+n). For more information see the documentation2771of :func:`~admcycles.double_ramification_cycle.DR_cycle`.27722773EXAMPLES::27742775sage: from admcycles import TautologicalRing2776sage: R = TautologicalRing(2,2)2777sage: DR = R.double_ramification_cycle([1,3], k=1, d=1)2778sage: (DR * R.psi(1)^3).evaluate()2779-11/19202780"""2781from .double_ramification_cycle import DR_cycle2782if len(Avector) != self._n:2783raise ValueError('length of argument Avector must be n')2784return DR_cycle(self._g, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False, chiodo_coeff=False, r_coeff=None, moduli=self._moduli, base_ring=self.base_ring())27852786def theta_class(self):2787r"""2788Return the class Theta_{g,n} from [Norbury - A new cohomology class on the moduli space of curves].2789For more information see the documentation of :func:`~admcycles.double_ramification_cycle.ThetaClass`.27902791EXAMPLES::27922793sage: from admcycles import TautologicalRing2794sage: R = TautologicalRing(1, 1)2795sage: R.theta_class()2796Graph : [1] [[1]] []2797Polynomial : 11/6*(kappa_1)_0 + 1/6*psi_12798<BLANKLINE>2799Graph : [0] [[3, 4, 1]] [(3, 4)]2800Polynomial : 1/242801sage: R = TautologicalRing(2, 1, moduli='ct')2802sage: R.theta_class()280302804"""2805from .double_ramification_cycle import ThetaClass2806return self(ThetaClass(self._g, self._n, moduli=self._moduli))28072808def hyperelliptic_cycle(self, n=0, m=0):2809r"""2810Return the cycle class of the hyperelliptic locus of genus g curves with n marked2811fixed points and m pairs of conjugate points in `\bar M_{g,n+2m}`.28122813For more information see the documentation of :func:`~admcycles.admcycles.Hyperell`.28142815EXAMPLES::28162817sage: from admcycles import TautologicalRing2818sage: R = TautologicalRing(2,1)2819sage: H = R.hyperelliptic_cycle(1,0)2820sage: H.forgetful_pushforward([1]).fund_evaluate()282162822"""2823if n + 2 * m != self._n:2824raise ValueError('the number n+2m must equal the total number of marked points')2825from .admcycles import Hyperell2826return self(Hyperell(self._g, n, m))28272828def bielliptic_cycle(self, n=0, m=0):2829r"""2830Return the cycle class of the bielliptic locus of genus g curves with n marked2831fixed points and m pairs of conjugate points in `\bar M_{g,n+2m}`.28322833For more information see the documentation of :func:`~admcycles.admcycles.Biell`.28342835EXAMPLES::28362837sage: from admcycles import TautologicalRing2838sage: R = TautologicalRing(1,2)2839sage: B = R.bielliptic_cycle(0,1)2840sage: B.degree_list()2841[1]2842sage: B.forgetful_pushforward([2]).fund_evaluate()284332844"""2845if n + 2 * m != self._n:2846raise ValueError('the number n+2m must equal the total number of marked points')2847from .admcycles import Biell2848return self(Biell(self._g, n, m))28492850def generalized_lambda(self, deg, l, d, a):2851r"""2852Computes the Chern class c_deg of the derived pushforward of a line bundle2853\O(D) on the universal curve C_{g,n} over the space Mbar_{g,n} of stable curves, for28542855D = l \tilde{K} + sum_{i=1}^n d_i \sigma_i + \sum_{h,S} a_{h,S} C_{h,S}28562857where the numbers l, d_i and a_{h,S} are integers, \tilde{K} is the relative canonical2858class of the morphism C_{g,n} -> Mbar_{g,n}, \sigma_i is the image of the ith section2859and C_{h,S} is the boundary divisor of C_{g,n} where the moving point lies on a genus h2860component with markings given by the set S.28612862For more information see the documentation of :func:`~admcycles.GRRcomp.generalized_lambda`.28632864EXAMPLES::28652866sage: from admcycles import TautologicalRing2867sage: R = TautologicalRing(2,1)2868sage: l = 1; d = [0]; a = []2869sage: t = R.generalized_lambda(1,l,d,a)2870sage: t.basis_vector()2871(1/2, -1/2, -1/2)2872"""2873if len(d) != self._n:2874raise ValueError('the number of entries of d equal the total number of marked points')2875from .GRRcomp import generalized_lambda2876return self(generalized_lambda(deg, l, d, a, self._g, self._n))28772878def differential_stratum(self, k, mu, virt=False, res_cond=(), xi_power=0, method='pull'):2879r"""2880Return the fundamental class of the closure of the stratum of ``k``-differentials2881with vanishing and pole orders ``mu``.28822883For more information see the documentation of :func:`~admcycles.stratarecursion.Strataclass`.28842885EXAMPLES::28862887sage: from admcycles import TautologicalRing2888sage: R = TautologicalRing(1,2)2889sage: H1 = R.differential_stratum(1,(1, -1))2890sage: H1.is_zero()2891True2892sage: H5 = R.differential_stratum(1,(5, -5))2893sage: H5.forgetful_pushforward([2]).fund_evaluate()2894242895"""2896if len(mu) != self._n:2897raise ValueError('the length of partition mu must equal the total number of marked points')2898from .stratarecursion import Strataclass2899return self(Strataclass(self._g, k, mu, virt=virt, res_cond=res_cond, xi_power=xi_power, method=method))29002901identify_class = identify_class29022903def presentation(self, generators=None, assume_FZ=True, eliminate_generators=None, output='pres'):2904r"""2905Computes a presentation of the tautological ring as a quotient of2906a polynomial ring by an ideal, where the generators of the polynomial2907ring are sent to the given generators.29082909The function returns the above ideal in a polynomial ring together with2910a ring homomorphism. Assumes FZ relations are sufficient unless otherwise2911instructed.29122913INPUT:29142915- generators (list) --- optional, if provided we will use the given2916elements as ring generators (it is the user's responsibility to check2917they generate as an algebra)2918- elimimate_generators (bool) --- optional, decides whether to keep around unnecessary ring generators.2919- output (str) --- optional. Can be::2920'pres', in which case returns a surjecion from a polynomial ring to self, and an ideal of that ring (the kernel)2921'lists', in which case it returns generators for the ideal, then lists of generators for R and a correspondng list of generators for TR.2922'fun', in which case it returns an ideal, a rung hom from free polynomial ring to self, and a function from self to free polynomial ring (a lift). This does not allow the user to choose their own basis.29232924EXAMPLES::29252926sage: from admcycles import TautologicalRing2927sage: TR = TautologicalRing(2,0)2928sage: I, f = TR.presentation()2929sage: R = I.ring()2930sage: I == R.ideal([2*R.0*R.1 + R.1^2, 40*R.0^3 - 43*R.1^3, R.1^4])2931True2932sage: R.ngens()293322934sage: I, f = TR.presentation(eliminate_generators = False)2935sage: R = I.ring()2936sage: R.ngens()2937529382939By specifying generators lambda_1 and delta_1, we can check a result by2940Faber.::29412942sage: gens = (TR.lambdaclass(1), 1/2*TR.sepbdiv(1,()))2943sage: I, f = TR.presentation(gens)2944sage: R = I.ring()2945sage: x0, x1 = R.gens()2946sage: I == R.ideal([x1^2 + x0*x1, 5*x0^3 - x0^2 * x1])2947True2948sage: r = f(x1^2 + x0*x1)2949sage: r.is_zero()2950True29512952Going to the locus of smooth curves, we check Theorem 1.1 in [CanLars]_ by Canning-Larson (we correct the exponent of kap1 in the last term).::29532954sage: TR = TautologicalRing(7, 0, moduli = 'sm')2955sage: gens = (TR.kappa(1), TR.kappa(2))2956sage: I, f = TR.presentation(gens)2957sage: R = I.ring(); R2958Multivariate Polynomial Ring in x0, x1 over Rational Field2959sage: kap1, kap2 = R.gens()2960sage: idgens = [2423*kap1^2*kap2 - 52632*kap2^2, 1152000*kap2^2 - 2423*kap1^4, 16000*kap1^3*kap2 - 731*kap1^5]2961sage: I == R.ideal(idgens)2962True29632964Theorem 1.2 in [CanLars]_ (we correct the signs in the first term).::29652966sage: TR = TautologicalRing(8, 0, moduli = 'sm')2967sage: gens = (TR.kappa(1), TR.kappa(2))2968sage: I, f = TR.presentation(gens)2969sage: R = I.ring(); R2970Multivariate Polynomial Ring in x0, x1 over Rational Field2971sage: kap1, kap2 = R.gens()2972sage: idgens = [714894336*kap2^2 - 55211328*kap1^2*kap2 + 1058587*kap1^4, 62208000*kap1*kap2^2 - 95287*kap1^5, 144000*kap1^3*kap2 - 5617*kap1^5]2973sage: I == R.ideal(idgens)2974True29752976Theorem 1.3 in [CanLars]_ (corrected the last coefficient of the third generator).::29772978sage: TR = TautologicalRing(9, 0, moduli = 'sm')2979sage: gens = (TR.kappa(1), TR.kappa(2), TR.kappa(3))2980sage: I, f = TR.presentation(gens)2981sage: R = I.ring(); R2982Multivariate Polynomial Ring in x0, x1, x2 over Rational Field2983sage: kap1, kap2, kap3 = R.gens()2984sage: idgens = [5195*kap1^4 + 3644694*kap1*kap3 + 749412*kap2^2 - 265788*kap1^2*kap2, 33859814400*kap2*kap3 - 95311440*kap1^3*kap2 + 2288539*kap1^5, 19151377*kap1^5 + 16929907200*kap1*kap2^2 - 1142345520*kap1^3*kap2, 1422489600*kap3^2 - 983*kap1^6, 1185408000*kap2^3 - 47543*kap1^6]2985sage: I == R.ideal(idgens)2986True29872988"""2989if not assume_FZ:2990return NotImplementedError("Need to insert a step checking FZ conjecture here. ")2991if output == 'fun' and generators is not None:2992return NotImplementedError('if you want an inverse, please let me choose my own generators for now. Otherwise I would have to do a bit of linear algebra... ')2993if generators is None:2994generators = self.basis()2995if eliminate_generators is None:2996eliminate_generators = True2997if eliminate_generators is None:2998eliminate_generators = False2999if output in ['lists', 'fun']:3000eliminate_generators = False3001gens = []3002gen_degree_list = []3003for gen in generators:3004if not len(gen.degree_list()) == 1:3005return NotImplementedError("So far needs to assume the generators are homogeneous. ")3006m = max(gen.degree_list())3007if not m == 0:3008gens.append(gen)3009gen_degree_list.append(m)3010T = TermOrder("wdeglex", gen_degree_list)3011ngens = len(gens)3012R = PolynomialRing(QQ, 'x', ngens, order=T)3013x = R.gens()3014maxdeg = self.socle_degree()3015LL = [WeightedIntegerVectors(maxdeg + i + 1, gen_degree_list) for i in range(max(gen_degree_list))]3016LL2 = [a for b in LL for a in b]3017# NOTE: this is not maximally efficient, I guess3018deg_ideal = R.ideal([R({tuple(a): 1}) for a in LL2])3019ideal_gens = []3020for deg in range(1, maxdeg + 1):3021exponent_tuples_to_try = WeightedIntegerVectors(deg, gen_degree_list)3022taut_monomials_to_try = [prod([gens[i]**w[i] for i in range(len(gens))]) for w in exponent_tuples_to_try]3023poly_monomials_to_try = [R({tuple(w): 1}) for w in exponent_tuples_to_try]3024# NOTE: need to specify the degree, as some elements may be zero!3025rels = matrix([m.basis_vector(deg) for m in taut_monomials_to_try]).kernel().gens()3026for r in rels:3027P = sum([r[i] * poly_monomials_to_try[i] for i in range(len(r))])3028ideal_gens.append(P)3029genlist = (R.ideal(ideal_gens) + deg_ideal).groebner_basis()3030elimlist = []3031for i in range(ngens):3032i = ngens - i - 13033elim = False3034for f in genlist:3035if not f.coefficient(x[i]) == 0 and f.coefficient(x[i]).is_constant():3036elim = True3037elimlist.append(i)3038break3039if elim:3040I = R.ideal(genlist).elimination_ideal(x[i])3041genlist = I.gens()3042if eliminate_generators:3043for i in elimlist:3044R = R.remove_var(x[i], order='degrevlex')3045gens.pop(i)3046II = R.ideal(list(genlist))3047if output == 'pres':3048Rhom = R.hom(gens, self)3049return II, Rhom3050if output == 'lists':3051return II, gens, R.gens()3052# return II, gens[i]:R.gens()[i] for i in range(ngens)}, {R.gens()[i]:gens[i] for i in range(ngens)}3053if output == 'fun':3054Rhom = R.hom(gens, self)30553056def f(tautelt):3057polyelt = 03058gennum = 03059for d in range(1, self.socle_degree()):3060vd = tautelt.basis_vector(d)3061polyelt += sum([vd[i] * R.gens()[i + gennum] for i in range(len(vd))])3062gennum += len(vd)3063return polyelt + tautelt.basis_vector(0)[0]3064return II, Rhom, f306530663067class TautologicalRingFunctor(ConstructionFunctor):3068r"""3069Construction functor for tautological ring.30703071This class is the way to implement the "promotion of base ring" (see below in the examples).30723073EXAMPLES::30743075sage: from admcycles.tautological_ring import TautologicalRing, TautologicalRingFunctor3076sage: F = TautologicalRingFunctor(1, (1,), 'st')3077sage: F(QQ)3078TautologicalRing(g=1, n=1, moduli='st') over Rational Field30793080sage: x = polygen(QQ, 'x')3081sage: (x**2 + 2) * TautologicalRing(1, 1).generators(1)[0]3082Graph : [1] [[1]] []3083Polynomial : (x^2 + 2)*(kappa_1)_03084"""3085rank = 1030863087def __init__(self, g, markings, moduli):3088Functor.__init__(self, _CommutativeRings, _CommutativeRings)3089self.g = g3090self.markings = markings3091self.moduli = moduli30923093def _repr_(self):3094return 'TautologicalRingFunctor(g={}, n={}, moduli={!r})'.format(3095self.g, self.markings, _moduli_to_str[self.moduli])30963097def _apply_functor(self, R):3098return TautologicalRing(self.g, self.markings, self.moduli, base_ring=R)30993100def merge(self, other):3101r"""3102Return the merge of two tautological ring functors.3103"""3104if isinstance(other, TautologicalRingFunctor) and \3105self.g == other.g and \3106self.markings == other.markings:3107return TautologicalRingFunctor(self.g, self.markings, min(self.moduli, other.moduli))31083109def __eq__(self, other):3110return isinstance(other, TautologicalRingFunctor) and \3111self.g == other.g and \3112self.markings == other.markings and \3113self.moduli == other.moduli31143115def __ne__(self, other):3116return not (self == other)31173118__hash__ = ConstructionFunctor.__hash__311931203121