Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: admcycles
Views: 724
Visibility: Unlisted (only visible to those who know the link)
Image: ubuntu2004
1
r"""
2
Double ramification cycle
3
"""
4
from __future__ import absolute_import, print_function
5
6
import itertools
7
8
from six.moves import range
9
10
from admcycles.admcycles import Tautv_to_tautclass, Tautvb_to_tautclass, Tautvecttobasis, tautgens, psiclass, tautclass
11
from admcycles.stable_graph import StableGraph
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.polynomial_ring import polygen
21
from sage.rings.polynomial.multi_polynomial_element import MPolynomial
22
23
############
24
#
25
# Old DR-cycle implementation
26
#
27
############
28
29
30
def DR_cycle_old(g, Avector, d=None, k=None, tautout=True, basis=False):
31
r"""Returns the k-twisted Double ramification cycle in genus g and codimension d
32
for the partition Avector of k*(2g-2+n).
33
34
In the notation of the [JPPZ]-paper, the output is 2^(-d) * P_g^{d,k}(Avector).
35
36
Note: This is based on the old implementation DR_compute by Pixton. A new one,
37
which can be faster in many examples is DR_cycle.
38
39
INPUT:
40
41
- ``tautout` -- bool (default: `True`); if False, returns a vector
42
(in all generators for basis=false, in the basis of the ring for basis=true)
43
44
- ``basis` -- bool (default: `False`); if True, use FZ relations to give out the
45
DR cycle in a basis of the tautological ring
46
"""
47
if d is None:
48
d = g
49
n = len(Avector)
50
if k is None:
51
k = floor(sum(Avector)/(2*g-2+n))
52
if sum(Avector) != k*(2*g-2+n):
53
raise ValueError('2g-2+n must divide the sum of entries of Avector.')
54
55
v = DR.DR_compute(g, d, n, Avector, k)
56
v1 = vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v, g, d, tuple(range(1, n+1)), DR.MODULI_ST)])
57
58
if basis:
59
v2 = Tautvecttobasis(v1, g, n, d)
60
if tautout:
61
return Tautvb_to_tautclass(v2, g, n, d)
62
return v2
63
else:
64
if tautout:
65
return Tautv_to_tautclass(v1, g, n, d)
66
return v1
67
68
69
############
70
#
71
# New DR-cycle implementation
72
#
73
############
74
75
def DR_cycle(g, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False):
76
r"""Returns the k-twisted Double ramification cycle in genus g and codimension d
77
for the partition Avector of k*(2g-2+n). If some elements of Avector are elements of a
78
polynomial ring, compute DR-polynomial and substitute the entries of Avector - the result
79
then has coefficients in a polynomial ring.
80
81
In the notation of the [JPPZ]-paper, the output is 2^(-d) * P_g^{d,k}(Avector).
82
83
Note: This is a reimplementation of Pixton's DR_compute which can be faster in many examples.
84
To access the old version, use DR_cycle_old - it might be faster on older SageMath-versions.
85
86
INPUT:
87
88
- ``rpoly`` -- bool (default: `False`); if True, return tautclass 2^(-d) * P_g^{d,r,k}(Avector)
89
whose coefficients are polynomials in the variable r.
90
91
- ``tautout` -- bool (default: `True`); if False, returns a vector
92
(in all generators for basis=false, in the basis of the ring for basis=true)
93
94
- ``basis` -- bool (default: `False`); if True, use FZ relations to give out the
95
DR cycle in a basis of the tautological ring
96
97
EXAMPLES::
98
99
sage: from admcycles import DR_cycle, DR_cycle_old
100
sage: DR_cycle(1, (2, 3, -5))
101
Graph : [1] [[1, 2, 3]] []
102
Polynomial : 2*psi_1^1
103
<BLANKLINE>
104
Graph : [1] [[1, 2, 3]] []
105
Polynomial : 9/2*psi_2^1
106
<BLANKLINE>
107
Graph : [1] [[1, 2, 3]] []
108
Polynomial : 25/2*psi_3^1
109
<BLANKLINE>
110
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
111
Polynomial : (-2)*
112
<BLANKLINE>
113
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
114
Polynomial : (-9/2)*
115
<BLANKLINE>
116
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
117
Polynomial : (-25/2)*
118
<BLANKLINE>
119
Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]
120
Polynomial : (-1/24)*
121
sage: D = DR_cycle(1, (1, 3, -4), d=2)
122
sage: D2 = DR_cycle_old(1, (1, 3, -4), d=2) # long time
123
sage: D.toTautvect() == D2.toTautvect() # long time
124
True
125
sage: D.is_zero() # Clader-Janda: P_g^{d,k}(Avector)=0 for d>g
126
True
127
sage: v = DR_cycle(2, (3,), d=4, rpoly=true).evaluate()
128
sage: v # Conjecture by Longting Wu predicts that r^1-term vanishes
129
-1/1728*r^6 + 1/576*r^5 + 37/34560*r^4 - 13/6912*r^3 - 1/1152*r^2
130
131
We can create a polynomial ring and use an Avector with entries in this ring.
132
The result is a tautological class with polynomial coefficients::
133
134
sage: R.<a1,a2>=PolynomialRing(QQ,2)
135
sage: Q=DR_cycle(1,(a1,a2,-a1-a2))
136
sage: Q
137
Graph : [1] [[1, 2, 3]] []
138
Polynomial : 1/2*a1^2*psi_1^1
139
<BLANKLINE>
140
Graph : [1] [[1, 2, 3]] []
141
Polynomial : 1/2*a2^2*psi_2^1
142
<BLANKLINE>
143
Graph : [1] [[1, 2, 3]] []
144
Polynomial : (1/2*a1^2 + a1*a2 + 1/2*a2^2)*psi_3^1
145
<BLANKLINE>
146
Graph : [0, 1] [[2, 3, 5], [1, 6]] [(5, 6)]
147
Polynomial : (-1/2*a1^2)*
148
<BLANKLINE>
149
Graph : [0, 1] [[1, 3, 5], [2, 6]] [(5, 6)]
150
Polynomial : (-1/2*a2^2)*
151
<BLANKLINE>
152
Graph : [0, 1] [[1, 2, 5], [3, 6]] [(5, 6)]
153
Polynomial : (-1/2*a1^2 - a1*a2 - 1/2*a2^2)*
154
<BLANKLINE>
155
Graph : [0] [[5, 6, 1, 2, 3]] [(5, 6)]
156
Polynomial : (-1/24)*
157
158
159
TESTS::
160
161
sage: from admcycles import DR_cycle, DR_cycle_old
162
sage: D = DR_cycle(2, (3, 4, -2), k=1) # long time
163
sage: D2 = DR_cycle_old(2, (3, 4, -2), k=1) # long time
164
sage: D.toTautvect() == D2.toTautvect() # long time
165
True
166
sage: D = DR_cycle(2, (1, 4, 5), k=2) # long time
167
sage: D2 = DR_cycle_old(2, (1, 4, 5), k=2) # long time
168
sage: D.toTautvect() == D2.toTautvect() # long time
169
True
170
sage: D = DR_cycle(3, (2,4), k=1) # long time
171
sage: D2 = DR_cycle_old(3, (2,4), k=1) # long time
172
sage: D.toTautvect() == D2.toTautvect() # long time
173
True
174
175
sage: D = DR_cycle(2, (3,),tautout=False); D
176
(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)
177
sage: D2=DR_cycle_old(2, (3,), tautout=False); D2
178
(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)
179
sage: D3 = DR_cycle(2, (3,), tautout=False, basis=True); D3
180
(-5, 2, -8, 18, 3/2)
181
sage: D4 = DR_cycle_old(2, (3,), tautout=False, basis=True); D4
182
(-5, 2, -8, 18, 3/2)
183
"""
184
if d is None:
185
d = g
186
n = len(Avector)
187
188
if any(isinstance(ai, MPolynomial) for ai in Avector):
189
# compute DRpoly and substitute the ai in there
190
pol, gens = DRpoly(g, d, n, tautout=tautout, basis=basis, gensout=True)
191
subsdict = {gens[i]: Avector[i] for i in range(n)}
192
193
if tautout:
194
pol.coeff_subs(subsdict)
195
pol.simplify()
196
return pol
197
return pol.apply_map(lambda x: x.subs(subsdict))
198
199
if k is None:
200
k = floor(sum(Avector)/(2*g-2+n))
201
if sum(Avector) != k*(2*g-2+n):
202
raise ValueError('2g-2+n must divide the sum of entries of Avector.')
203
204
gens = tautgens(g, n, d, decst=True)
205
v = vector([DR_coeff_new(decs, g, n, d, Avector, k, rpoly) for decs in gens]) / ZZ(2)**d
206
#v1=vector([QQ(a) for a in DR.convert_vector_to_monomial_basis(v,g,d,tuple(range(1, n+1)),DR.MODULI_ST)])
207
208
if basis:
209
v1 = Tautvecttobasis(v, g, n, d)
210
if tautout:
211
return Tautvb_to_tautclass(v1, g, n, d)
212
return v1
213
else:
214
if tautout:
215
return Tautv_to_tautclass(v, g, n, d)
216
return v
217
218
219
def interpolate(A, B):
220
r"""
221
Univariate Lagrange interpolation over the rationals.
222
223
EXAMPLES::
224
225
sage: from admcycles.double_ramification_cycle import interpolate
226
sage: p = interpolate([1/2, -2, 3], [4/5, 2/3, -7/6])
227
sage: p(1/2)
228
4/5
229
sage: p(-2)
230
2/3
231
sage: p(3)
232
-7/6
233
234
TESTS::
235
236
sage: from admcycles.double_ramification_cycle import interpolate
237
sage: parent(interpolate([], []))
238
Univariate Polynomial Ring in r over Rational Field
239
"""
240
R = polygen(QQ, 'r').parent()
241
return R.lagrange_polynomial(zip(A, B))
242
243
244
def DR_coeff_setup(G, g, n, d, Avector, k):
245
gamma = G.gamma
246
kappa, psi = G.poly.monom[0]
247
exp_list = []
248
scalar_factor = QQ((1, G.automorphism_number()))
249
given_weights = [-k * (2 * gv - 2 + len(gamma._legs[i]))
250
for i, gv in enumerate(gamma._genera)]
251
252
# contributions to scalar_factor from kappa-classes
253
for v in range(gamma.num_verts()):
254
if len(kappa[v]) == 1: # some kappa_1^e at this vertex
255
scalar_factor *= (-k**2)**(kappa[v][0]) / factorial(kappa[v][0])
256
257
# contributions to scalar_factor and given_weights from markings
258
for i in range(1, n + 1):
259
v = gamma.vertex(i)
260
psipow = psi.get(i, 0) # if i in dictionary, have psi_i^(psi[i]), otherwise have psi_i^0
261
given_weights[v] += Avector[i - 1]
262
scalar_factor *= (Avector[i - 1])**(2 * psipow) / factorial(psipow)
263
264
# contributions to scalar_factor and explist from edges
265
for (lu, lv) in gamma._edges:
266
psipowu = psi.get(lu, 0)
267
psipowv = psi.get(lv, 0)
268
exp_list.append(psipowu + psipowv + 1)
269
scalar_factor *= ((-1)**(psipowu+psipowv)) / factorial(psipowv) / factorial(psipowu) / (psipowu+psipowv+1)
270
271
return exp_list, given_weights, scalar_factor
272
273
274
def DR_coeff_new(G, g, n, d, Avector, k, rpoly):
275
gamma = G.gamma # underlying stable graph of decstratum G
276
kappa, _ = G.poly.monom[0]
277
# kappa is a list of lenght = # vertices, of entries like [3,0,2] meaning that at this vertex there is a kappa_1^3 * kappa_3^2
278
# _ = psi is a dictionary and psi[3]=4 means there is a decoration psi^4 at marking/half-edge 3
279
280
if any(len(kalist) > 1 for kalist in kappa):
281
return 0 # vertices can only carry powers of kappa_1, no higher kappas allowed
282
283
m0 = ceil(sum([abs(i) for i in Avector]) / ZZ(2)) + g*abs(k) # value from which on the Pixton-sum below will be polynomial in m
284
h0 = gamma.num_edges() - gamma.num_verts() + 1 # first Betti number of the graph Gamma
285
exp_list, given_weights, scalar_factor = DR_coeff_setup(G, g, n, d, Avector, k)
286
287
deg = 2 * sum(exp_list) # degree of polynomial in m
288
# R = PolynomialRing(QQ, len(gamma.edges) + 1, 'd')
289
# P=prod([(di*(d0-di))**exp_list[i] for i,di in enumerate(R.gens()[1:])])/(d0**h0)
290
u = gamma.flow_solve(given_weights)
291
Vbasis = gamma.cycle_basis()
292
293
#mpoly = mpoly_affine_sum(P, u, Vbasis,m0+1,deg)
294
mpoly = mpoly_special(exp_list, h0, u, Vbasis, m0+1, deg)
295
296
# mpoly = interpolate(mrange, mvalues)
297
if rpoly:
298
return mpoly * scalar_factor
299
return mpoly.subs(r=0) * scalar_factor
300
301
302
def mpoly_special(exp_list, h0, u, Vbasis, start, degr):
303
r"""
304
Return the sum of the rational function in DR_coeff_new over the
305
affine space u + V modulo r in ZZ^n as a univariate polynomial in r.
306
V is the lattice with basis Vbasis.
307
Use that this sum is polynomial in r starting from r=start and the polynomial has
308
degree degr (for now).
309
"""
310
mrange = list(range(start, start + degr + 2))
311
mvalues = []
312
rank = len(Vbasis)
313
ulen = len(u)
314
315
for m in mrange:
316
total = 0
317
for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):
318
v = u + sum([coeff[i]*Vbasis[i] for i in range(rank)])
319
v = [vi % m for vi in v]
320
total += prod([(v[i]*(m-v[i]))**exp_list[i] for i in range(ulen)])
321
mvalues.append(total/QQ(m**h0))
322
323
return interpolate(mrange, mvalues)
324
325
326
def mpoly_affine_sum(P, u, Vbasis, start, degr):
327
r"""
328
Return the sum of the polynomial P in variables r, d1, ..., dn over the
329
affine space u + V modulo r in ZZ^n as a univariate polynomial in r.
330
V is the lattice with basis Vbasis.
331
Use that this sum is polynomial in r starting from r=start and the polynomial has
332
degree degr (for now).
333
"""
334
mrange = list(range(start, start + degr + 2))
335
mvalues = []
336
rank = len(Vbasis)
337
338
for m in mrange:
339
total = 0
340
for coeff in itertools.product(*[list(range(m)) for i in range(rank)]):
341
v = u + sum([coeff[i] * Vbasis[i] for i in range(rank)])
342
v = [vi % m for vi in v]
343
total += P(m, *v)
344
mvalues.append(total)
345
346
return interpolate(mrange, mvalues)
347
348
############
349
#
350
# DR polynomial
351
#
352
############
353
354
355
def multivariate_interpolate(f, d, n, gridwidth=1, R=None, generator=None):
356
r"""Takes a vector/number-valued function f on n variables and interpolates it on a grid around 0.
357
Returns a vector with entries in a polynomial ring.
358
359
INPUT:
360
361
- ``d`` -- integer; maximal degree of output-polynomial in any of the variables
362
363
- ``gridwidth` -- integer (default: `1`); width of the grid used for interpolation
364
365
- ``R` -- polynomial ring (default: `None`); expected to be polynomial ring in
366
at least n variables; if None, use ring over QQ in variables z0,...,z(n-1)
367
368
- ``generator` -- list (default: `None`); list of n variables in the above polynomial
369
ring to be used in output; if None, use first n generators of ring
370
371
EXAMPLES::
372
373
sage: from admcycles.double_ramification_cycle import multivariate_interpolate
374
sage: from sage.modules.free_module_element import vector
375
sage: def foo(x,y):
376
....: return vector((x**2+y,2*x*y))
377
....:
378
sage: multivariate_interpolate(foo,2,2)
379
(z0^2 + z1, 2*z0*z1)
380
"""
381
if R is None:
382
R = PolynomialRing(QQ, 'z', n)
383
if generator is None:
384
generator = R.gens()
385
386
if n == 0: # return constant
387
return R.one() * f()
388
389
cube = [list(range(d + 1))] * n
390
points = itertools.product(*cube)
391
# shift cube containing evaluation points in negative direction to reduce abs value of evaluation points
392
shift = floor((d + 1) / QQ(2)) * gridwidth
393
result = 0
394
395
for p in points:
396
# compute Lagrange polynomial not vanishing exactly at gridwidth*p
397
lagr = prod([prod([generator[i]-(j*gridwidth-shift) for j in range(d+1) if j != p[i]]) for i in range(n)])
398
pval = [gridwidth*coord-shift for coord in p]
399
#global fex,lagrex, pvalex
400
#fex=f
401
#lagrex=lagr
402
#pvalex=pval
403
value = lagr.subs({generator[i]: pval[i] for i in range(n)})
404
if n == 1: # avoid problems with multiplication of polynomial with vector
405
mult = lagr/value
406
result += vector((e*mult for e in f(*pval)))
407
else:
408
result += f(*pval) / value * lagr
409
return result
410
411
412
def DRpoly(g, d, n, dplus=0, tautout=True, basis=False, ring=None, gens=None, gensout=False):
413
r"""
414
Return the Double ramification cycle in genus g with n markings in degree d as a
415
tautclass with polynomial coefficients. Evaluated at a point (a_1, ..., a_n) with
416
sum a_i = k(2g-2+n) it equals DR_cycle(g,(a_1, ..., a_n),d).
417
418
The computation uses interpolation and the fact that DR is a polynomial in the a_i
419
of degree 2*d.
420
421
INPUT:
422
423
- ``dplus`` -- integer (default: `0`); if dplus>0, the interpolation is performed
424
on a larger grid as a consistency check
425
426
- ``tautout` -- bool (default: `True`); if False, returns a polynomial-valued vector
427
(in all generators for basis=false, in the basis of the ring for basis=true)
428
429
- ``basis` -- bool (default: `False`); if True, use FZ relations to give out the
430
DR cycle in a basis of the tautological ring
431
432
- ``ring` -- polynomial ring (default: `None`); expected to be polynomial ring in
433
at least n variables; if None, use ring over QQ in variables a1,...,an
434
435
- ``gens` -- list (default: `None`); list of n variables in the above polynomial
436
ring to be used in output; if None, use first n generators of ring
437
438
- ``gensout` -- bool (default: `False`); if True, return (DR polynomial, list of generators
439
of coefficient ring)
440
441
EXAMPLES::
442
443
sage: from admcycles import DRpoly, DR_cycle, psiclass
444
sage: D, (a1, a2) = DRpoly(1, 1, 2, gensout=True)
445
sage: D.coeff_subs({a2:-a1}).simplify()
446
Graph : [1] [[1, 2]] []
447
Polynomial : 1/2*a1^2*psi_1^1
448
<BLANKLINE>
449
Graph : [1] [[1, 2]] []
450
Polynomial : 1/2*a1^2*psi_2^1
451
<BLANKLINE>
452
Graph : [0] [[4, 5, 1, 2]] [(4, 5)]
453
Polynomial : (-1/24)*
454
sage: (D*psiclass(1,1,2)).evaluate() # intersection number with psi_1
455
1/24*a1^2 - 1/24
456
sage: (D.coeff_subs({a1:4})-DR_cycle(1,(4,-4))).is_zero() # polynomial agrees with DR_cycle at (4,-4)
457
True
458
sage: DRpoly(1,2,2).is_zero() # Clader-Janda: DR vanishes in degree d>g
459
True
460
461
TESTS::
462
463
sage: R.<a1,a2,a3,b1,b2,b3> = PolynomialRing(QQ, 6)
464
sage: D = DRpoly(1, 1, 3, ring=R, gens=[a1, a2, a3])
465
sage: Da = deepcopy(D)
466
sage: Db = deepcopy(D).coeff_subs({a1:b1, a2:b2, a3:b3})
467
sage: Dab = deepcopy(D).coeff_subs({a1:a1+b1, a2:a2+b2, a3:a3+b3})
468
sage: diff = Da*Db - Da*Dab # this should vanish in compact type by [Holmes-Pixton-Schmitt]
469
sage: diff.toTautbasis(moduli='ct')
470
(0)
471
"""
472
def f(*Avector):
473
return DR_cycle(g, Avector, d, tautout=False, basis=basis)
474
#k=floor(QQ(sum(Avector))/(2 * g - 2 + n))
475
#return vector([QQ(e) for e in DR_red(g,d,n,Avector, k, basis)])
476
#return vector(DR_compute(g,d,n,Avector, k))
477
478
if ring is None:
479
ring = PolynomialRing(QQ, ['a%s' % i for i in range(1, n + 1)])
480
if gens is None:
481
gens = ring.gens()[0:n]
482
483
gridwidth = 2*g - 2 + n
484
interp = multivariate_interpolate(f, 2*d + dplus, n, gridwidth, R=ring, generator=gens)
485
486
if not tautout:
487
ans = interp
488
else:
489
if not basis:
490
ans = Tautv_to_tautclass(interp, g, n, d)
491
else:
492
ans = Tautvb_to_tautclass(interp, g, n, d)
493
if gensout:
494
return (ans, gens)
495
return ans
496
497
498
def degree_filter(polyvec, d):
499
r"""Takes a vector of polynomials in several variables and returns a vector containing the (total)
500
degree d part of these polynomials.
501
502
INPUT:
503
504
- vector of polynomials
505
506
- integer degree
507
508
EXAMPLES::
509
510
sage: from admcycles.double_ramification_cycle import degree_filter
511
sage: R.<x,y> = PolynomialRing(QQ, 2)
512
sage: v = vector((x+y**2+2*x*y,1+x**3+x*y))
513
sage: degree_filter(v, 2)
514
(2*x*y + y^2, x*y)
515
"""
516
resultvec = []
517
for pi in polyvec:
518
s = 0
519
for c, m in pi:
520
if m.degree() == d:
521
s += c * m
522
resultvec.append(s)
523
return vector(resultvec)
524
525
526
def Hain_divisor(g, A):
527
r"""
528
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.
529
530
Note: D^g/g! agrees with the Double-Ramification cycle in compact type.
531
532
EXAMPLES::
533
534
sage: from admcycles import *
535
536
sage: R = PolynomialRing(QQ, 'z', 3)
537
sage: z0, z1, z2 = R.gens()
538
sage: u = Hain_divisor(2, (z0, z1, z2))
539
sage: g = DRpoly(2, 1, 3, ring=R, gens=[z0, z1, z2]) #u,g should agree inside compact type
540
sage: (u.toTautvect() - g.toTautvect()).subs({z0: -z1-z2})
541
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1/24)
542
"""
543
n = len(A)
544
545
def delt(l, g, n, I):
546
# takes genus l and subset I of markings 1,...,n and returns generalized boundary divisor Delta_{l,I}
547
I = set(I)
548
if l == 0 and len(I) == 1:
549
return -psiclass(list(I)[0], g, n)
550
if l == g and len(I) == n-1:
551
i_set = set(range(1, n+1)) - I
552
return -psiclass(list(i_set)[0], g, n)
553
if (l == 0 and len(I) == 0) or (l == g and len(I) == n):
554
return tautclass([])
555
556
Icomp = set(range(1, n+1)) - I
557
gra = StableGraph([l, g-l], [list(I) + [n+1], list(Icomp) + [n+2]],
558
[(n+1, n+2)])
559
return QQ((1, gra.automorphism_number())) * gra.to_tautclass()
560
561
result = sum([sum([(sum([A[i - 1] for i in I])**2) * delt(l, g, n, I)
562
for I in Subsets(list(range(1, n + 1)))])
563
for l in range(g + 1)])
564
# note index i-1 since I subset {1,...,n} but array indices subset {0,...,n-1}
565
566
return QQ((-1, 4)) * result
567
568