unlisted
ubuntu2004# encoding: utf-81r"""2Double ramification cycle3"""45import itertools67from admcycles.admcycles import Tautvecttobasis, tautgens8from admcycles.stable_graph import StableGraph9from .tautological_ring import TautologicalRing10from .DR import interpolate11import admcycles.DR as DR1213from sage.combinat.subset import Subsets14from sage.arith.all import factorial15from sage.functions.other import floor, ceil16from sage.misc.misc_c import prod17from sage.rings.all import PolynomialRing, QQ, ZZ18from sage.modules.free_module_element import vector19from sage.rings.polynomial.multi_polynomial_element import MPolynomial20from sage.misc.cachefunc import cached_function21from sage.rings.power_series_ring import PowerSeriesRing22from sage.combinat.combinat import bernoulli_polynomial23from sage.functions.log import exp24from sage.combinat.partition import Partitions2526############27#28# Old DR-cycle implementation29#30############313233def DR_cycle_old(g, Avector, d=None, k=None, tautout=True, basis=False):34r"""Returns the k-twisted Double ramification cycle in genus g and codimension d35for the partition Avector of k*(2g-2+n).3637In the notation of [JPPZ17]_, the output is 2^(-d) * P_g^{d,k}(Avector).3839Note: This is based on the old implementation DR_compute by Pixton. A new one,40which can be faster in many examples is DR_cycle.4142INPUT:4344- ``tautout`` -- bool (default: `True`); if False, returns a vector45(in all generators for basis=false, in the basis of the ring for basis=true)4647- ``basis`` -- bool (default: `False`); if True, use FZ relations to give out the48DR cycle in a basis of the tautological ring49"""50if d is None:51d = g52n = len(Avector)53R = TautologicalRing(g, n)54if k is None:55k = floor(sum(Avector) / (2 * g - 2 + n))56if sum(Avector) != k * (2 * g - 2 + n):57raise ValueError('2g-2+n must divide the sum of entries of Avector.')5859v = DR.DR_compute(g, d, n, Avector, k)60v1 = vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v, g, d, tuple(range(1, n + 1)), DR.MODULI_ST)])6162if basis:63v2 = Tautvecttobasis(v1, g, n, d)64if tautout:65return R.from_basis_vector(v2, d)66return v267else:68if tautout:69return R.from_vector(v1, d)70return v1717273############74#75# New DR-cycle implementation76#77############7879def DR_cycle(g, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False, chiodo_coeff=False, r_coeff=None, moduli='st', base_ring=QQ, spin=False):80r"""Returns the k-twisted Double ramification cycle in genus g and codimension d81for the partition Avector of k*(2g-2+n). If some elements of Avector are elements of a82polynomial ring, compute DR-polynomial and substitute the entries of Avector - the result83then has coefficients in a polynomial ring.8485In the notation of [JPPZ17]_, the output is 2^(-d) * P_g^{d,k}(Avector).8687Note: This is a reimplementation of Pixton's DR_compute which can be faster in many examples.88To access the old version, use DR_cycle_old - it might be faster on older SageMath-versions.8990INPUT:9192- ``rpoly`` -- bool (default: `False`); if True, return tautclass 2^(-d) * P_g^{d,r,k}(Avector)93whose coefficients are polynomials in the variable r (for r>>0).9495- ``tautout`` -- bool (default: `True`); if False, returns a vector96(in all generators for basis=false, in the basis of the ring for basis=true)9798- ``basis`` -- bool (default: `False`); if True, use FZ relations to give out the99DR cycle in a basis of the tautological ring100101- ``chiodo_coeff`` -- bool (default: `False`); if True, return the formula102r^(2d-2g+1) epsilon_* c_d(-R* pi_* \L) from [JPPZ17]_, Corollary 4, Proposition 5 instead.103It has DR_cycle(g, Avector) as its r=0 specialization.104105- ``r_coeff`` -- integer or None (default: `None`); if an integer ``r0`` is given, return106the class/vector 2^(-d) * P_g^{d,r0,k}(Avector) for this fixed ``r0``. This option is107incompatible with ``rpoly = True`` or polynomial entries of ``Àvector``.108For r0>>0 this will agree with the value of the polynomial-coefficient class from109``rpoly = True`` at ``r=r0`` above.110111- ``moduli`` -- string (default: `'st'`); moduli on which DR is computed112113- ``base_ring`` -- string (default: `QQ`); ring of coefficients of the DR-cycle114115- ``spin`` -- bool (default: `False`); if True, compute the spin DR-cycle, and116the input ramification data has to be odd numbers117118EXAMPLES::119120sage: from admcycles import DR_cycle, DR_cycle_old121sage: DR_cycle(1, (2, 3, -5))122Graph : [1] [[1, 2, 3]] []123Polynomial : 2*psi_1 + 9/2*psi_2 + 25/2*psi_3124<BLANKLINE>125Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]126Polynomial : -1/24127<BLANKLINE>128Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]129Polynomial : -25/2130<BLANKLINE>131Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]132Polynomial : -9/2133<BLANKLINE>134Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]135Polynomial : -2136sage: DR_cycle(1, (2, 3, -5), moduli='ct')137Graph : [1] [[1, 2, 3]] []138Polynomial : 2*psi_1 + 9/2*psi_2 + 25/2*psi_3139<BLANKLINE>140Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]141Polynomial : -25/2142<BLANKLINE>143Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]144Polynomial : -9/2145<BLANKLINE>146Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]147Polynomial : -2148sage: DR_cycle(1, (2, 3, -5), moduli='sm')1490150151Here we check that `P_g^{d,k}(Avector)=0` for `d>g` ([CJ18]_)::152153sage: D = DR_cycle(1, (1, 3, -4), d=2)154sage: D2 = DR_cycle_old(1, (1, 3, -4), d=2) # long time155sage: D.vector() == D2.vector() # long time156True157sage: D.is_zero()158True159sage: v = DR_cycle(2, (3,), d=4, rpoly=true).evaluate()160sage: v # Conjecture by Longting Wu predicts that r^1-term vanishes161-1/1728*r^6 + 1/576*r^5 + 37/34560*r^4 - 13/6912*r^3 - 1/1152*r^2162163Using ``chiodo_coeff = True`` we can compute the more complicated formula for164the DR_cycle appearing in the original proof in [JPPZ17]_. It should give the same165result when ``rpoly = False``, but as a polynomial in r it will differ from the166simplified formula::167168sage: g=2; A=(2,4,-1); d=2; k=1169sage: v1 = DR_cycle(g,A,d,k,chiodo_coeff=True).vector()170sage: v2 = DR_cycle(g,A,d,k,chiodo_coeff=False).vector()171sage: v1 == v2172True173sage: g=1; A=(1,5); d=1; k=3174sage: DR_cycle(g,A,d,k,rpoly=True, chiodo_coeff=True)175Graph : [1] [[1, 2]] []176Polynomial : (-1/12*r^2 + 3/2*r - 9/2)*(kappa_1)_0 + (1/12*r^2 - 1/2*r + 1/2)*psi_1 + (1/12*r^2 - 5/2*r + 25/2)*psi_2177<BLANKLINE>178Graph : [0] [[4, 5, 1, 2]] [(4, 5)]179Polynomial : -1/24180<BLANKLINE>181Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]182Polynomial : -1/12*r^2 + 3/2*r - 9/2183184Setting ``r_coeff`` to a specific value, we can compute the class1852^(-d) * P_g^{d,r0,k}(Avector) even in the non-polynomial regime, both with186``chiodo_coeff`` being ``True`` or ``False``::187188sage: g=2; A=(5,-1); d=2; k=1189sage: D2 = DR_cycle(g,A,tautout=False,r_coeff=7)190sage: D7 = DR_cycle(g,A,tautout=False,r_coeff=7)191sage: Dr = DR_cycle(g,A,tautout=False,rpoly=True)192sage: r = Dr.parent().base().gens()[0] # extract coefficient variable r193sage: Dr.subs({r:2}) == D2 # r=2 not in polynomial regime194False195sage: Dr.subs({r:7}) == D7 # r=7 is in polynomial regime196True197198We can create a polynomial ring and use an Avector with entries in this ring.199The result is a tautological class with polynomial coefficients::200201sage: R.<a1,a2>=PolynomialRing(QQ,2)202sage: Q=DR_cycle(1,(a1,a2,-a1-a2))203sage: Q204Graph : [1] [[1, 2, 3]] []205Polynomial : 1/2*a1^2*psi_1 + 1/2*a2^2*psi_2 + (1/2*a1^2 + a1*a2 + 1/2*a2^2)*psi_3206<BLANKLINE>207Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]208Polynomial : -1/24209<BLANKLINE>210Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]211Polynomial : -1/2*a1^2 - a1*a2 - 1/2*a2^2212<BLANKLINE>213Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]214Polynomial : -1/2*a2^2215<BLANKLINE>216Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]217Polynomial : -1/2*a1^2218219We can compute the spin DR cycle as constructed in [Costantini-Sauvaget-Schmitt-21]::220221sage: spinDR=DR_cycle(2,(9,-3,-1),spin=True)222sage: spinDR.basis_vector()223(-367, 122, -117, -372, -319, 28, 405, 268, 841, 397, 709, 427, -600, -923, 216, -422, -388, 285, -370, -612, 850, 364, -850, 243/2, 16, -227/2, -243/2, -122, -49, 89/2, 168, 26, -125/2, -48, 10, -45/2, 76, 24, -35/2, 79, -207/2, -31, -90, 195/2)224225We can compute the spin DR as a polynomial::226227sage: R.<a>=PolynomialRing(QQ,1)228sage: DR_cycle(1,(2*a+1,-2*a+1),spin=True)229Graph : [1] [[1, 2]] []230Polynomial : -1/4*(kappa_1)_0 + (a^2 + a + 1/4)*psi_1 + (a^2 - a + 1/4)*psi_2231<BLANKLINE>232Graph : [0] [[4, 5, 1, 2]] [(4, 5)]233Polynomial : 1/24234<BLANKLINE>235Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]236Polynomial : -1/4237238This can be used to check Theorem 1.4 in [CSS21]_ for g=2, n=2::239240sage: g=2; n=2241sage: from admcycles import psiclass242sage: T = PolynomialRing(QQ,n,'a')243sage: avec = T.gens()244sage: k = sum(avec)/(2*g-2+n)245sage: R.<z> = PowerSeriesRing(T, default_prec = 2*g+10)246sage: cosh = 1/2*(exp(z)+exp(-z))247sage: sinh = 1/2*(exp(z)-exp(-z))248sage: S = sinh(z/2)/(z/2)249sage: Sprime = S.derivative()250sage: helpfun = exp(avec[0]*z*Sprime(k*z)/S(k*z))*cosh(z/2)/S(z)*prod(S(avec[i]*z) for i in range(1,n))/S(k*z)**(2*g+n-1)251sage: 2**(-g)*helpfun[2*g]25223/5898240*a0^4 - 29/1474560*a0^3*a1 + 157/2949120*a0^2*a1^2 - 53/1474560*a0*a1^3 + 103/5898240*a1^4 + 1/6144*a0^2 - 1/9216*a0*a1 + 11/18432*a1^2 - 1/2880253sage: a0, a1 = T.gens()254sage: (DR_cycle(g,(a0, a1), chiodo_coeff=True, spin=True)*psiclass(1,2,2)^(2*g-3+n)).evaluate()25523/5898240*a0^4 - 29/1474560*a0^3*a1 + 157/2949120*a0^2*a1^2 - 53/1474560*a0*a1^3 + 103/5898240*a1^4 + 1/6144*a0^2 - 1/9216*a0*a1 + 11/18432*a1^2 - 1/2880256257TESTS::258259sage: from admcycles import DR_cycle, DR_cycle_old260sage: D = DR_cycle(2, (3, 4, -2), k=1) # long time261sage: D2 = DR_cycle_old(2, (3, 4, -2), k=1) # long time262sage: D.vector() == D2.vector() # long time263True264sage: D = DR_cycle(2, (1, 4, 5), k=2) # long time265sage: D2 = DR_cycle_old(2, (1, 4, 5), k=2) # long time266sage: D.vector() == D2.vector() # long time267True268sage: D = DR_cycle(3, (2,4), k=1) # long time269sage: D2 = DR_cycle_old(3, (2,4), k=1) # long time270sage: D.vector() == D2.vector() # long time271True272273sage: D = DR_cycle(2, (3,),tautout=False); D274(0, 1/8, -9/4, 81/8, 1/4, -9/4, -1/8, 1/8, 1/4, -1/8, 1/48, 1/240, -3/16, -41/240, 1/48, 1/48, 1/1152)275sage: D2=DR_cycle_old(2, (3,), tautout=False); D2276(0, 1/8, -9/4, 81/8, 1/4, -9/4, -1/8, 1/8, 1/4, -1/8, 1/48, 1/240, -3/16, -41/240, 1/48, 1/48, 1/1152)277sage: D3 = DR_cycle(2, (3,), tautout=False, basis=True); D3278(-5, 2, -8, 18, 3/2)279sage: D4 = DR_cycle_old(2, (3,), tautout=False, basis=True); D4280(-5, 2, -8, 18, 3/2)281282sage: g=3; A=(5,8,1); d=1; k=2283sage: D1 = DR_cycle(g,A,d,k,chiodo_coeff=True)284sage: D2 = DR_cycle(g,A,d,k,chiodo_coeff=False)285sage: D1.vector() == D2.vector()286True287288sage: g=2; A=(6,); d=2; k=2289sage: D8 = DR_cycle(g,A,tautout=False,chiodo_coeff=True,r_coeff=8)290sage: Dr = DR_cycle(g,A,tautout=False,chiodo_coeff=True,rpoly=True)291sage: r = Dr.parent().base().gens()[0] # extract coefficient variable r292sage: Dr.subs({r:8}) == D8293True294295"""296if d is None:297d = g298n = len(Avector)299300if any(isinstance(ai, MPolynomial) for ai in Avector):301# compute DRpoly and substitute the ai in there302Avector = vector(Avector)303BR = Avector.parent().base_ring()304pol, gens = DRpoly(g, d, n, tautout=False, basis=basis, gensout=True,305chiodo_coeff=chiodo_coeff, moduli=moduli, spin=spin)306subsdict = {gens[i]: Avector[i] for i in range(n)}307pol = pol.apply_map(lambda x: x.subs(subsdict))308309if tautout:310R = TautologicalRing(g, n, moduli=moduli, base_ring=BR)311return R.from_vector(pol, d)312return pol313314if spin:315if any(x % 2 != 1 for x in Avector):316raise ValueError('The ramification data is not of spin type.')317318Asum = sum(Avector)319320if r_coeff is None:321if Asum % (2 * g - 2 + n) != 0:322raise ValueError('2g-2+n must divide the sum of entries of Avector.')323if k is None:324k = floor(Asum / (2 * g - 2 + n))325if sum(Avector) != k * (2 * g - 2 + n):326raise ValueError('The entries of Avector must sum to k*(2g-2+n).')327else:328if k is None:329if Asum % (2 * g - 2 + n) == 0:330k = floor(Asum / (2 * g - 2 + n)) # we are guessing that this is the right k, but only unique mod r331else:332raise ValueError('If a value of r_coeff is specified, the parameter k must be specified.')333if sum(Avector) % r_coeff != k * (2 * g - 2 + n) % r_coeff:334raise ValueError('The entries of Avector must sum to k*(2g-2+n) mod r_coeff.')335if spin and k % 2 != 1:336raise ValueError('The integer k has to be odd for spin DR.')337338R = TautologicalRing(g, n, moduli, base_ring=base_ring)339gens = tautgens(g, n, d, decst=True, moduli=moduli)340341v = vector([DR_coeff_new(decs, g, n, d, Avector, k, rpoly, chiodo_coeff, r_coeff, spin=spin) for decs in gens])342343if not chiodo_coeff:344v *= 1 / ZZ(2)**d345# v1=vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v,g,d,tuple(range(1, n+1)),DR.MODULI_ST)])346347if basis:348v1 = Tautvecttobasis(v, g, n, d)349if tautout:350return R.from_basis_vector(v1, d)351return v1352else:353if tautout:354return R.from_vector(v, d)355return v356357358def DR_coeff_setup(G, g, n, d, Avector, k, chiodo_coeff):359gamma = G.gamma360kappa, psi = G.poly.monom[0]361exp_list = []362exp_list_fine = []363if chiodo_coeff:364R = PolynomialRing(QQ, 'x,r', 2)365x, r = R.gens()366scalar_factor = R.one() / G.automorphism_number()367else:368scalar_factor = QQ((1, G.automorphism_number()))369given_weights = [-k * (2 * gv - 2 + len(gamma._legs[i]))370for i, gv in enumerate(gamma._genera)]371372# contributions to scalar_factor from kappa-classes373for v in range(gamma.num_verts()):374if chiodo_coeff:375scalar_factor *= prod(((-1)**(m + 1) * bernoulli_polynomial(x, m + 2)(k / r, r) /376(m + 1) / (m + 2))**ex / factorial(ex) for m, ex in enumerate(kappa[v]))377else:378if len(kappa[v]) == 1: # some kappa_1^e at this vertex379scalar_factor *= (-k**2)**(kappa[v][0]) / factorial(kappa[v][0])380381# contributions to scalar_factor and given_weights from markings382for i in range(1, n + 1):383v = gamma.vertex(i)384psipow = psi.get(i, 0) # if i in dictionary, have psi_i^(psi[i]), otherwise have psi_i^0385given_weights[v] += Avector[i - 1]386if chiodo_coeff:387sfcontrib = QQ(0)388for par in Partitions(psipow):389parexp = par.to_exp() # list [1,0,4] corresponds to psi^1 * (psi^3)^4390sfcontrib += prod(((-1)**m * bernoulli_polynomial(x, m + 2)391(Avector[i - 1] / r, r) / (m + 1) / (m + 2))**ex / factorial(ex) for m, ex in enumerate(parexp))392scalar_factor *= sfcontrib393else:394scalar_factor *= (Avector[i - 1])**(2 * psipow) / factorial(psipow)395396# contributions to scalar_factor and explist from edges397for (lu, lv) in gamma._edges:398psipowu = psi.get(lu, 0)399psipowv = psi.get(lv, 0)400exp_list.append(psipowu + psipowv + 1)401exp_list_fine.append([psipowu, psipowv])402if chiodo_coeff:403pass # formula is more complicated, take care of entire edge term below404else:405scalar_factor *= ((-1)**(psipowu + psipowv)) / factorial(psipowv) / \406factorial(psipowu) / (psipowu + psipowv + 1)407408return exp_list, given_weights, scalar_factor, exp_list_fine409410411def DR_coeff_new(G, g, n, d, Avector, k, rpoly, chiodo_coeff, r_coeff, spin=False):412gamma = G.gamma # underlying stable graph of decstratum G413kappa, _ = G.poly.monom[0]414# kappa is a list of length = # vertices, of entries like [3,0,2] meaning that at this vertex there is a kappa_1^3 * kappa_3^2415# _ = psi is a dictionary and psi[3]=4 means there is a decoration psi^4 at marking/half-edge 3416417if not chiodo_coeff and any(len(kalist) > 1 for kalist in kappa):418return 0 # vertices can only carry powers of kappa_1, no higher kappas allowed419420# value from which on the Pixton-sum below will be polynomial in m421m0 = ceil(sum([abs(i) for i in Avector]) / ZZ(2)) + g * abs(k)422# TODO: verify this is still ok with chiodo_coeff423h0 = gamma.num_edges() - gamma.num_verts() + 1 # first Betti number of the graph Gamma424exp_list, given_weights, scalar_factor, exp_list_fine = DR_coeff_setup(G, g, n, d, Avector, k, chiodo_coeff)425426if chiodo_coeff:427deg = 2 * d428else:429deg = 2 * sum(exp_list) # degree of polynomial in m430# TODO: verify this is still ok with chiodo_coeff431432# R = PolynomialRing(QQ, len(gamma.edges) + 1, 'd')433# P=prod([(di*(d0-di))**exp_list[i] for i,di in enumerate(R.gens()[1:])])/(d0**h0)434if r_coeff is not None:435# it might be that given_weights doesn't sum to zero (only to zero mod r)436# artificially subtract the sum from the first entry; this does not change the result mod r437given_weights[0] -= sum(given_weights)438439u = gamma.flow_solve(given_weights)440Vbasis = gamma.cycle_basis()441442# if r_coeff is not None, we want one particular value of r, so let function mpoly_special443# interpolate a degree 0 polynomial at this value444if r_coeff is not None:445deg = 0446m0 = r_coeff - 1447448if spin:449spin_factor = QQ(1) / QQ(2 ** (g - h0))450else:451spin_factor = QQ(1)452453# mpoly = mpoly_affine_sum(P, u, Vbasis,m0+1,deg)454mpoly = spin_factor * mpoly_special(exp_list, h0, u, Vbasis, m0 + 1, deg, chiodo_coeff, r_coeff, scalar_factor, exp_list_fine, d, spin)455456# mpoly = interpolate(mrange, mvalues)457if rpoly:458if chiodo_coeff:459return mpoly460else:461return mpoly * scalar_factor462if chiodo_coeff:463return mpoly.subs(r=0)464return mpoly.subs(r=0) * scalar_factor465466467def mpoly_special(exp_list, h0, u, Vbasis, start, degr, chiodo_coeff, r_coeff, scalar_factor, exp_list_fine, d, spin=False):468r"""469Return the sum of the rational function in DR_coeff_new over the470affine space u + V modulo r in ZZ^n as a univariate polynomial in r.471V is the lattice with basis Vbasis.472Use that this sum is polynomial in r starting from r=start and the polynomial has473degree degr (for now).474"""475mrange = list(range(start, start + degr + 1))476if chiodo_coeff and r_coeff is None:477# temporary security measure: interpolate with one more value, check degree still at most degr478mrange.append(start + degr + 1)479if spin:480mrange = [2 * q for q in mrange] # if spin is true, only consider r even481mvalues = []482rank = len(Vbasis)483ulen = len(u)484485for m in mrange:486total = 0487for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):488v = u + sum([coeff[i] * Vbasis[i] for i in range(rank)])489v = [vi % m for vi in v]490if chiodo_coeff:491if not spin or all(x % 2 == 1 for x in v): # if spin is true, only count the odd weightings492total += prod(econtrib(exp_list_fine[i][0], exp_list_fine[i][1], -v[i] % m, m) for i in range(ulen))493else:494if not spin or all(x % 2 == 1 for x in v): # if spin is true, only count the odd weightings495total += prod([(v[i] * (m - v[i]))**exp_list[i] for i in range(ulen)])496if chiodo_coeff:497mvalues.append(scalar_factor(0, m) * total * (m ** (2 * d - h0)))498else:499mvalues.append(total / QQ(m ** h0))500result = interpolate(mrange, mvalues, 'r')501if result.degree() > degr:502raise ValueError('Polynomial has higher degree in r than expected!')503return result504505506def econtrib(e1, e2, w1, r):507polycoeff = edge_power_series(e1 + e2)[(e1, e2)]508return polycoeff(*((-1)**m * bernoulli_polynomial(w1 / ZZ(r), m + 2) / (m + 1) / (m + 2) for m in range(e1 + e2 + 1)))509510511@cached_function512def edge_power_series(deg):513R = PolynomialRing(QQ, 'a', deg + 1)514A = R.gens() # A = [a0, ..., a(deg)]515S = PowerSeriesRing(R, 'X,Y', default_prec=deg + 2)516X, Y = S.gens()517ex = sum(A[i] * (X**(i + 1) - (-Y)**(i + 1)) for i in range(deg + 1))518h = (1 - exp(ex)) / (X + Y)519return {tuple(k): it for k, it in h.dict().items()} # dictionary (1, 0) : -a1 - (1/2)*a0^2, ... with R-values520521522def mpoly_affine_sum(P, u, Vbasis, start, degr):523r"""524Return the sum of the polynomial P in variables r, d1, ..., dn over the525affine space u + V modulo r in ZZ^n as a univariate polynomial in r.526V is the lattice with basis Vbasis.527Use that this sum is polynomial in r starting from r=start and the polynomial has528degree degr (for now).529"""530mrange = list(range(start, start + degr + 2))531mvalues = []532rank = len(Vbasis)533534for m in mrange:535total = 0536for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):537v = u + sum([coeff[i] * Vbasis[i] for i in range(rank)])538v = [vi % m for vi in v]539total += P(m, *v)540mvalues.append(total)541542return interpolate(mrange, mvalues, 'r')543544############545#546# DR polynomial547#548############549550551def multivariate_interpolate(f, d, n, gridwidth=1, R=None, generator=None, gridshift=None, transf_poly=None):552r"""Takes a vector/number-valued function f on n variables and interpolates it on a grid around 0.553Returns a vector with entries in a polynomial ring.554555INPUT:556557- ``d`` -- integer; maximal degree of output-polynomial in any of the variables558559- ``gridwidth``-- integer (default: `1`); width of the grid used for interpolation560561- ``R`` -- polynomial ring (default: `None`); expected to be polynomial ring in562at least n variables; if None, use ring over QQ in variables z0,...,z(n-1)563564- ``generator``-- list (default: `None`); list of n variables in the above polynomial565ring to be used in output; if None, use first n generators of ring566567- ``enlarge``-- integer (default: `1`); the factor of enlarging the gridwidth during568interpolating the values569570- ``gridshift``-- tuple (default: `None`); of length n to shift the571grid before interpolation takes place572573- ``transf_poly``-- polynomial (default: `None`); a polynomial of single variable of how to574modify the input values to the function575576EXAMPLES::577578sage: from admcycles.double_ramification_cycle import multivariate_interpolate579sage: from sage.modules.free_module_element import vector580sage: def foo(x,y):581....: return vector((x**2+y,2*x*y))582....:583sage: multivariate_interpolate(foo,2,2)584(z0^2 + z1, 2*z0*z1)585"""586if R is None:587R = PolynomialRing(QQ, 'z', n)588if generator is None:589generator = R.gens()590591if n == 0: # return constant592return R.one() * f()593594cube = [list(range(d + 1))] * n595points = itertools.product(*cube)596# shift cube containing evaluation points in negative direction to reduce abs value of evaluation points597# the customized shift will also be taken into account598if gridshift is None:599shift = [-floor((d + 1) / QQ(2)) * gridwidth for i in range(n)]600else:601shift = [gridshift[i] - floor((d + 1) / QQ(2)) * gridwidth for i in range(n)]602result = 0603604for p in points:605# compute Lagrange polynomial not vanishing exactly at gridwidth*p606lagr = prod([prod([generator[i] - (j * gridwidth + shift[i])607for j in range(d + 1) if j != p[i]]) for i in range(n)])608609pval = [gridwidth * p[i] + shift[i] for i in range(n)]610value = lagr.subs({generator[i]: pval[i] for i in range(n)})611if transf_poly is not None:612pval = [transf_poly(val) for val in pval]613614# global fex,lagrex, pvalex615# fex=f616# lagrex=lagr617# pvalex=pval618if n == 1: # avoid problems with multiplication of polynomial with vector619mult = lagr / value620result += vector((e * mult for e in f(*pval)))621else:622result += f(*pval) / value * lagr623return result624625626def DRpoly(g, d, n, dplus=0, tautout=True, basis=False, ring=None, gens=None, gensout=False, chiodo_coeff=False, moduli='st', spin=False):627r"""628Return the Double ramification cycle in genus g with n markings in degree d as a629tautclass with polynomial coefficients. Evaluated at a point (a_1, ..., a_n) with630sum a_i = k(2g-2+n) it equals DR_cycle(g,(a_1, ..., a_n),d).631632The computation uses interpolation and the fact that DR is a polynomial in the a_i633of degree 2*d.634635INPUT:636637- ``dplus`` -- integer (default: `0`); if dplus>0, the interpolation is performed638on a larger grid as a consistency check639640- ``tautout``-- bool (default: `True`); if False, returns a polynomial-valued vector641(in all generators for basis=false, in the basis of the ring for basis=true)642643- ``basis`` -- bool (default: `False`); if True, use FZ relations to give out the644DR cycle in a basis of the tautological ring645646- ``ring`` -- polynomial ring (default: `None`); expected to be polynomial ring in647at least n variables; if None, use ring over QQ in variables a1,...,an648649- ``gens`` -- list (default: `None`); list of n variables in the above polynomial650ring to be used in output; if None, use first n generators of ring651652- ``gensout``-- bool (default: `False`); if True, return (DR polynomial, list of generators653of coefficient ring)654655- ``spin``-- bool (default: False`); if True, return the spin DR polynomial656657EXAMPLES::658659sage: from admcycles import DRpoly, DR_cycle, TautologicalRing660sage: R = TautologicalRing(1, 2)661sage: D, (a1, a2) = DRpoly(1, 1, 2, gensout=True)662sage: D = D.subs({a2:-a1})663sage: D = D.simplify()664sage: D665Graph : [1] [[1, 2]] []666Polynomial : 1/2*a1^2*psi_1 + 1/2*a1^2*psi_2667<BLANKLINE>668Graph : [0] [[4, 5, 1, 2]] [(4, 5)]669Polynomial : -1/24670sage: (D*R.psi(1)).evaluate() # intersection number with psi_16711/24*a1^2 - 1/24672sage: (D.subs({a1:4})-DR_cycle(1,(4,-4))).is_zero() # polynomial agrees with DR_cycle at (4,-4)673True674675DR vanishes in degree d>g ([CJ18]_)::676677sage: DRpoly(1,2,2).is_zero()678True679sage: DRpoly(1,1,2,moduli='ct')680Graph : [1] [[1, 2]] []681Polynomial : (-1/8*a1^2 - 1/4*a1*a2 - 1/8*a2^2)*(kappa_1)_0 + 1/2*a1^2*psi_1 + 1/2*a2^2*psi_2682<BLANKLINE>683Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]684Polynomial : -1/8*a1^2 - 1/4*a1*a2 - 1/8*a2^2685sage: R.<a1,a2,a3,b1,b2,b3> = PolynomialRing(QQ, 6)686sage: Da = DRpoly(1, 1, 3, ring=R, gens=[a1, a2, a3])687sage: Db = Da.subs({a1:b1, a2:b2, a3:b3})688sage: Dab = Da.subs({a1:a1+b1, a2:a2+b2, a3:a3+b3})689sage: diff = Da*Db - Da*Dab # this should vanish in compact type by [HoPiSc19]_690sage: diff.is_zero()691False692sage: diff.is_zero(moduli='ct')693True694"""695def f(*Avector):696return DR_cycle(g, Avector, d, tautout=False, basis=basis, moduli=moduli, spin=spin)697# k=floor(QQ(sum(Avector))/(2 * g - 2 + n))698# return vector([QQ(e) for e in DR_red(g,d,n,Avector, k, basis)])699# return vector(DR_compute(g,d,n,Avector, k))700701if ring is None:702ring = PolynomialRing(QQ, ['a%s' % i for i in range(1, n + 1)])703if gens is None:704gens = ring.gens()[0:n]705706R = TautologicalRing(g, n, moduli)707gridwidth = 2 * g - 2 + n708if spin:709shiftvec = tuple([g - 2 + n] + [-1 for i in range(n - 1)]) # to set an initial of Avector whose k is odd710R_transf = PolynomialRing(QQ, 'x', 1)711transf_poly = 2 * R_transf.gens()[0] + 1712interp = multivariate_interpolate(f, 2 * d + dplus, n, gridwidth, R=ring, generator=gens, gridshift=shiftvec, transf_poly=transf_poly)713interp = interp.subs({gens[i]: (QQ(1) / 2) * (gens[i] - QQ(1)) for i in range(n)})714else:715interp = multivariate_interpolate(f, 2 * d + dplus, n, gridwidth, R=ring, generator=gens)716717if not tautout:718ans = interp719else:720if not basis:721ans = R.from_vector(interp, d)722else:723ans = R.from_basis_vector(interp, d)724if gensout:725return (ans, gens)726return ans727728729def degree_filter(polyvec, d):730r"""Takes a vector of polynomials in several variables and returns a vector containing the (total)731degree d part of these polynomials.732733INPUT:734735- vector of polynomials736737- integer degree738739EXAMPLES::740741sage: from admcycles.double_ramification_cycle import degree_filter742sage: R.<x,y> = PolynomialRing(QQ, 2)743sage: v = vector((x+y**2+2*x*y,1+x**3+x*y))744sage: degree_filter(v, 2)745(2*x*y + y^2, x*y)746"""747resultvec = []748for pi in polyvec:749s = 0750for c, m in pi:751if m.degree() == d:752s += c * m753resultvec.append(s)754return vector(resultvec)755756757def Hain_divisor(g, A):758r"""759Returns a divisor class D extending the pullback of the theta-divisor under the Abel-Jacobi map (on compact type) given by partition A of zero.760761Note: D^g/g! agrees with the Double-Ramification cycle in compact type.762763EXAMPLES::764765sage: from admcycles import *766767sage: R = PolynomialRing(QQ, 'z', 3)768sage: z0, z1, z2 = R.gens()769sage: u = Hain_divisor(2, (z0, z1, z2))770sage: g = DRpoly(2, 1, 3, ring=R, gens=[z0, z1, z2]) #u,g should agree inside compact type771sage: (u.vector() - g.vector()).subs({z0: -z1-z2})772(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1/24)773"""774n = len(A)775R = TautologicalRing(g, n)776777def delt(l, I):778# takes genus l and subset I of markings 1,...,n and returns generalized boundary divisor Delta_{l,I}779I = set(I)780if l == 0 and len(I) == 1:781return -R.psi(list(I)[0])782if l == g and len(I) == n - 1:783i_set = set(range(1, n + 1)) - I784return -R.psi(list(i_set)[0])785if (l == 0 and len(I) == 0) or (l == g and len(I) == n):786return R.zero()787788Icomp = set(range(1, n + 1)) - I789gra = StableGraph([l, g - l], [list(I) + [n + 1], list(Icomp) + [n + 2]],790[(n + 1, n + 2)])791return QQ((1, gra.automorphism_number())) * R(gra)792793result = sum([sum([(sum([A[i - 1] for i in I])**2) * delt(l, I)794for I in Subsets(list(range(1, n + 1)))])795for l in range(g + 1)])796# note index i-1 since I subset {1,...,n} but array indices subset {0,...,n-1}797798return QQ((-1, 4)) * result799800801# Norbury_ThetaClass802def ThetaClass(g, n, moduli='st'):803r"""804Return the class Theta_{g,n} from the paper [Nor]_ by Norbury.805806INPUT:807808- ``moduli`` -- string (default: `'st'`); moduli on which Theta_{g,n} is computed809810EXAMPLES::811812We can verify many of the general properties of Theta_{g,n} in examples, starting813with the initial condition Theta_{1,1} = 3 * psi_{1,1}.814815sage: from admcycles import ThetaClass, TautologicalRing816sage: R = TautologicalRing(1, 1)817sage: (ThetaClass(1,1) - 3*R.psi(1)).is_zero()818True819820Likewise, we can check that the Theta-class pulls back correctly under boundary821gluing maps.822823sage: from admcycles.admcycles import StableGraph, prodtautclass824sage: A = ThetaClass(3,0)825sage: gamma1 = StableGraph([1,2],[[1],[2]],[(1,2)])826sage: pb1 = gamma1.boundary_pullback(A)827sage: pb1.totensorTautbasis(4)828[[0], [ 207 -189/2 27/2], [], [], []]829sage: check1 = prodtautclass(gamma1, protaut = [ThetaClass(1,1), ThetaClass(2,1)])830sage: check1.totensorTautbasis(4)831[[0], [ 207 -189/2 27/2], [], [], []]832833A similar pullback property holds for the gluing map associated to the834divisor of irreducible nodal curves.835836sage: B = ThetaClass(2,1)837sage: gamma2 = StableGraph([1],[[1,2,3]],[(2,3)])838sage: pb2 = gamma2.boundary_pullback(B)839sage: pb2.totensorTautbasis(3)840(6)841sage: ThetaClass(1,3).basis_vector()842(6)843844Finally, we check the property Theta_{g,n+1} = pi*(Theta_{g,n}) * psiclass_{n+1}::845846sage: C = ThetaClass(2,0)847sage: R = TautologicalRing(2, 1)848sage: (C.forgetful_pullback([1]) * R.psi(1) - B).is_zero()849True850"""851r = ZZ(2)852s = -ZZ(1)853d = ZZ(2) * g - 2 + n854x = -ZZ(1)855A = n * (1,)856return ZZ(2)**(g - 1 + n) * (r**(2 * g - 2 * d - 1)) * (x**d) * DR_cycle(g, A, d, s, chiodo_coeff=True, r_coeff=r, moduli=moduli)857858859