Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
180 views
unlisted
ubuntu2004
1
# encoding: utf-8
2
r"""
3
Double ramification cycle
4
"""
5
6
import itertools
7
8
from admcycles.admcycles import Tautvecttobasis, tautgens
9
from admcycles.stable_graph import StableGraph
10
from .tautological_ring import TautologicalRing
11
from .DR import interpolate
12
import admcycles.DR as DR
13
14
from sage.combinat.subset import Subsets
15
from sage.arith.all import factorial
16
from sage.functions.other import floor, ceil
17
from sage.misc.misc_c import prod
18
from sage.rings.all import PolynomialRing, QQ, ZZ
19
from sage.modules.free_module_element import vector
20
from sage.rings.polynomial.multi_polynomial_element import MPolynomial
21
from sage.misc.cachefunc import cached_function
22
from sage.rings.power_series_ring import PowerSeriesRing
23
from sage.combinat.combinat import bernoulli_polynomial
24
from sage.functions.log import exp
25
from sage.combinat.partition import Partitions
26
27
############
28
#
29
# Old DR-cycle implementation
30
#
31
############
32
33
34
def DR_cycle_old(g, Avector, d=None, k=None, tautout=True, basis=False):
35
r"""Returns the k-twisted Double ramification cycle in genus g and codimension d
36
for the partition Avector of k*(2g-2+n).
37
38
In the notation of [JPPZ17]_, the output is 2^(-d) * P_g^{d,k}(Avector).
39
40
Note: This is based on the old implementation DR_compute by Pixton. A new one,
41
which can be faster in many examples is DR_cycle.
42
43
INPUT:
44
45
- ``tautout`` -- bool (default: `True`); if False, returns a vector
46
(in all generators for basis=false, in the basis of the ring for basis=true)
47
48
- ``basis`` -- bool (default: `False`); if True, use FZ relations to give out the
49
DR cycle in a basis of the tautological ring
50
"""
51
if d is None:
52
d = g
53
n = len(Avector)
54
R = TautologicalRing(g, n)
55
if k is None:
56
k = floor(sum(Avector) / (2 * g - 2 + n))
57
if sum(Avector) != k * (2 * g - 2 + n):
58
raise ValueError('2g-2+n must divide the sum of entries of Avector.')
59
60
v = DR.DR_compute(g, d, n, Avector, k)
61
v1 = vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v, g, d, tuple(range(1, n + 1)), DR.MODULI_ST)])
62
63
if basis:
64
v2 = Tautvecttobasis(v1, g, n, d)
65
if tautout:
66
return R.from_basis_vector(v2, d)
67
return v2
68
else:
69
if tautout:
70
return R.from_vector(v1, d)
71
return v1
72
73
74
############
75
#
76
# New DR-cycle implementation
77
#
78
############
79
80
def 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):
81
r"""Returns the k-twisted Double ramification cycle in genus g and codimension d
82
for the partition Avector of k*(2g-2+n). If some elements of Avector are elements of a
83
polynomial ring, compute DR-polynomial and substitute the entries of Avector - the result
84
then has coefficients in a polynomial ring.
85
86
In the notation of [JPPZ17]_, the output is 2^(-d) * P_g^{d,k}(Avector).
87
88
Note: This is a reimplementation of Pixton's DR_compute which can be faster in many examples.
89
To access the old version, use DR_cycle_old - it might be faster on older SageMath-versions.
90
91
INPUT:
92
93
- ``rpoly`` -- bool (default: `False`); if True, return tautclass 2^(-d) * P_g^{d,r,k}(Avector)
94
whose coefficients are polynomials in the variable r (for r>>0).
95
96
- ``tautout`` -- bool (default: `True`); if False, returns a vector
97
(in all generators for basis=false, in the basis of the ring for basis=true)
98
99
- ``basis`` -- bool (default: `False`); if True, use FZ relations to give out the
100
DR cycle in a basis of the tautological ring
101
102
- ``chiodo_coeff`` -- bool (default: `False`); if True, return the formula
103
r^(2d-2g+1) epsilon_* c_d(-R* pi_* \L) from [JPPZ17]_, Corollary 4, Proposition 5 instead.
104
It has DR_cycle(g, Avector) as its r=0 specialization.
105
106
- ``r_coeff`` -- integer or None (default: `None`); if an integer ``r0`` is given, return
107
the class/vector 2^(-d) * P_g^{d,r0,k}(Avector) for this fixed ``r0``. This option is
108
incompatible with ``rpoly = True`` or polynomial entries of ``Àvector``.
109
For r0>>0 this will agree with the value of the polynomial-coefficient class from
110
``rpoly = True`` at ``r=r0`` above.
111
112
- ``moduli`` -- string (default: `'st'`); moduli on which DR is computed
113
114
- ``base_ring`` -- string (default: `QQ`); ring of coefficients of the DR-cycle
115
116
- ``spin`` -- bool (default: `False`); if True, compute the spin DR-cycle, and
117
the input ramification data has to be odd numbers
118
119
EXAMPLES::
120
121
sage: from admcycles import DR_cycle, DR_cycle_old
122
sage: DR_cycle(1, (2, 3, -5))
123
Graph : [1] [[1, 2, 3]] []
124
Polynomial : 2*psi_1 + 9/2*psi_2 + 25/2*psi_3
125
<BLANKLINE>
126
Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]
127
Polynomial : -1/24
128
<BLANKLINE>
129
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
130
Polynomial : -25/2
131
<BLANKLINE>
132
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
133
Polynomial : -9/2
134
<BLANKLINE>
135
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
136
Polynomial : -2
137
sage: DR_cycle(1, (2, 3, -5), moduli='ct')
138
Graph : [1] [[1, 2, 3]] []
139
Polynomial : 2*psi_1 + 9/2*psi_2 + 25/2*psi_3
140
<BLANKLINE>
141
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
142
Polynomial : -25/2
143
<BLANKLINE>
144
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
145
Polynomial : -9/2
146
<BLANKLINE>
147
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
148
Polynomial : -2
149
sage: DR_cycle(1, (2, 3, -5), moduli='sm')
150
0
151
152
Here we check that `P_g^{d,k}(Avector)=0` for `d>g` ([CJ18]_)::
153
154
sage: D = DR_cycle(1, (1, 3, -4), d=2)
155
sage: D2 = DR_cycle_old(1, (1, 3, -4), d=2) # long time
156
sage: D.vector() == D2.vector() # long time
157
True
158
sage: D.is_zero()
159
True
160
sage: v = DR_cycle(2, (3,), d=4, rpoly=true).evaluate()
161
sage: v # Conjecture by Longting Wu predicts that r^1-term vanishes
162
-1/1728*r^6 + 1/576*r^5 + 37/34560*r^4 - 13/6912*r^3 - 1/1152*r^2
163
164
Using ``chiodo_coeff = True`` we can compute the more complicated formula for
165
the DR_cycle appearing in the original proof in [JPPZ17]_. It should give the same
166
result when ``rpoly = False``, but as a polynomial in r it will differ from the
167
simplified formula::
168
169
sage: g=2; A=(2,4,-1); d=2; k=1
170
sage: v1 = DR_cycle(g,A,d,k,chiodo_coeff=True).vector()
171
sage: v2 = DR_cycle(g,A,d,k,chiodo_coeff=False).vector()
172
sage: v1 == v2
173
True
174
sage: g=1; A=(1,5); d=1; k=3
175
sage: DR_cycle(g,A,d,k,rpoly=True, chiodo_coeff=True)
176
Graph : [1] [[1, 2]] []
177
Polynomial : (-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_2
178
<BLANKLINE>
179
Graph : [0] [[4, 5, 1, 2]] [(4, 5)]
180
Polynomial : -1/24
181
<BLANKLINE>
182
Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]
183
Polynomial : -1/12*r^2 + 3/2*r - 9/2
184
185
Setting ``r_coeff`` to a specific value, we can compute the class
186
2^(-d) * P_g^{d,r0,k}(Avector) even in the non-polynomial regime, both with
187
``chiodo_coeff`` being ``True`` or ``False``::
188
189
sage: g=2; A=(5,-1); d=2; k=1
190
sage: D2 = DR_cycle(g,A,tautout=False,r_coeff=7)
191
sage: D7 = DR_cycle(g,A,tautout=False,r_coeff=7)
192
sage: Dr = DR_cycle(g,A,tautout=False,rpoly=True)
193
sage: r = Dr.parent().base().gens()[0] # extract coefficient variable r
194
sage: Dr.subs({r:2}) == D2 # r=2 not in polynomial regime
195
False
196
sage: Dr.subs({r:7}) == D7 # r=7 is in polynomial regime
197
True
198
199
We can create a polynomial ring and use an Avector with entries in this ring.
200
The result is a tautological class with polynomial coefficients::
201
202
sage: R.<a1,a2>=PolynomialRing(QQ,2)
203
sage: Q=DR_cycle(1,(a1,a2,-a1-a2))
204
sage: Q
205
Graph : [1] [[1, 2, 3]] []
206
Polynomial : 1/2*a1^2*psi_1 + 1/2*a2^2*psi_2 + (1/2*a1^2 + a1*a2 + 1/2*a2^2)*psi_3
207
<BLANKLINE>
208
Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]
209
Polynomial : -1/24
210
<BLANKLINE>
211
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
212
Polynomial : -1/2*a1^2 - a1*a2 - 1/2*a2^2
213
<BLANKLINE>
214
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
215
Polynomial : -1/2*a2^2
216
<BLANKLINE>
217
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
218
Polynomial : -1/2*a1^2
219
220
We can compute the spin DR cycle as constructed in [Costantini-Sauvaget-Schmitt-21]::
221
222
sage: spinDR=DR_cycle(2,(9,-3,-1),spin=True)
223
sage: spinDR.basis_vector()
224
(-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)
225
226
We can compute the spin DR as a polynomial::
227
228
sage: R.<a>=PolynomialRing(QQ,1)
229
sage: DR_cycle(1,(2*a+1,-2*a+1),spin=True)
230
Graph : [1] [[1, 2]] []
231
Polynomial : -1/4*(kappa_1)_0 + (a^2 + a + 1/4)*psi_1 + (a^2 - a + 1/4)*psi_2
232
<BLANKLINE>
233
Graph : [0] [[4, 5, 1, 2]] [(4, 5)]
234
Polynomial : 1/24
235
<BLANKLINE>
236
Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]
237
Polynomial : -1/4
238
239
This can be used to check Theorem 1.4 in [CSS21]_ for g=2, n=2::
240
241
sage: g=2; n=2
242
sage: from admcycles import psiclass
243
sage: T = PolynomialRing(QQ,n,'a')
244
sage: avec = T.gens()
245
sage: k = sum(avec)/(2*g-2+n)
246
sage: R.<z> = PowerSeriesRing(T, default_prec = 2*g+10)
247
sage: cosh = 1/2*(exp(z)+exp(-z))
248
sage: sinh = 1/2*(exp(z)-exp(-z))
249
sage: S = sinh(z/2)/(z/2)
250
sage: Sprime = S.derivative()
251
sage: 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)
252
sage: 2**(-g)*helpfun[2*g]
253
23/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/2880
254
sage: a0, a1 = T.gens()
255
sage: (DR_cycle(g,(a0, a1), chiodo_coeff=True, spin=True)*psiclass(1,2,2)^(2*g-3+n)).evaluate()
256
23/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/2880
257
258
TESTS::
259
260
sage: from admcycles import DR_cycle, DR_cycle_old
261
sage: D = DR_cycle(2, (3, 4, -2), k=1) # long time
262
sage: D2 = DR_cycle_old(2, (3, 4, -2), k=1) # long time
263
sage: D.vector() == D2.vector() # long time
264
True
265
sage: D = DR_cycle(2, (1, 4, 5), k=2) # long time
266
sage: D2 = DR_cycle_old(2, (1, 4, 5), k=2) # long time
267
sage: D.vector() == D2.vector() # long time
268
True
269
sage: D = DR_cycle(3, (2,4), k=1) # long time
270
sage: D2 = DR_cycle_old(3, (2,4), k=1) # long time
271
sage: D.vector() == D2.vector() # long time
272
True
273
274
sage: D = DR_cycle(2, (3,),tautout=False); D
275
(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)
276
sage: D2=DR_cycle_old(2, (3,), tautout=False); D2
277
(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)
278
sage: D3 = DR_cycle(2, (3,), tautout=False, basis=True); D3
279
(-5, 2, -8, 18, 3/2)
280
sage: D4 = DR_cycle_old(2, (3,), tautout=False, basis=True); D4
281
(-5, 2, -8, 18, 3/2)
282
283
sage: g=3; A=(5,8,1); d=1; k=2
284
sage: D1 = DR_cycle(g,A,d,k,chiodo_coeff=True)
285
sage: D2 = DR_cycle(g,A,d,k,chiodo_coeff=False)
286
sage: D1.vector() == D2.vector()
287
True
288
289
sage: g=2; A=(6,); d=2; k=2
290
sage: D8 = DR_cycle(g,A,tautout=False,chiodo_coeff=True,r_coeff=8)
291
sage: Dr = DR_cycle(g,A,tautout=False,chiodo_coeff=True,rpoly=True)
292
sage: r = Dr.parent().base().gens()[0] # extract coefficient variable r
293
sage: Dr.subs({r:8}) == D8
294
True
295
296
"""
297
if d is None:
298
d = g
299
n = len(Avector)
300
301
if any(isinstance(ai, MPolynomial) for ai in Avector):
302
# compute DRpoly and substitute the ai in there
303
Avector = vector(Avector)
304
BR = Avector.parent().base_ring()
305
pol, gens = DRpoly(g, d, n, tautout=False, basis=basis, gensout=True,
306
chiodo_coeff=chiodo_coeff, moduli=moduli, spin=spin)
307
subsdict = {gens[i]: Avector[i] for i in range(n)}
308
pol = pol.apply_map(lambda x: x.subs(subsdict))
309
310
if tautout:
311
R = TautologicalRing(g, n, moduli=moduli, base_ring=BR)
312
return R.from_vector(pol, d)
313
return pol
314
315
if spin:
316
if any(x % 2 != 1 for x in Avector):
317
raise ValueError('The ramification data is not of spin type.')
318
319
Asum = sum(Avector)
320
321
if r_coeff is None:
322
if Asum % (2 * g - 2 + n) != 0:
323
raise ValueError('2g-2+n must divide the sum of entries of Avector.')
324
if k is None:
325
k = floor(Asum / (2 * g - 2 + n))
326
if sum(Avector) != k * (2 * g - 2 + n):
327
raise ValueError('The entries of Avector must sum to k*(2g-2+n).')
328
else:
329
if k is None:
330
if Asum % (2 * g - 2 + n) == 0:
331
k = floor(Asum / (2 * g - 2 + n)) # we are guessing that this is the right k, but only unique mod r
332
else:
333
raise ValueError('If a value of r_coeff is specified, the parameter k must be specified.')
334
if sum(Avector) % r_coeff != k * (2 * g - 2 + n) % r_coeff:
335
raise ValueError('The entries of Avector must sum to k*(2g-2+n) mod r_coeff.')
336
if spin and k % 2 != 1:
337
raise ValueError('The integer k has to be odd for spin DR.')
338
339
R = TautologicalRing(g, n, moduli, base_ring=base_ring)
340
gens = tautgens(g, n, d, decst=True, moduli=moduli)
341
342
v = vector([DR_coeff_new(decs, g, n, d, Avector, k, rpoly, chiodo_coeff, r_coeff, spin=spin) for decs in gens])
343
344
if not chiodo_coeff:
345
v *= 1 / ZZ(2)**d
346
# v1=vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v,g,d,tuple(range(1, n+1)),DR.MODULI_ST)])
347
348
if basis:
349
v1 = Tautvecttobasis(v, g, n, d)
350
if tautout:
351
return R.from_basis_vector(v1, d)
352
return v1
353
else:
354
if tautout:
355
return R.from_vector(v, d)
356
return v
357
358
359
def DR_coeff_setup(G, g, n, d, Avector, k, chiodo_coeff):
360
gamma = G.gamma
361
kappa, psi = G.poly.monom[0]
362
exp_list = []
363
exp_list_fine = []
364
if chiodo_coeff:
365
R = PolynomialRing(QQ, 'x,r', 2)
366
x, r = R.gens()
367
scalar_factor = R.one() / G.automorphism_number()
368
else:
369
scalar_factor = QQ((1, G.automorphism_number()))
370
given_weights = [-k * (2 * gv - 2 + len(gamma._legs[i]))
371
for i, gv in enumerate(gamma._genera)]
372
373
# contributions to scalar_factor from kappa-classes
374
for v in range(gamma.num_verts()):
375
if chiodo_coeff:
376
scalar_factor *= prod(((-1)**(m + 1) * bernoulli_polynomial(x, m + 2)(k / r, r) /
377
(m + 1) / (m + 2))**ex / factorial(ex) for m, ex in enumerate(kappa[v]))
378
else:
379
if len(kappa[v]) == 1: # some kappa_1^e at this vertex
380
scalar_factor *= (-k**2)**(kappa[v][0]) / factorial(kappa[v][0])
381
382
# contributions to scalar_factor and given_weights from markings
383
for i in range(1, n + 1):
384
v = gamma.vertex(i)
385
psipow = psi.get(i, 0) # if i in dictionary, have psi_i^(psi[i]), otherwise have psi_i^0
386
given_weights[v] += Avector[i - 1]
387
if chiodo_coeff:
388
sfcontrib = QQ(0)
389
for par in Partitions(psipow):
390
parexp = par.to_exp() # list [1,0,4] corresponds to psi^1 * (psi^3)^4
391
sfcontrib += prod(((-1)**m * bernoulli_polynomial(x, m + 2)
392
(Avector[i - 1] / r, r) / (m + 1) / (m + 2))**ex / factorial(ex) for m, ex in enumerate(parexp))
393
scalar_factor *= sfcontrib
394
else:
395
scalar_factor *= (Avector[i - 1])**(2 * psipow) / factorial(psipow)
396
397
# contributions to scalar_factor and explist from edges
398
for (lu, lv) in gamma._edges:
399
psipowu = psi.get(lu, 0)
400
psipowv = psi.get(lv, 0)
401
exp_list.append(psipowu + psipowv + 1)
402
exp_list_fine.append([psipowu, psipowv])
403
if chiodo_coeff:
404
pass # formula is more complicated, take care of entire edge term below
405
else:
406
scalar_factor *= ((-1)**(psipowu + psipowv)) / factorial(psipowv) / \
407
factorial(psipowu) / (psipowu + psipowv + 1)
408
409
return exp_list, given_weights, scalar_factor, exp_list_fine
410
411
412
def DR_coeff_new(G, g, n, d, Avector, k, rpoly, chiodo_coeff, r_coeff, spin=False):
413
gamma = G.gamma # underlying stable graph of decstratum G
414
kappa, _ = G.poly.monom[0]
415
# 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^2
416
# _ = psi is a dictionary and psi[3]=4 means there is a decoration psi^4 at marking/half-edge 3
417
418
if not chiodo_coeff and any(len(kalist) > 1 for kalist in kappa):
419
return 0 # vertices can only carry powers of kappa_1, no higher kappas allowed
420
421
# value from which on the Pixton-sum below will be polynomial in m
422
m0 = ceil(sum([abs(i) for i in Avector]) / ZZ(2)) + g * abs(k)
423
# TODO: verify this is still ok with chiodo_coeff
424
h0 = gamma.num_edges() - gamma.num_verts() + 1 # first Betti number of the graph Gamma
425
exp_list, given_weights, scalar_factor, exp_list_fine = DR_coeff_setup(G, g, n, d, Avector, k, chiodo_coeff)
426
427
if chiodo_coeff:
428
deg = 2 * d
429
else:
430
deg = 2 * sum(exp_list) # degree of polynomial in m
431
# TODO: verify this is still ok with chiodo_coeff
432
433
# R = PolynomialRing(QQ, len(gamma.edges) + 1, 'd')
434
# P=prod([(di*(d0-di))**exp_list[i] for i,di in enumerate(R.gens()[1:])])/(d0**h0)
435
if r_coeff is not None:
436
# it might be that given_weights doesn't sum to zero (only to zero mod r)
437
# artificially subtract the sum from the first entry; this does not change the result mod r
438
given_weights[0] -= sum(given_weights)
439
440
u = gamma.flow_solve(given_weights)
441
Vbasis = gamma.cycle_basis()
442
443
# if r_coeff is not None, we want one particular value of r, so let function mpoly_special
444
# interpolate a degree 0 polynomial at this value
445
if r_coeff is not None:
446
deg = 0
447
m0 = r_coeff - 1
448
449
if spin:
450
spin_factor = QQ(1) / QQ(2 ** (g - h0))
451
else:
452
spin_factor = QQ(1)
453
454
# mpoly = mpoly_affine_sum(P, u, Vbasis,m0+1,deg)
455
mpoly = spin_factor * mpoly_special(exp_list, h0, u, Vbasis, m0 + 1, deg, chiodo_coeff, r_coeff, scalar_factor, exp_list_fine, d, spin)
456
457
# mpoly = interpolate(mrange, mvalues)
458
if rpoly:
459
if chiodo_coeff:
460
return mpoly
461
else:
462
return mpoly * scalar_factor
463
if chiodo_coeff:
464
return mpoly.subs(r=0)
465
return mpoly.subs(r=0) * scalar_factor
466
467
468
def mpoly_special(exp_list, h0, u, Vbasis, start, degr, chiodo_coeff, r_coeff, scalar_factor, exp_list_fine, d, spin=False):
469
r"""
470
Return the sum of the rational function in DR_coeff_new over the
471
affine space u + V modulo r in ZZ^n as a univariate polynomial in r.
472
V is the lattice with basis Vbasis.
473
Use that this sum is polynomial in r starting from r=start and the polynomial has
474
degree degr (for now).
475
"""
476
mrange = list(range(start, start + degr + 1))
477
if chiodo_coeff and r_coeff is None:
478
# temporary security measure: interpolate with one more value, check degree still at most degr
479
mrange.append(start + degr + 1)
480
if spin:
481
mrange = [2 * q for q in mrange] # if spin is true, only consider r even
482
mvalues = []
483
rank = len(Vbasis)
484
ulen = len(u)
485
486
for m in mrange:
487
total = 0
488
for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):
489
v = u + sum([coeff[i] * Vbasis[i] for i in range(rank)])
490
v = [vi % m for vi in v]
491
if chiodo_coeff:
492
if not spin or all(x % 2 == 1 for x in v): # if spin is true, only count the odd weightings
493
total += prod(econtrib(exp_list_fine[i][0], exp_list_fine[i][1], -v[i] % m, m) for i in range(ulen))
494
else:
495
if not spin or all(x % 2 == 1 for x in v): # if spin is true, only count the odd weightings
496
total += prod([(v[i] * (m - v[i]))**exp_list[i] for i in range(ulen)])
497
if chiodo_coeff:
498
mvalues.append(scalar_factor(0, m) * total * (m ** (2 * d - h0)))
499
else:
500
mvalues.append(total / QQ(m ** h0))
501
result = interpolate(mrange, mvalues, 'r')
502
if result.degree() > degr:
503
raise ValueError('Polynomial has higher degree in r than expected!')
504
return result
505
506
507
def econtrib(e1, e2, w1, r):
508
polycoeff = edge_power_series(e1 + e2)[(e1, e2)]
509
return polycoeff(*((-1)**m * bernoulli_polynomial(w1 / ZZ(r), m + 2) / (m + 1) / (m + 2) for m in range(e1 + e2 + 1)))
510
511
512
@cached_function
513
def edge_power_series(deg):
514
R = PolynomialRing(QQ, 'a', deg + 1)
515
A = R.gens() # A = [a0, ..., a(deg)]
516
S = PowerSeriesRing(R, 'X,Y', default_prec=deg + 2)
517
X, Y = S.gens()
518
ex = sum(A[i] * (X**(i + 1) - (-Y)**(i + 1)) for i in range(deg + 1))
519
h = (1 - exp(ex)) / (X + Y)
520
return {tuple(k): it for k, it in h.dict().items()} # dictionary (1, 0) : -a1 - (1/2)*a0^2, ... with R-values
521
522
523
def mpoly_affine_sum(P, u, Vbasis, start, degr):
524
r"""
525
Return the sum of the polynomial P in variables r, d1, ..., dn over the
526
affine space u + V modulo r in ZZ^n as a univariate polynomial in r.
527
V is the lattice with basis Vbasis.
528
Use that this sum is polynomial in r starting from r=start and the polynomial has
529
degree degr (for now).
530
"""
531
mrange = list(range(start, start + degr + 2))
532
mvalues = []
533
rank = len(Vbasis)
534
535
for m in mrange:
536
total = 0
537
for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):
538
v = u + sum([coeff[i] * Vbasis[i] for i in range(rank)])
539
v = [vi % m for vi in v]
540
total += P(m, *v)
541
mvalues.append(total)
542
543
return interpolate(mrange, mvalues, 'r')
544
545
############
546
#
547
# DR polynomial
548
#
549
############
550
551
552
def multivariate_interpolate(f, d, n, gridwidth=1, R=None, generator=None, gridshift=None, transf_poly=None):
553
r"""Takes a vector/number-valued function f on n variables and interpolates it on a grid around 0.
554
Returns a vector with entries in a polynomial ring.
555
556
INPUT:
557
558
- ``d`` -- integer; maximal degree of output-polynomial in any of the variables
559
560
- ``gridwidth``-- integer (default: `1`); width of the grid used for interpolation
561
562
- ``R`` -- polynomial ring (default: `None`); expected to be polynomial ring in
563
at least n variables; if None, use ring over QQ in variables z0,...,z(n-1)
564
565
- ``generator``-- list (default: `None`); list of n variables in the above polynomial
566
ring to be used in output; if None, use first n generators of ring
567
568
- ``enlarge``-- integer (default: `1`); the factor of enlarging the gridwidth during
569
interpolating the values
570
571
- ``gridshift``-- tuple (default: `None`); of length n to shift the
572
grid before interpolation takes place
573
574
- ``transf_poly``-- polynomial (default: `None`); a polynomial of single variable of how to
575
modify the input values to the function
576
577
EXAMPLES::
578
579
sage: from admcycles.double_ramification_cycle import multivariate_interpolate
580
sage: from sage.modules.free_module_element import vector
581
sage: def foo(x,y):
582
....: return vector((x**2+y,2*x*y))
583
....:
584
sage: multivariate_interpolate(foo,2,2)
585
(z0^2 + z1, 2*z0*z1)
586
"""
587
if R is None:
588
R = PolynomialRing(QQ, 'z', n)
589
if generator is None:
590
generator = R.gens()
591
592
if n == 0: # return constant
593
return R.one() * f()
594
595
cube = [list(range(d + 1))] * n
596
points = itertools.product(*cube)
597
# shift cube containing evaluation points in negative direction to reduce abs value of evaluation points
598
# the customized shift will also be taken into account
599
if gridshift is None:
600
shift = [-floor((d + 1) / QQ(2)) * gridwidth for i in range(n)]
601
else:
602
shift = [gridshift[i] - floor((d + 1) / QQ(2)) * gridwidth for i in range(n)]
603
result = 0
604
605
for p in points:
606
# compute Lagrange polynomial not vanishing exactly at gridwidth*p
607
lagr = prod([prod([generator[i] - (j * gridwidth + shift[i])
608
for j in range(d + 1) if j != p[i]]) for i in range(n)])
609
610
pval = [gridwidth * p[i] + shift[i] for i in range(n)]
611
value = lagr.subs({generator[i]: pval[i] for i in range(n)})
612
if transf_poly is not None:
613
pval = [transf_poly(val) for val in pval]
614
615
# global fex,lagrex, pvalex
616
# fex=f
617
# lagrex=lagr
618
# pvalex=pval
619
if n == 1: # avoid problems with multiplication of polynomial with vector
620
mult = lagr / value
621
result += vector((e * mult for e in f(*pval)))
622
else:
623
result += f(*pval) / value * lagr
624
return result
625
626
627
def DRpoly(g, d, n, dplus=0, tautout=True, basis=False, ring=None, gens=None, gensout=False, chiodo_coeff=False, moduli='st', spin=False):
628
r"""
629
Return the Double ramification cycle in genus g with n markings in degree d as a
630
tautclass with polynomial coefficients. Evaluated at a point (a_1, ..., a_n) with
631
sum a_i = k(2g-2+n) it equals DR_cycle(g,(a_1, ..., a_n),d).
632
633
The computation uses interpolation and the fact that DR is a polynomial in the a_i
634
of degree 2*d.
635
636
INPUT:
637
638
- ``dplus`` -- integer (default: `0`); if dplus>0, the interpolation is performed
639
on a larger grid as a consistency check
640
641
- ``tautout``-- bool (default: `True`); if False, returns a polynomial-valued vector
642
(in all generators for basis=false, in the basis of the ring for basis=true)
643
644
- ``basis`` -- bool (default: `False`); if True, use FZ relations to give out the
645
DR cycle in a basis of the tautological ring
646
647
- ``ring`` -- polynomial ring (default: `None`); expected to be polynomial ring in
648
at least n variables; if None, use ring over QQ in variables a1,...,an
649
650
- ``gens`` -- list (default: `None`); list of n variables in the above polynomial
651
ring to be used in output; if None, use first n generators of ring
652
653
- ``gensout``-- bool (default: `False`); if True, return (DR polynomial, list of generators
654
of coefficient ring)
655
656
- ``spin``-- bool (default: False`); if True, return the spin DR polynomial
657
658
EXAMPLES::
659
660
sage: from admcycles import DRpoly, DR_cycle, TautologicalRing
661
sage: R = TautologicalRing(1, 2)
662
sage: D, (a1, a2) = DRpoly(1, 1, 2, gensout=True)
663
sage: D = D.subs({a2:-a1})
664
sage: D = D.simplify()
665
sage: D
666
Graph : [1] [[1, 2]] []
667
Polynomial : 1/2*a1^2*psi_1 + 1/2*a1^2*psi_2
668
<BLANKLINE>
669
Graph : [0] [[4, 5, 1, 2]] [(4, 5)]
670
Polynomial : -1/24
671
sage: (D*R.psi(1)).evaluate() # intersection number with psi_1
672
1/24*a1^2 - 1/24
673
sage: (D.subs({a1:4})-DR_cycle(1,(4,-4))).is_zero() # polynomial agrees with DR_cycle at (4,-4)
674
True
675
676
DR vanishes in degree d>g ([CJ18]_)::
677
678
sage: DRpoly(1,2,2).is_zero()
679
True
680
sage: DRpoly(1,1,2,moduli='ct')
681
Graph : [1] [[1, 2]] []
682
Polynomial : (-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_2
683
<BLANKLINE>
684
Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]
685
Polynomial : -1/8*a1^2 - 1/4*a1*a2 - 1/8*a2^2
686
sage: R.<a1,a2,a3,b1,b2,b3> = PolynomialRing(QQ, 6)
687
sage: Da = DRpoly(1, 1, 3, ring=R, gens=[a1, a2, a3])
688
sage: Db = Da.subs({a1:b1, a2:b2, a3:b3})
689
sage: Dab = Da.subs({a1:a1+b1, a2:a2+b2, a3:a3+b3})
690
sage: diff = Da*Db - Da*Dab # this should vanish in compact type by [HoPiSc19]_
691
sage: diff.is_zero()
692
False
693
sage: diff.is_zero(moduli='ct')
694
True
695
"""
696
def f(*Avector):
697
return DR_cycle(g, Avector, d, tautout=False, basis=basis, moduli=moduli, spin=spin)
698
# k=floor(QQ(sum(Avector))/(2 * g - 2 + n))
699
# return vector([QQ(e) for e in DR_red(g,d,n,Avector, k, basis)])
700
# return vector(DR_compute(g,d,n,Avector, k))
701
702
if ring is None:
703
ring = PolynomialRing(QQ, ['a%s' % i for i in range(1, n + 1)])
704
if gens is None:
705
gens = ring.gens()[0:n]
706
707
R = TautologicalRing(g, n, moduli)
708
gridwidth = 2 * g - 2 + n
709
if spin:
710
shiftvec = tuple([g - 2 + n] + [-1 for i in range(n - 1)]) # to set an initial of Avector whose k is odd
711
R_transf = PolynomialRing(QQ, 'x', 1)
712
transf_poly = 2 * R_transf.gens()[0] + 1
713
interp = multivariate_interpolate(f, 2 * d + dplus, n, gridwidth, R=ring, generator=gens, gridshift=shiftvec, transf_poly=transf_poly)
714
interp = interp.subs({gens[i]: (QQ(1) / 2) * (gens[i] - QQ(1)) for i in range(n)})
715
else:
716
interp = multivariate_interpolate(f, 2 * d + dplus, n, gridwidth, R=ring, generator=gens)
717
718
if not tautout:
719
ans = interp
720
else:
721
if not basis:
722
ans = R.from_vector(interp, d)
723
else:
724
ans = R.from_basis_vector(interp, d)
725
if gensout:
726
return (ans, gens)
727
return ans
728
729
730
def degree_filter(polyvec, d):
731
r"""Takes a vector of polynomials in several variables and returns a vector containing the (total)
732
degree d part of these polynomials.
733
734
INPUT:
735
736
- vector of polynomials
737
738
- integer degree
739
740
EXAMPLES::
741
742
sage: from admcycles.double_ramification_cycle import degree_filter
743
sage: R.<x,y> = PolynomialRing(QQ, 2)
744
sage: v = vector((x+y**2+2*x*y,1+x**3+x*y))
745
sage: degree_filter(v, 2)
746
(2*x*y + y^2, x*y)
747
"""
748
resultvec = []
749
for pi in polyvec:
750
s = 0
751
for c, m in pi:
752
if m.degree() == d:
753
s += c * m
754
resultvec.append(s)
755
return vector(resultvec)
756
757
758
def Hain_divisor(g, A):
759
r"""
760
Returns 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.
761
762
Note: D^g/g! agrees with the Double-Ramification cycle in compact type.
763
764
EXAMPLES::
765
766
sage: from admcycles import *
767
768
sage: R = PolynomialRing(QQ, 'z', 3)
769
sage: z0, z1, z2 = R.gens()
770
sage: u = Hain_divisor(2, (z0, z1, z2))
771
sage: g = DRpoly(2, 1, 3, ring=R, gens=[z0, z1, z2]) #u,g should agree inside compact type
772
sage: (u.vector() - g.vector()).subs({z0: -z1-z2})
773
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1/24)
774
"""
775
n = len(A)
776
R = TautologicalRing(g, n)
777
778
def delt(l, I):
779
# takes genus l and subset I of markings 1,...,n and returns generalized boundary divisor Delta_{l,I}
780
I = set(I)
781
if l == 0 and len(I) == 1:
782
return -R.psi(list(I)[0])
783
if l == g and len(I) == n - 1:
784
i_set = set(range(1, n + 1)) - I
785
return -R.psi(list(i_set)[0])
786
if (l == 0 and len(I) == 0) or (l == g and len(I) == n):
787
return R.zero()
788
789
Icomp = set(range(1, n + 1)) - I
790
gra = StableGraph([l, g - l], [list(I) + [n + 1], list(Icomp) + [n + 2]],
791
[(n + 1, n + 2)])
792
return QQ((1, gra.automorphism_number())) * R(gra)
793
794
result = sum([sum([(sum([A[i - 1] for i in I])**2) * delt(l, I)
795
for I in Subsets(list(range(1, n + 1)))])
796
for l in range(g + 1)])
797
# note index i-1 since I subset {1,...,n} but array indices subset {0,...,n-1}
798
799
return QQ((-1, 4)) * result
800
801
802
# Norbury_ThetaClass
803
def ThetaClass(g, n, moduli='st'):
804
r"""
805
Return the class Theta_{g,n} from the paper [Nor]_ by Norbury.
806
807
INPUT:
808
809
- ``moduli`` -- string (default: `'st'`); moduli on which Theta_{g,n} is computed
810
811
EXAMPLES::
812
813
We can verify many of the general properties of Theta_{g,n} in examples, starting
814
with the initial condition Theta_{1,1} = 3 * psi_{1,1}.
815
816
sage: from admcycles import ThetaClass, TautologicalRing
817
sage: R = TautologicalRing(1, 1)
818
sage: (ThetaClass(1,1) - 3*R.psi(1)).is_zero()
819
True
820
821
Likewise, we can check that the Theta-class pulls back correctly under boundary
822
gluing maps.
823
824
sage: from admcycles.admcycles import StableGraph, prodtautclass
825
sage: A = ThetaClass(3,0)
826
sage: gamma1 = StableGraph([1,2],[[1],[2]],[(1,2)])
827
sage: pb1 = gamma1.boundary_pullback(A)
828
sage: pb1.totensorTautbasis(4)
829
[[0], [ 207 -189/2 27/2], [], [], []]
830
sage: check1 = prodtautclass(gamma1, protaut = [ThetaClass(1,1), ThetaClass(2,1)])
831
sage: check1.totensorTautbasis(4)
832
[[0], [ 207 -189/2 27/2], [], [], []]
833
834
A similar pullback property holds for the gluing map associated to the
835
divisor of irreducible nodal curves.
836
837
sage: B = ThetaClass(2,1)
838
sage: gamma2 = StableGraph([1],[[1,2,3]],[(2,3)])
839
sage: pb2 = gamma2.boundary_pullback(B)
840
sage: pb2.totensorTautbasis(3)
841
(6)
842
sage: ThetaClass(1,3).basis_vector()
843
(6)
844
845
Finally, we check the property Theta_{g,n+1} = pi*(Theta_{g,n}) * psiclass_{n+1}::
846
847
sage: C = ThetaClass(2,0)
848
sage: R = TautologicalRing(2, 1)
849
sage: (C.forgetful_pullback([1]) * R.psi(1) - B).is_zero()
850
True
851
"""
852
r = ZZ(2)
853
s = -ZZ(1)
854
d = ZZ(2) * g - 2 + n
855
x = -ZZ(1)
856
A = n * (1,)
857
return 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)
858
859