unlisted
ubuntu2004# -*- coding: utf-8 -*-1r""" Recursive computation of strata of k-differentials23This is following the papers [FaPa18]_ by Farkas-Pandharipande and [Sch18]_ by Schmitt.4"""56from collections.abc import Iterable7from copy import deepcopy8import itertools910from sage.misc.misc import subsets11from sage.combinat.integer_vector import IntegerVectors12from sage.combinat.partition import Partitions13from sage.misc.misc_c import prod14from sage.rings.all import ZZ15from sage.misc.cachefunc import cached_function16from sage.combinat.words.word import Word17from sage.functions.other import ceil1819from .stable_graph import StableGraph20from admcycles.double_ramification_cycle import DR_cycle21import admcycles.diffstrata.sig22import admcycles.diffstrata.generalisedstratum23import admcycles.diffstrata.spinstratum24from .tautological_ring import TautologicalRing2526# Given a genus g, an order k and a partition mu of k(2g-2), twistenum27# returns a list of graphs with twists2829# Elements of this list are of the following form30# [31# [g_0, [markings of v_0] ],32# [g_1, [markings of v_1], #edges of v_1, [twists of edges to v_1] ],33# ...,34# [g_r, [markings of v_r], #edges of v_r, [twists of edges to v_r] ]35# ]36# NOTE: Twists are counted in multiples of k, so a twist of 3 actually means 3k3738# ordering of graphs (always descending)39# genus g0 of central vertex > outlying vertex data40# ordering of outlying vertices41# total genus > genus > total edge twist424344def classes(l):45"""46INPUT:4748- l -- a list4950EXAMPLES::5152sage: from admcycles.stratarecursion import classes53sage: classes([])54[]55sage: classes([4,4,3,3,3,1,1])56[[0, 1], [2, 3, 4], [5, 6]]57"""58if not l:59return []60indices = []61current = [0]62currentitem = l[0]63for i in range(1, len(l)):64if l[i] == currentitem:65current += [i]66else:67indices += [current]68current = [i]69currentitem = l[i]70indices += [current]71return indices727374def SetPart(S, s):75"""76Return a list of partitions of the set S into the disjoint union of s sets.7778A partition is a list of sets.7980EXAMPLES::8182sage: from admcycles.stratarecursion import SetPart83sage: S = set(['b','o','f'])84sage: SetPart(S,2)85[[set(), {'b', 'f', 'o'}],86...87[{'b', 'f', 'o'}, set()]]88sage: SetPart(S,1)89[[{'b', 'f', 'o'}]]90sage: SetPart(S,-1)91[]92sage: SetPart(S,0)93[]94"""95if s < 0:96return []97if s == 0:98if S:99return []100else:101return [[]]102if s == 1:103return [[S]]104105resu = []106for T in subsets(S):107for r in SetPart(S - set(T), s - 1):108resu.append([set(T)] + r)109return resu110111112def twistenum(g, k, mu):113k = ZZ(k)114lis = [] # list of all graphs, returned in the end115116# collect all negative or non-divisible-by-k markings in A, all others in B117A = []118B = []119for i in range(len(mu)):120if mu[i] < 0 or (mu[i] % k != 0):121A.append(i)122else:123B.append(i)124Atot = sum([mu[i] for i in A])125126for g0 in range(g + 1): # g0 = genus of center vertex127# iterate over possible partitions of the rest of the genera to vertices128for totgenlist in Partitions(g - g0):129# list containing lists of indices with equal total genus130cl = classes(totgenlist)131132# we can have 1 up to total_genus many edges, distribute the len(c) many possibilities to those133# each of the len(c) vertices can have 1, ..., total_genus many edges; vector (1,0,2) means134# 1 vertex has 1 edge, no vertex has 2 edges, 2 vertices have 3 edges135par = [IntegerVectors(len(c), totgenlist[c[0]]) for c in cl]136137for p in itertools.product(*par):138# gra contains the information that will eventually be added to lis139gra = [[g0, A]]140numed = 0 # total number of edges141for j in range(len(cl)):142# p[j] is now an integer vector, encoding edge numbers of vertices c in cl[j] with totgenlist[c[0]] total genus143for z in range(len(p[j])):144gra += [[totgenlist[cl[j][0]] - z, [], z + 1, []]145for _ in range(p[j][z])]146numed += p[j][z] * (z + 1)147148# iterate over additional markings sent to v_0149for markings0 in subsets(B):150# vertex v0 unstable151if g0 == 0 and numed + len(A) + len(markings0) <= 2:152continue153Btot = sum([mu[i] for i in markings0])154if not k.divides(Atot + Btot):155continue156157Brest = [b for b in B if b not in markings0]158159Itotal = (Atot + Btot) // k - 2 * numed - (2 * g0 - 2)160161# Itotal is the total number of ADDITIONAL twists k that can be distributed to the various edges162# (all have k as default anyway, so each edge has at least a pole of order 2*k on the central vertex)163164# TODO: break if Itotal<0165166vertcl = classes(gra)167168for coarsesttwistdist in IntegerVectors(Itotal, len(vertcl) - 1):169# distribute the additional twists on the classes of outlying vertices170twist = []171# twist will be a list for every class of outlying vertex recording a list of all possible twist->edge distr.172# element of the list for the class {3,4,5} have the form173# [[a,b],[c],[d,e,f]] if v_3 has two edges, v_4 one and v_5 three174175for i in range(len(vertcl) - 1):176# for each of the classes collect the possibilities to distribute inside this class177vertcltwistlist = []178# will be added as an element to twist179for coarsetwistdist in Partitions(coarsesttwistdist[i] + len(vertcl[i + 1]), length=len(vertcl[i + 1])):180# coarsetwistdist describes how twists are distributed to the vertices in vertcl[i+1]181# artificially added ones to be able to use Partitions182183inditwist = [Partitions(184coarsetwistdist[v] + gra[vertcl[i + 1][v]][2] - 1, length=gra[vertcl[i + 1][v]][2]) for v in range(len(vertcl[i + 1]))]185# list for every vertex in the class giving all possible twists of the edges of this vertex186187vertcltwistlist += itertools.product(188*inditwist)189190twist.append(vertcltwistlist)191for inde in itertools.product(*twist):192# inde is now of the form [ [[a,b], [c], [d,e,f]] , [[g], [h,i]], ... ]193grap = deepcopy(gra)194# this will be updated now with the edge twists determined above and the markings sent to v_0195grap[0][1] += markings0196count = 1197for i in inde:198for j in i:199grap[count][3] = j200count += 1201202twicla = classes(grap)203# print((twicla,len(twicla)-1,Brest))204205for pa in SetPart(set(Brest), len(twicla) - 1):206mpar = [SetPart(pa[c], len(twicla[c + 1]))207for c in range(len(twicla) - 1)]208if not mpar and Brest:209continue210for part in itertools.product(*mpar):211# part is now of the form [ [ set([0,2]), set([1]) ] , [ set([3]) ] , ... ]212graph = deepcopy(grap)213count = 1214adm = True # check if graph satisfies twist conditions at all vertices215for i in part:216for j in i:217# we are now at the vertex v_count and need to check if the twist-condition is satisfied218graph[count][1] = list(j)219if (2 * graph[count][0] - 2) * k + k * sum(-l + 1 for l in graph[count][3]) != sum(mu[l] for l in graph[count][1]):220adm = False221count += 1222if adm:223sgraph = (224(graph[0][0], tuple(sorted(m + 1 for m in graph[0][1]))),)225sgraph += tuple(sorted(((gv, tuple(sorted(m + 1 for m in marki)), tuple(226sorted(k * t for t in etwist))) for gv, marki, enu, etwist in graph[1:])))227lis.append(sgraph)228229return list(set(lis))230231232def Strataclass(g, k, mu, virt=False, res_cond=(), xi_power=0, method='pull', spin=False, spin_conj=False):233r"""234Returns the fundamental class of the closure of the stratum of k-differentials in genus g in Mbar_{g,n}235with vanishing and pole orders mu.236237The class is computed using a formula from papers by Farkas-Pandharipande and Schmitt,238proven by [HoSc]_ and [BHPSS]_.239The formula for differentials with residue conditions is based on unpublished work240relying on [BaChGeGrMo]_.241242If the mu is of spin type, we can compute the spin stratum class H^+ - H^-. In [CSS21]_, Costantini,243Sauvaget and Schmitt made a similar conjecture for the Pixton formula.244245INPUT:246247- ``g`` -- integer ; genus of the curves248249- ``k`` -- integer ; power of the canonical line bundle in definition of stratum250251- ``mu`` -- tuple ; tuple of integers of length n giving zero and pole multiplicities252of k-differential, required to sum up to k*(2g-2)253254- ``virt`` -- bool (default: `False`); if True, k=1 and all entries of mu nonnegative, this255computes the virtual codimension g class supported on the codimension g-1 stratum of256holomorphic 1-differentials.257258- ``res_cond`` -- tuple (default: `()`); tuple of residue conditions. Each entry of259res_cond can be of one of the following two types:260261- an integer i from 1, ..., n indicating a marking pi with mu[i]<0, such that the262differential eta is required to have a pole with vanishing residue at the marking.263264- a tuple (c1, ..., cn) of rational numbers indicating that a condition265c1 * Res_{p1}(eta) + ... + cn * Res_{pn}(eta) = 0266is imposed. Currently only implemented for ci in {0,1}.267268The function then computes the class of the closure of the locus of smooth curves269having such a differential eta. Currently only implemented for k=1.270271- ``xi_power`` -- integer (default: `0`); if positive, returns the pushforward of272the corresponding power of the first Chern class xi = c_1(O(-1)) of the tautological273bundle on the moduli space of multi-scale differentials from [BCGGM3].274Currently only implemented for k=1 and with method = 'diffstrata'.275276- ``method`` -- string (default: `'pull'`); when computing a stratum of 1-differentials277with residue conditions, there are two choices here: 'pull' will compute it via boundary278pullbacks of higher genus strata, 'diffstrata' will use the package `diffstrata`, which279iteratively replaces residue conditions with equivalent divisorial conditions. The two280results should be equal.281282- ``spin`` -- bool (default: `False`); if true, we will compute the spin stratum class283284- ``spin_conj`` -- bool (default: `False`); if we assume the conjecture in [CSS21]_ to be true,285we will compute the spin stratum class by first compute the holomorphic strata of the same genus,286then use the conjecture to recursively compute the input stratum class287288WARNING::289290Imposing residue conditions at poles of high order leads to very long computations,291since the method works by computing strata of differentials on high-genus moduli292spaces.293294TESTS::295296sage: from admcycles import Hyperell, Biell297sage: from admcycles.stratarecursion import Strataclass298sage: L=Strataclass(2,1,(3,-1)); L.is_zero()299True300sage: L=Strataclass(3,1,(5,-1)); L.is_zero() # doctest: +SKIP301True302sage: L=Strataclass(2,1,(2,)); (L-Hyperell(2,1)).is_zero()303True304305In g=2, the locus Hbar_2(2,2) decomposes into the codimension 1 set of Hbar_2(1,1) and the306codimension 2 set of curves (C,p,q) with p,q Weierstrass points. The latter is equal to the cycle307Hyperell(2,2). We can obtain it by subtracting the virtual cycle for the partition (1,1) from the308virtual cycle for the partition (2,2)::309310sage: H1 = Strataclass(2, 2, (2, 2), virt = True)311sage: H2 = Strataclass(2, 1, (1, 1), virt = True)312sage: T = H1 - H2 - Hyperell(2, 2)313sage: T.is_zero()314True315316In g=1, the locus Hbar_1(2,-2) is the locus of curves (E,p,q) with p-q being 2-torsion in E.317Equivalently, this is the locus of bielliptic curves with a pair of bielliptic conjugate points::318319sage: (Strataclass(1,1,(2,-2)) - Biell(1,0,1)).is_zero()320True321322Some tests of computations involving residue conditions::323324sage: from admcycles import Strataclass325sage: OmegaR = Strataclass(1,1,(6,-4,-2),res_cond=(3,))326sage: OmegaRalt = Strataclass(1,1,(6,-4,-2),res_cond=(2,)) # long time327sage: (OmegaR - OmegaRalt).is_zero() # long time328True329sage: (OmegaR.forgetful_pushforward([2,3])).fund_evaluate()33042331sage: a=4; (a+2)**2 + a**2 - 10 # formula from [Castorena-Gendron, Cor. 5.5]33242333sage: OmegaR2 = Strataclass(1,1,(4,-2,-2),res_cond=(3,))334sage: (OmegaR2.forgetful_pushforward([2,3])).fund_evaluate()33510336sage: OmegaR3 = Strataclass(1,1,(5,-3,-2),res_cond=(2,)) # not tested337sage: (OmegaR3.forgetful_pushforward([2,3])).fund_evaluate() # not tested33824339sage: a=3; (a+2)**2 + a**2 - 10 # formula from [Castorena-Gendron, Cor. 5.5] # not tested34024341sage: OmegaR5 = Strataclass(2,1,(5,-1,-2),res_cond=(3,)) # not tested342sage: OmegaR5.is_zero() # vanishes by residue theorem # not tested343True344345We can also check that the two ways of computing residue conditions (via pullbacks and346via the package diffstrata) coincide::347348sage: a = Strataclass(1,1,(4,-2,-2), res_cond=(2,))349sage: b = Strataclass(1,1,(4,-2,-2), res_cond=(2,), method='diffstrata')350sage: (a-b).is_zero()351True352353The following computes the locus of genus 1 curves admitting a differential with multiplicity354vector (8,-2,-2,-2,-2) at the markings such that the sum of residues at p2 and p3 equals zero::355356sage: c = Strataclass(1,1,(8,-2,-2,-2,-2), res_cond=((0,1,1,0,0),))357358Using the parameter xi_power, we can observe an interesting relationship between strata with359xi-insertions and higher terms in Pixton's formula of the double ramification cycle::360361sage: from admcycles import DR_cycle362sage: g=0; mu = [11,4,-8,-2,-5,-2]; A = [a+1 for a in mu]363sage: n = len(mu); adddeg = 1364sage: D = DR_cycle(g,A,rpoly=True,d=g+adddeg,chiodo_coeff=True)365sage: v = vector(QQ,[a[adddeg] for a in D.basis_vector()])366sage: S = Strataclass(g,1,mu,xi_power=adddeg)367sage: print(v); print(-S.basis_vector())368(0, 0, -4, 7, 2, 4, 2, 0, -1, 0, 0, 0, 0, 2, 0, 2)369(0, 0, -4, 7, 2, 4, 2, 0, -1, 0, 0, 0, 0, 2, 0, 2)370371We can use the function to compute spin stratum class, with or without the assumption of the372conjecture of spin Pixton formula::373374sage: cl1=Strataclass(2,1,(8,-4,-2),spin=True)375sage: cl1.basis_vector()376(-1107/2, 369/2, -246, -552, -899/2, 288, 590, 807/2, 1254, 1167/2, 1947/2, 613, -793, -1354, 351, -615, -666, 939/2, -563, -2077/2, 1302, 1107/2, -2583/2, 369/2, 21, -165, -181, -369/2, -201/2, 201/2, 551/2, -31/2, -155/2, -203/2, 18, -18, 114, 119/2, -58, 102, -102, -109/2, -76, 142)377sage: cl2=Strataclass(2,1,(8,-4,-2),spin=True,spin_conj=True)378sage: cl1==cl2379True380381If we assume the conjecture above, we can also compute spin strata of meromorphic k-differentials382for k > 1. Below we check that they restrict under a boundary gluing map as expected::383384sage: from admcycles import StableGraph385sage: from admcycles.admcycles import prodtautclass386sage: H = Strataclass(2,3,[10,-4],spin=True,spin_conj=True)387sage: gamma = StableGraph([1,1],[[1,3],[2,4]],[(3, 4)])388sage: pb = gamma.boundary_pullback(H)389sage: H1 = Strataclass(1,3,[10,-10],spin=True,spin_conj=True)390sage: H2 = Strataclass(1,3,[-4,4],spin=True,spin_conj=True)391sage: prot = prodtautclass(gamma, protaut=[H1, H2])392sage: pb.totensorTautbasis(2) == prot.totensorTautbasis(2)393True394"""395n = len(mu)396R = TautologicalRing(g, n)397k = ZZ(k)398if sum(mu) != k * (2 * g - 2):399raise ValueError('mu must be a partition of k*(2g-2).')400401# Hbar has codimension g402meromorphic = any(not k.divides(m) or m < 0 for m in mu)403404if spin:405method = 'diffstrata'406if any(m % 2 != 0 for m in mu):407raise ValueError('The signature is not of spin type')408if k > 1:409if k % 2 != 1:410raise ValueError('The integer k has to be odd if we want to have spin structure.')411if not spin_conj or not meromorphic or res_cond != () or xi_power != 0:412raise NotImplementedError413method = 'pull' # for k>1, we do not use diffstrata414elif res_cond == () and meromorphic and spin_conj:415method = 'pull' # in this cases, we do not use diffstrata416417if (g == 0) and not (res_cond or xi_power):418return R.fundamental_class()419420if all(m == 0 for m in mu) and not (res_cond or xi_power):421return -R.fundamental_class() if spin else R.fundamental_class()422423if method == 'diffstrata' or xi_power > 0 or any(isinstance(rc, Iterable) for rc in res_cond):424# preprocessing residue conditions425fancy_res_cond = []426for rc in res_cond:427if isinstance(rc, Iterable):428if not all(a == 0 or a == 1 for a in rc):429raise NotImplementedError('Only residue conditions with coefficients 0,1 implemented.')430fancy_res_cond.append([(0, i) for i, a in enumerate(rc) if a == 1])431else:432fancy_res_cond.append([(0, rc - 1)])433434X = admcycles.diffstrata.generalisedstratum.GeneralisedStratum(435sig_list=[admcycles.diffstrata.sig.Signature(tuple(mu))], res_cond=fancy_res_cond)436437elgtclass = X.xi ** xi_power # ELGTautClass of the xi class with power438439if spin: # if spin, we turn the ELGTautClass into spin version440elgtspin = admcycles.diffstrata.spinstratum.ELGT_addspin(elgtclass)441return elgtspin.to_prodtautclass_spin().pushforward()442443return elgtclass.to_prodtautclass().pushforward()444445if len(res_cond) > 0:446# res_cond = tuple(res_cond) # make sure it is of type tuple447if not k == 1:448raise NotImplementedError('Residue conditions only implemented for k=1')449if virt:450raise ValueError('Residue conditions not compatible with virt=True')451452res_cond = sorted(res_cond)453poles = [i for (i, mui) in enumerate(mu) if mui < 0] # indices of poles454455if len(poles) == 1: # residue conditions are automatic456return Strataclass(g, k, mu, virt=virt)457458if any(mu[i - 1] >= 0 for i in res_cond): # try imposing residue cond. at non-pole459raise ValueError('Residue conditions can only be imposed at poles')460if any(mu[i - 1] == -1 for i in res_cond): # try imposing residue cond. at simple pole461return R.zero()462463num_add_marks = len([1 for i in res_cond if mu[i - 1] % 2]) # additional markings464maxleg = n + num_add_marks + 2465466genera = [g] + [ceil(-mu[i - 1] / 2) for i in res_cond]467munew = []468outermus = []469markcounter = 1470legs = [[]]471edges = [(maxleg + 2 * j, maxleg + 2 * j + 1) for j in range(len(res_cond))]472473for i in range(1, n + 1):474if i not in res_cond:475legs[0].append(markcounter)476munew.append(mu[i - 1])477markcounter += 1478else:479legs[0].append(maxleg)480if mu[i - 1] % 2 == 0:481legs.append([maxleg + 1])482outermus.append([-mu[i - 1] - 2])483else:484legs.append([maxleg + 1, markcounter])485outermus.append([-mu[i - 1] - 2, 1])486munew.append(1)487markcounter += 1488maxleg += 2489gamma = StableGraph(genera, legs, edges)490491outerg = sum(genera)492outerclass = Strataclass(outerg, k, munew, virt=False)493494pullb = gamma.boundary_pullback(outerclass)495return pullb.factor_reconstruct(0, [Strataclass(genera[j + 1], 1, outermus[j]) for j in range(len(res_cond))])496497if k > 1 and not meromorphic and not virt:498# all entries divisible by k and nonnegative AND user wants a codim g-1 class499# return the corresponding stratum of abelian differentials500return Strataclass(g, 1, tuple(m // k for m in mu))501502ordering_permutation = Word(mu).standard_permutation().inverse()503ordering_dict = {i + 1: j for i, j in enumerate(ordering_permutation)}504# this is the dictionary such that renaming the answer with standard-ordering according to ordering_dict gives correct answer505sortmu = tuple(sorted(mu))506507try:508v = StrataDB.cached(g, k, sortmu, virt, spin)509if meromorphic or virt: # return codim g class510ans_preord = R.from_vector(v, g)511else:512ans_preord = R.from_vector(v, g - 1)513ans_preord.rename_legs(ordering_dict)514return ans_preord515except KeyError:516pass517518# at this point, we actually have to compute519# we will compute the answer with standard ordering, store its vector in StrataDB and then return the renamed answer520if meromorphic or virt:521# return codimension g class522bdry_list = twistenum(g, k, sortmu)523indfind1 = tuple((i for i, l in enumerate(bdry_list) if l[0][0] == g))524assert len(indfind1) == 1, (g, k, mu, tuple(indfind1))525bdry_list.pop(indfind1[0]) # remove the trivial graph526527# right hand side of recursion528result = DR_cycle(g, tuple((m + k for m in sortmu)), spin=spin)529result -= Strataboundarysum(g, k, sortmu, bdry_list=bdry_list, spin=spin, spin_conj=spin_conj)530531v = result.vector(g)532StrataDB.set_cache(v, *(g, k, sortmu, virt, spin))533result = R.from_vector(v, g) # gives simplified version of result534result.rename_legs(ordering_dict)535return result536else:537# return codimension g-1 class538# by previous check can assume that k=1539assert k == 1, (g, k, mu)540541sortmuprime = list(sortmu) + [-1]542sortmuprime[n - 1] += 1543sortmuprime = tuple(sortmuprime)544545bdry_list = twistenum(g, k, sortmuprime)546547indfind1 = tuple((i for i, l in enumerate(bdry_list) if l[0][0] == g))548assert len(indfind1) == 1, (g, k, mu, tuple(indfind1))549bdry_list.pop(indfind1[0]) # remove the trivial graph550551indfind2 = tuple((i for i, l in enumerate(bdry_list) if len(552l) == 2 and l[0][0] == 0 and l[0][1] == (n, n + 1) and l[1][0] == g))553assert len(indfind2) == 1, (g, k, mu)554bdry_list.pop(indfind2[0])555556preresult = DR_cycle(g, tuple(m + k for m in sortmuprime))557preresult -= Strataboundarysum(g, k, sortmuprime, bdry_list=bdry_list)558result = preresult.forgetful_pushforward([n + 1])559result *= (1 / sortmuprime[n - 1])560561v = result.vector(g - 1)562StrataDB.set_cache(v, *(g, k, sortmu, virt, spin))563result = R.from_vector(v, g - 1) # gives simplified version of result564result.rename_legs(ordering_dict)565return result566567568def Strataboundarysum(g, k, mu, bdry_list=None, termsout=False, spin=False, spin_conj=False):569r"""570Returns sum of boundary terms in Conjecture A for entries of bdry_list.571These entries have the format from the output of twistenum, but might be a subset of these.572573INPUT:574575- g (integer): genus576577- k (integer): power of canonical bundle578579- bdry_list (list, default=None): a sublist of all the twisted stable graphs; if not580specified, it will just use the whole list581582- termsout (bool, default=False): if true, then the output will not be simplified583584- spin (bool, default=False): if true, then compute the spin strata boundary sum585586- spin_conj (bool, default=False): if true, then we will assume the spin Conjecture A587to be true and the summand will be computed by recursion on this formula588589EXAMPLES::590591sage: from admcycles.stratarecursion import Strataboundarysum592sage: from admcycles.double_ramification_cycle import DR_cycle593sage: Strataboundarysum(2,1,(6,-4),spin=True).basis_vector()594(-139, 46, -109, -99, 219, 194, 174, 136, -318, 91/2, -45/2, -15, -46, -25/2)595596"""597if spin:598if any(m % 2 != 0 for m in mu):599raise ValueError('This signature is not of spin type')600if all(m >= 0 for m in mu) and all(k.divides(m) for m in mu):601raise ValueError('The boundary sum with spin should not be called by such signature')602if k > 1:603if k % 2 != 1:604raise ValueError('The integer k has to be odd if we want to have spin structure.')605if not spin_conj:606raise NotImplementedError607608if bdry_list is None:609bdry_list = twistenum(g, 1, mu)610611resultterms = []612n = len(mu)613for vdata in bdry_list:614genera = [b[0] for b in vdata]615legs = [list(b[1]) for b in vdata] # just markings at the moment616edges = []617maxleg = n + 1618# stores twist at legs and half-edges619twist = {i: mu[i - 1] for i in range(1, n + 1)}620for v, (_, _, twistvect) in list(enumerate(vdata))[1:]:621for I in twistvect:622legs[0].append(maxleg)623legs[v].append(maxleg + 1)624edges.append((maxleg, maxleg + 1))625twist[maxleg] = -I - k626twist[maxleg + 1] = I - k627maxleg += 2628bdry_graph = StableGraph(genera, legs, edges)629coeff = prod([I for (_, _, twistvect) in vdata[1:] for I in twistvect])630coeff /= k**(len(genera) - 1) * automorphism_number_fixing_twist(bdry_graph, twist)631632if spin and any(y % 2 != 0 for _, y in twist.items()):633continue634else:635vertterms = [Strataclass(genera[0], k, [twist[l] for l in legs[0]], spin=spin, spin_conj=spin_conj)]636vertterms += [Strataclass(genera[v], 1, [twist[l] // k for l in legs[v]], spin=spin, spin_conj=spin_conj)637for v in range(1, len(genera))]638bdry_term = bdry_graph.boundary_pushforward(vertterms)639resultterms += [coeff * bdry_term]640641if termsout:642return resultterms643else:644result = sum(resultterms)645result.simplify()646return result647648649@cached_function650def StrataDB(g, k, mu):651raise NotImplementedError('StrataDB is just an internal database '652'for strata classes, use Strataclass instead')653654655def automorphism_number_fixing_twist(gr, I):656r"""657Return number of automorphisms of gr leaving the twist I on gr invariant.658659EXAMPLES::660661sage: from admcycles import StableGraph662sage: from admcycles.stratarecursion import automorphism_number_fixing_twist663sage: gr = StableGraph([0,0],[[1,2,3],[4,5,6]],[(1,2),(3,4),(5,6)])664sage: twist = {i:0 for i in range(7)}665sage: automorphism_number_fixing_twist(gr,twist)6668667sage: twist[1] = 1668sage: automorphism_number_fixing_twist(gr,twist)6692670sage: twist[2] = 1671sage: automorphism_number_fixing_twist(gr,twist)6724673"""674halfedges = gr.halfedges()675G = gr.leg_automorphism_group()676return len([1 for g in G if all(I[g(h)] == I[h] for h in halfedges)])677678679