unlisted
ubuntu20041# pylint does not know sage2from sage.structure.sage_object import SageObject # pylint: disable=import-error3from sage.misc.cachefunc import cached_method # pylint: disable=import-error4from sage.rings.rational_field import QQ # pylint: disable=import-error56import admcycles.admcycles7import admcycles.stratarecursion89import admcycles.diffstrata.elgtautclass1011from admcycles.diffstrata.auxiliary import hash_AG121314class AdditiveGenerator (SageObject):15"""16Product of Psi classes on an EmbeddedLevelGraph (of a stratum X).1718The information of a product of psi-class on an EmbeddedLevelGraph, i.e. a19leg_dict and an enhanced_profile, where leg_dict is a dictionary on the legs20leg -> exponent of the LevelGraph associated to the enhanced profile, i.e.21(profile,index) or None if we refer to the class of the graph.2223We (implicitly) work inside some stratum X, where the enhanced profile24makes sense.2526This class should be considered constant (hashable)!27"""2829def __init__(self, X, enh_profile, leg_dict=None):30"""31AdditiveGenerator for psi polynomial given by leg_dict on graph32corresponding to enh_profile in X.3334Args:35X (GeneralisedStratum): enveloping stratum3637enh_profile (tuple): enhanced profile (in X)3839leg_dict (dict, optional): dictionary leg of enh_profile -> exponent40encoding a psi monomial. Defaults to None.41"""42self._X = X43self._hash = hash_AG(leg_dict, enh_profile)44self._enh_profile = (tuple(enh_profile[0]), enh_profile[1])45self._leg_dict = leg_dict46self._G = self._X.lookup_graph(*enh_profile)47# dictionary leg -> level48# Careful! These are leg numbers on the whole graph, not on49# the graphs inside the LevelStrata!!50self._level_dict = {}51if leg_dict is not None:52for l in leg_dict:53self._level_dict[l] = self._G.LG.level_number(54self._G.LG.levelofleg(l))55self._inv_level_dict = {}56for leg in self._level_dict:57try:58self._inv_level_dict[self._level_dict[leg]].append(leg)59except KeyError:60self._inv_level_dict[self._level_dict[leg]] = [leg]6162@classmethod63def from_hash(cls, X, hash):64"""65AdditiveGenerator from a hash generated with hash_AG.6667Args:68X (GeneralisedStratum): Enveloping stratum.69hash (tuple): hash from hash_AG7071Returns:72AdditiveGenerator: AG from hash.73"""74if hash[0] is None:75leg_dict = None76else:77leg_dict = dict(hash[0])78return cls(X, (hash[1], hash[2]), leg_dict)7980def __hash__(self):81return hash(self._hash)8283def __eq__(self, other):84try:85return self._hash == other._hash86except AttributeError:87return NotImplemented8889def __repr__(self):90return "AdditiveGenerator(X=%r,enh_profile=%r,leg_dict=%r)"\91% (self._X, self._enh_profile, self._leg_dict)92# Better, but destroys tests:93# return "AdditiveGenerator(enh_profile=%r,leg_dict=%r)"\94# % (self._enh_profile, self._leg_dict)9596def __str__(self):97str = ""98if self._leg_dict is not None:99for l in self._leg_dict:100str += "Psi class %r with exponent %r on level %r * "\101% (l, self._leg_dict[l], self._level_dict[l])102str += "Graph %r" % (self._enh_profile,)103return str104105def __mul__(self, other):106"""107Multiply to psi products on the same graph (add dictionaries).108109Args:110other (AdditiveGenerator): Product of psi classes on same graph.111112Returns:113AdditiveGenerator: Product of psi classes on same graph.114115EXAMPLES::116117118Also works without legs.119120"""121# Check that other is an AdditiveGenerator for the same graph:122try:123if self._X != other._X or self._enh_profile != other._enh_profile:124return NotImplemented125other_leg_dict = other._leg_dict126except AttributeError:127return NotImplemented128# "unite" the leg_dicts:129if self._leg_dict is None:130self_leg_dict = {}131else:132self_leg_dict = self._leg_dict133if other_leg_dict is None:134other_leg_dict = {}135new_leg_dict = {l: self_leg_dict.get(l, 0) + other_leg_dict.get(l, 0)136for l in set(self_leg_dict) | set(other_leg_dict)}137return self._X.additive_generator(self._enh_profile, new_leg_dict)138139def __rmul__(self, other):140self.__mul__(other)141142def __pow__(self, n):143return self.pow(n)144145@property146def enh_profile(self):147return self._enh_profile148149@property150def psi_degree(self):151"""152Sum of powers of psi classes of self.153"""154if self._leg_dict is None:155return 0156else:157return sum(self._leg_dict.values())158159@cached_method160def dim_check(self):161"""162Check if, on any level, the psi degree is higher than the dimension.163164Returns:165bool: False if the class is 0 for dim reasons, True otherwise.166"""167# remove if degree > dim(X)168if self.degree > self._X.dim():169return False170if self.codim == 0:171# Avoid crazy infinite recursion for smooth graph :-)172return True173# for each level, check if psi product on level exceeds level dimension174for level_number in range(self.codim + 1):175assert self.level_dim(level_number) >= 0176if self.degree_on_level(177level_number) > self.level_dim(level_number):178return False179return True180181@property182def codim(self):183"""184The codimension of the graph (number of levels)185186Returns:187int: length of the profile188"""189return len(self._enh_profile[0])190191@property192def degree(self):193"""194Degree of class, i.e. codimension of graph + psi-degree195196Returns:197int: codim + psi_degree198"""199# degree = codim of graph + powers of psi classes200return self.codim + self.psi_degree201202@property203def leg_dict(self):204return self._leg_dict205206@property207def level_dict(self):208"""209The dictionary mapping leg -> level210"""211return self._level_dict212213@property214def inv_level_dict(self):215"""216The dictionary mapping level -> list of legs on level.217218Returns:219dict: level -> list of legs.220"""221return self._inv_level_dict222223@cached_method224def degree_on_level(self, level):225"""226Total degree of psi classes on level.227228Args:229level (int): (relative) level number (i.e. 0...codim)230231Raises:232RuntimeError: Raised for level number out of range.233234Returns:235int: sum of exponents of psis appearing on this level.236"""237if level not in range(self.codim + 1):238raise RuntimeError(239"Illegal level number: %r on %r" % (level, self))240try:241return sum(self._leg_dict[leg]242for leg in self._inv_level_dict[level])243except KeyError:244# no psis on this level245return 0246247def level(self, level_number):248"""249Level of underlying graph.250251Args:252level_number (int): (relative) level number (0...codim)253254Returns:255LevelStratum: Stratum at level level_number of self._G.256"""257return self._G.level(level_number)258259@cached_method260def level_dim(self, level_number):261"""262Dimension of level level_number.263264Args:265level_number (int): (relative) level number (i.e. 0...codim)266267Returns:268int: dimension of GeneralisedLevelStratum269"""270level = self._G.level(level_number)271return level.dim()272273@property274def stack_factor(self):275"""276The stack factor, that is the product of the prongs of the underlying graph277divided by the product of the ells of the BICs and the automorphisms.278279Returns:280QQ: stack factor281"""282try:283return self._stack_factor284except AttributeError:285# to get g_Gamma, we have to take the product of prongs/lcm for286# each bic:287prod = 1288for k in self._G.LG.prongs.values():289prod *= k290291p, _ = self.enh_profile292293bic_contr = 1294for i in p:295bic_contr *= self._X.bics[i].ell296297stack_factor = QQ(prod) / QQ(bic_contr *298len(self._G.automorphisms))299300self._stack_factor = stack_factor301return self._stack_factor302303@cached_method304def as_taut(self):305"""306Helper method, returns [(1,self)] as default input to ELGTautClass.307"""308return admcycles.diffstrata.elgtautclass.ELGTautClass(self._X, [309(1, self)])310311@cached_method312def is_in_ambient(self, ambient_enh_profile):313"""314Check if ambient_enh_profile is an ambient graph, i.e. self is a degeneration315of ambient_enh_profile.316317INPUT:318319ambient_enh_profile: tuple320An enhanced profile.321322OUTPUT:323324True if there exists a leg map, False otherwise.325"""326return self._X.is_degeneration(self._enh_profile, ambient_enh_profile)327328@cached_method329def pow(self, n, amb=None):330"""331Recursively calculate the n-th power of self (in amb), caching all results.332333Args:334n (int): exponent335amb (tuple, optional): enhanced profile. Defaults to None.336337Returns:338ELGTautClass: self^n in CH(amb)339"""340if amb is None:341ONE = self._X.ONE342amb = ((), 0)343else:344ONE = self._X.taut_from_graph(*amb)345if n == 0:346return ONE347return self._X.intersection(self.as_taut(), self.pow(n - 1, amb), amb)348349@cached_method350def exp(self, c, amb=None, stop=None):351"""352exp(c * self) in CH(amb), calculated via exp_list.353354Args:355c (QQ): coefficient356amb (tuple, optional): enhanced profile. Defaults to None.357stop (int, optional): cut-off. Defaults to None.358359Returns:360ELGTautClass: the tautological class associated to the361graded list exp_list.362"""363# graded pieces are already reduced:364new_taut_list = []365for T in self.exp_list(c, amb, stop):366new_taut_list.extend(T.psi_list)367return admcycles.diffstrata.elgtautclass.ELGTautClass(368self._X, new_taut_list, reduce=False)369370@cached_method371def exp_list(self, c, amb=None, stop=None):372"""373Calculate exp(c * self) in CH(amb).374375We calculate exp as a sum of powers (using self.pow, i.e. cached)376and check at each step if the power vanishes (if yes, we obviously stop).377378The result is returned as a list consisting of the graded pieces.379380Optionally, one may specify the cut-off degree using stop (by381default this is dim + 1).382383Args:384c (QQ): coefficient385amb (tuple, optional): enhanced profile. Defaults to None.386stop (int, optional): cut-off. Defaults to None.387388Returns:389list: list of ELGTautClasses390"""391c = QQ(c)392if amb is None:393ONE = self._X.ONE394amb = ((), 0)395else:396ONE = self._X.taut_from_graph(*amb)397e = [ONE]398f = ONE399coeff = QQ(1)400k = QQ(0)401if stop is None:402stop = self._X.dim() + 1403while k < stop and f != self._X.ZERO:404k += 1405coeff *= c / k406f = self.pow(k, amb)407e.append(coeff * f)408return e409410def pull_back(self, deg_enh_profile):411"""412Pull back self to the graph associated to deg_enh_profile.413414Note that this returns an ELGTautClass as there could be several maps.415416More precisely, we return the sum over the pulled back classes divided417by the number of undegeneration maps.418419Args:420deg_enh_profile (tuple): enhanced profile of graph to pull back to.421422Raises:423RuntimeError: raised if deg_enh_profile is not a degeneration of the424underlying graph of self.425426Returns:427ELGTautClass: sum of pullbacks of self to deg_enh_profile for each428undegeneration map divided by the number of such maps.429430"""431if self._leg_dict is None:432# trivial pullback433return admcycles.diffstrata.elgtautclass.ELGTautClass(434self._X, [(1, self._X.additive_generator(deg_enh_profile))])435else:436leg_maps = self._X.explicit_leg_maps(437self._enh_profile, deg_enh_profile)438if leg_maps is None:439raise RuntimeError("Pullback failed: %r is not a degeneration of %r")\440% (deg_enh_profile, self._enh_profile)441psi_list = []442aut_factor = QQ(1) / QQ(len(leg_maps))443for leg_map in leg_maps:444new_leg_dict = {leg_map[l]: e for l,445e in self._leg_dict.items()}446psi_list.append(447(aut_factor, self._X.additive_generator(448deg_enh_profile, new_leg_dict)))449return admcycles.diffstrata.elgtautclass.ELGTautClass(450self._X, psi_list)451452def psis_on_level(self, l):453"""454The psi classes on level l of self.455456Args:457l (int): level, i.e. 0,...,codim458459Returns:460dict: psi dictionary on self.level(l).smooth_LG461"""462L = self.level(l)463# The psi classes on this level should be expressed in terms of the legs464# of the smooth_LG of L:465EG = L.smooth_LG466try:467# Careful: the legs of the smooth_LG are numbered 1,...,n468# The psi classes are still numbered inside the whole graph469# The conversion runs through the embedding of the LevelStratum470# and back through the embedding of smooth_LG (dmp_inv)471psis = {EG.dmp_inv[L.leg_dict[leg]]: self.leg_dict[leg]472for leg in self.inv_level_dict[l]}473except KeyError:474# no psis on this level475psis = {}476return psis477478def evaluate(self, quiet=False, warnings_only=False,479admcycles_output=False):480"""481Evaluate self (cap with the fundamental class of self._X).482483Note that this gives 0 if self is not a top-degree class.484485Evaluation works by taking the product of the evaluation of each level486(i.e. evaluating, for each level, the psi monomial on this level) and487multiplying this with the stack factor.488489The psi monomials on the levels are evaluated using admcycles (after490removing residue conditions).491492Raises a RuntimeError if there are inconsistencies with the psi degrees493on the levels.494495INPUT:496497quiet: boolean (optional)498If set to true, then get no output. Defaults to False.499500warnings_only: boolean (optional)501If set to true, then output warnings. Defaults to False.502503admcycles_output: boolean (optional)504If set to true, prints debugging info (used when evaluating levels). Defaults to False.505506OUTPUT:507508the integral of self on X as a rational number.509"""510if self.degree < self._X.dim():511if not quiet or warnings_only:512print("Warning: %r is not of top degree: %r (instead of %r)" %513(self, self.degree, self._X.dim()))514return 0515level_list = []516for l in range(self.codim + 1):517if self.degree_on_level(l) < self.level_dim(l):518raise RuntimeError(519"%r is of top degree, but not on level %r" % (self, l))520L = self.level(l)521value = L.evaluate(522psis=self.psis_on_level(l),523quiet=quiet,524warnings_only=warnings_only,525admcycles_output=admcycles_output)526if value == 0:527return 0528level_list.append(value)529# product over levels:530prod = 1531for p in level_list:532prod *= p533if not quiet:534print("----------------------------------------------------")535print("Contribution of Additive generator:")536print(self)537print("Product of level-wise integrals: %r" % prod)538print("Stack factor: %r" % self.stack_factor)539print("Total: %r" % (prod * self.stack_factor))540print("----------------------------------------------------")541return self.stack_factor * prod542543def to_prodtautclass(self, relabel=False):544"""545Transform self into an admcycles prodtautclass on the underlying stgraph of self.546547Note that this gives the pushforward to M_g,n in the sense that we multiply with548Strataclass and remove all residue conditions.549550Returns:551prodtautclass: the prodtautclass of self, multiplied with the Strataclasses of552the levels and all residue conditions removed.553554EXAMPLES::555556sage: from admcycles.diffstrata import *557sage: X=Stratum((2,))558sage: X.additive_generator(((),0)).to_prodtautclass()559Outer graph : [2] [[1]] []560Vertex 0 :561Graph : [2] [[1]] []562Polynomial : -7/24*(kappa_1)_0 + 79/24*psi_1563<BLANKLINE>564<BLANKLINE>565Vertex 0 :566Graph : [1] [[1, 3, 4]] [(3, 4)]567Polynomial : -1/48568<BLANKLINE>569<BLANKLINE>570Vertex 0 :571Graph : [1, 1] [[3], [1, 4]] [(3, 4)]572Polynomial : -19/24573sage: from admcycles.stratarecursion import Strataclass574sage: X=GeneralisedStratum([Signature((4,-2,-2))], res_cond=[[(0,1)], [(0,2)]])575sage: (X.additive_generator(((),0)).to_prodtautclass().pushforward() - Strataclass(1, 1, [4,-2,-2], res_cond=[2])).is_zero()576True577578579TESTS::580581sage: from admcycles import diffstrata, psiclass582sage: X = diffstrata.generalisedstratum.GeneralisedStratum(sig_list = [diffstrata.sig.Signature(tuple([8,-3,-2,-3]))], res_cond = [[(0,1)],[(0,2)]])583sage: X.psi(1).evaluate()5849585sage: v = X.ONE.to_prodtautclass().pushforward()586sage: (v*psiclass(1,1,4)).evaluate()5879588589We noticed the problem that the markings of the prodtautclass of an AdditiveGenerator are usually not the standard one,590and hence its pushforward to M_g,n is not comparable to that of other AdditiveGenerator. Thus we solve this591by adding the keyword "relabel"::592593sage: X=diffstrata.generalisedstratum.Stratum((4,-2))594sage: X.additive_generator(((1,),0)).to_prodtautclass(relabel=True)595Outer graph : [1, 0] [[3, 4], [1, 2, 5, 6]] [(3, 5), (4, 6)]596Vertex 0 :597Graph : [1] [[1, 2]] []598Polynomial : 1/2599Vertex 1 :600Graph : [0] [[1, 2, 3, 4]] []601Polynomial : psi_4602<BLANKLINE>603<BLANKLINE>604Vertex 0 :605Graph : [1] [[1, 2]] []606Polynomial : 1/2607Vertex 1 :608Graph : [0, 0] [[2, 3, 9], [1, 4, 12]] [(9, 12)]609Polynomial : 3610<BLANKLINE>611<BLANKLINE>612Vertex 0 :613Graph : [1] [[1, 2]] []614Polynomial : 1/2615Vertex 1 :616Graph : [0, 0] [[3, 4, 9], [1, 2, 12]] [(9, 12)]617Polynomial : -3618619620We fixed the problem that when the ELGTautClass arised from removing residue conditions of a level stratum has only one term, the621original algorithm would break down. We can test it::622623sage: X=diffstrata.generalisedstratum.GeneralisedStratum([diffstrata.sig.Signature((0,-1,-1)),diffstrata.sig.Signature((0,-1,-1))], res_cond=[[(0,1),(1,1)]])624sage: X.additive_generator(((),0)).to_prodtautclass()625Outer graph : [0, 0] [[1, 2, 3], [4, 5, 6]] []626Vertex 0 :627Graph : [0] [[1, 2, 3]] []628Polynomial : 1629Vertex 1 :630Graph : [0] [[1, 2, 3]] []631Polynomial : 1632633"""634LG = self._G.LG635stgraph = LG.stgraph636if any(self.level(l).zeroStratumClass()637for l in range(self.codim + 1)):638return admcycles.admcycles.prodtautclass(stgraph, terms=[]) # ZERO639640if len(LG.genera) == 1 and self._X.res_cond == []: # just some stratum class without res_cond641adm_psis = admcycles.admcycles.decstratum(stgraph, psi=self.leg_dict)642adm_psis_taut = admcycles.admcycles.tautclass([adm_psis])643sig = self._X._sig_list[0].sig644g = self._X._sig_list[0].g645stratum_class = admcycles.stratarecursion.Strataclass(g, 1, sig)646result = admcycles.admcycles.prodtautclass(stgraph, protaut=[adm_psis_taut * stratum_class])647return result648649# Now, we are dealing with nontrivial graphs650alpha = [] # prodtautclasses on levels651vertices = [] # embedding of level into stgraph652for l in range(self.codim + 1):653psis = self.psis_on_level(l) # the psi classes on level l654T = self.level(l).remove_res_cond(psis)655656# turn to the to_prodtautclass of ELGTautClass which will recursively be solved657alpha.append(T.to_prodtautclass())658vertices.append(LG.verticesonlevel(LG.internal_level_number(l)))659prod = self.stack_factor * admcycles.admcycles.prodtautclass(stgraph)660661for l, ptc in enumerate(alpha):662prod = prod.factor_pullback(vertices[l], ptc) # returns product (!)663664if relabel:665# we standardise the marking of the EmbeddedLevelGraph and leg_dict for psi666standard_legdict = self._G.standard_markings()667newEmbLG = self._G.relabel(standard_legdict, tidyup=False)668stgraph = newEmbLG.LG.stgraph669670# get the prodtaut under the standardised stable graph671prod = admcycles.admcycles.prodtautclass(stgraph, prod.terms)672673return prod674675676