Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
181 views
unlisted
ubuntu2004
1
# -*- coding: utf-8 -*-
2
r""" Recursive computation of strata of k-differentials
3
4
This is following the papers [FaPa18]_ by Farkas-Pandharipande and [Sch18]_ by Schmitt.
5
"""
6
7
from collections.abc import Iterable
8
from copy import deepcopy
9
import itertools
10
11
from sage.misc.misc import subsets
12
from sage.combinat.integer_vector import IntegerVectors
13
from sage.combinat.partition import Partitions
14
from sage.misc.misc_c import prod
15
from sage.rings.all import ZZ
16
from sage.misc.cachefunc import cached_function
17
from sage.combinat.words.word import Word
18
from sage.functions.other import ceil
19
20
from .stable_graph import StableGraph
21
from admcycles.double_ramification_cycle import DR_cycle
22
import admcycles.diffstrata.sig
23
import admcycles.diffstrata.generalisedstratum
24
import admcycles.diffstrata.spinstratum
25
from .tautological_ring import TautologicalRing
26
27
# Given a genus g, an order k and a partition mu of k(2g-2), twistenum
28
# returns a list of graphs with twists
29
30
# Elements of this list are of the following form
31
# [
32
# [g_0, [markings of v_0] ],
33
# [g_1, [markings of v_1], #edges of v_1, [twists of edges to v_1] ],
34
# ...,
35
# [g_r, [markings of v_r], #edges of v_r, [twists of edges to v_r] ]
36
# ]
37
# NOTE: Twists are counted in multiples of k, so a twist of 3 actually means 3k
38
39
# ordering of graphs (always descending)
40
# genus g0 of central vertex > outlying vertex data
41
# ordering of outlying vertices
42
# total genus > genus > total edge twist
43
44
45
def classes(l):
46
"""
47
INPUT:
48
49
- l -- a list
50
51
EXAMPLES::
52
53
sage: from admcycles.stratarecursion import classes
54
sage: classes([])
55
[]
56
sage: classes([4,4,3,3,3,1,1])
57
[[0, 1], [2, 3, 4], [5, 6]]
58
"""
59
if not l:
60
return []
61
indices = []
62
current = [0]
63
currentitem = l[0]
64
for i in range(1, len(l)):
65
if l[i] == currentitem:
66
current += [i]
67
else:
68
indices += [current]
69
current = [i]
70
currentitem = l[i]
71
indices += [current]
72
return indices
73
74
75
def SetPart(S, s):
76
"""
77
Return a list of partitions of the set S into the disjoint union of s sets.
78
79
A partition is a list of sets.
80
81
EXAMPLES::
82
83
sage: from admcycles.stratarecursion import SetPart
84
sage: S = set(['b','o','f'])
85
sage: SetPart(S,2)
86
[[set(), {'b', 'f', 'o'}],
87
...
88
[{'b', 'f', 'o'}, set()]]
89
sage: SetPart(S,1)
90
[[{'b', 'f', 'o'}]]
91
sage: SetPart(S,-1)
92
[]
93
sage: SetPart(S,0)
94
[]
95
"""
96
if s < 0:
97
return []
98
if s == 0:
99
if S:
100
return []
101
else:
102
return [[]]
103
if s == 1:
104
return [[S]]
105
106
resu = []
107
for T in subsets(S):
108
for r in SetPart(S - set(T), s - 1):
109
resu.append([set(T)] + r)
110
return resu
111
112
113
def twistenum(g, k, mu):
114
k = ZZ(k)
115
lis = [] # list of all graphs, returned in the end
116
117
# collect all negative or non-divisible-by-k markings in A, all others in B
118
A = []
119
B = []
120
for i in range(len(mu)):
121
if mu[i] < 0 or (mu[i] % k != 0):
122
A.append(i)
123
else:
124
B.append(i)
125
Atot = sum([mu[i] for i in A])
126
127
for g0 in range(g + 1): # g0 = genus of center vertex
128
# iterate over possible partitions of the rest of the genera to vertices
129
for totgenlist in Partitions(g - g0):
130
# list containing lists of indices with equal total genus
131
cl = classes(totgenlist)
132
133
# we can have 1 up to total_genus many edges, distribute the len(c) many possibilities to those
134
# each of the len(c) vertices can have 1, ..., total_genus many edges; vector (1,0,2) means
135
# 1 vertex has 1 edge, no vertex has 2 edges, 2 vertices have 3 edges
136
par = [IntegerVectors(len(c), totgenlist[c[0]]) for c in cl]
137
138
for p in itertools.product(*par):
139
# gra contains the information that will eventually be added to lis
140
gra = [[g0, A]]
141
numed = 0 # total number of edges
142
for j in range(len(cl)):
143
# p[j] is now an integer vector, encoding edge numbers of vertices c in cl[j] with totgenlist[c[0]] total genus
144
for z in range(len(p[j])):
145
gra += [[totgenlist[cl[j][0]] - z, [], z + 1, []]
146
for _ in range(p[j][z])]
147
numed += p[j][z] * (z + 1)
148
149
# iterate over additional markings sent to v_0
150
for markings0 in subsets(B):
151
# vertex v0 unstable
152
if g0 == 0 and numed + len(A) + len(markings0) <= 2:
153
continue
154
Btot = sum([mu[i] for i in markings0])
155
if not k.divides(Atot + Btot):
156
continue
157
158
Brest = [b for b in B if b not in markings0]
159
160
Itotal = (Atot + Btot) // k - 2 * numed - (2 * g0 - 2)
161
162
# Itotal is the total number of ADDITIONAL twists k that can be distributed to the various edges
163
# (all have k as default anyway, so each edge has at least a pole of order 2*k on the central vertex)
164
165
# TODO: break if Itotal<0
166
167
vertcl = classes(gra)
168
169
for coarsesttwistdist in IntegerVectors(Itotal, len(vertcl) - 1):
170
# distribute the additional twists on the classes of outlying vertices
171
twist = []
172
# twist will be a list for every class of outlying vertex recording a list of all possible twist->edge distr.
173
# element of the list for the class {3,4,5} have the form
174
# [[a,b],[c],[d,e,f]] if v_3 has two edges, v_4 one and v_5 three
175
176
for i in range(len(vertcl) - 1):
177
# for each of the classes collect the possibilities to distribute inside this class
178
vertcltwistlist = []
179
# will be added as an element to twist
180
for coarsetwistdist in Partitions(coarsesttwistdist[i] + len(vertcl[i + 1]), length=len(vertcl[i + 1])):
181
# coarsetwistdist describes how twists are distributed to the vertices in vertcl[i+1]
182
# artificially added ones to be able to use Partitions
183
184
inditwist = [Partitions(
185
coarsetwistdist[v] + gra[vertcl[i + 1][v]][2] - 1, length=gra[vertcl[i + 1][v]][2]) for v in range(len(vertcl[i + 1]))]
186
# list for every vertex in the class giving all possible twists of the edges of this vertex
187
188
vertcltwistlist += itertools.product(
189
*inditwist)
190
191
twist.append(vertcltwistlist)
192
for inde in itertools.product(*twist):
193
# inde is now of the form [ [[a,b], [c], [d,e,f]] , [[g], [h,i]], ... ]
194
grap = deepcopy(gra)
195
# this will be updated now with the edge twists determined above and the markings sent to v_0
196
grap[0][1] += markings0
197
count = 1
198
for i in inde:
199
for j in i:
200
grap[count][3] = j
201
count += 1
202
203
twicla = classes(grap)
204
# print((twicla,len(twicla)-1,Brest))
205
206
for pa in SetPart(set(Brest), len(twicla) - 1):
207
mpar = [SetPart(pa[c], len(twicla[c + 1]))
208
for c in range(len(twicla) - 1)]
209
if not mpar and Brest:
210
continue
211
for part in itertools.product(*mpar):
212
# part is now of the form [ [ set([0,2]), set([1]) ] , [ set([3]) ] , ... ]
213
graph = deepcopy(grap)
214
count = 1
215
adm = True # check if graph satisfies twist conditions at all vertices
216
for i in part:
217
for j in i:
218
# we are now at the vertex v_count and need to check if the twist-condition is satisfied
219
graph[count][1] = list(j)
220
if (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]):
221
adm = False
222
count += 1
223
if adm:
224
sgraph = (
225
(graph[0][0], tuple(sorted(m + 1 for m in graph[0][1]))),)
226
sgraph += tuple(sorted(((gv, tuple(sorted(m + 1 for m in marki)), tuple(
227
sorted(k * t for t in etwist))) for gv, marki, enu, etwist in graph[1:])))
228
lis.append(sgraph)
229
230
return list(set(lis))
231
232
233
def Strataclass(g, k, mu, virt=False, res_cond=(), xi_power=0, method='pull', spin=False, spin_conj=False):
234
r"""
235
Returns the fundamental class of the closure of the stratum of k-differentials in genus g in Mbar_{g,n}
236
with vanishing and pole orders mu.
237
238
The class is computed using a formula from papers by Farkas-Pandharipande and Schmitt,
239
proven by [HoSc]_ and [BHPSS]_.
240
The formula for differentials with residue conditions is based on unpublished work
241
relying on [BaChGeGrMo]_.
242
243
If the mu is of spin type, we can compute the spin stratum class H^+ - H^-. In [CSS21]_, Costantini,
244
Sauvaget and Schmitt made a similar conjecture for the Pixton formula.
245
246
INPUT:
247
248
- ``g`` -- integer ; genus of the curves
249
250
- ``k`` -- integer ; power of the canonical line bundle in definition of stratum
251
252
- ``mu`` -- tuple ; tuple of integers of length n giving zero and pole multiplicities
253
of k-differential, required to sum up to k*(2g-2)
254
255
- ``virt`` -- bool (default: `False`); if True, k=1 and all entries of mu nonnegative, this
256
computes the virtual codimension g class supported on the codimension g-1 stratum of
257
holomorphic 1-differentials.
258
259
- ``res_cond`` -- tuple (default: `()`); tuple of residue conditions. Each entry of
260
res_cond can be of one of the following two types:
261
262
- an integer i from 1, ..., n indicating a marking pi with mu[i]<0, such that the
263
differential eta is required to have a pole with vanishing residue at the marking.
264
265
- a tuple (c1, ..., cn) of rational numbers indicating that a condition
266
c1 * Res_{p1}(eta) + ... + cn * Res_{pn}(eta) = 0
267
is imposed. Currently only implemented for ci in {0,1}.
268
269
The function then computes the class of the closure of the locus of smooth curves
270
having such a differential eta. Currently only implemented for k=1.
271
272
- ``xi_power`` -- integer (default: `0`); if positive, returns the pushforward of
273
the corresponding power of the first Chern class xi = c_1(O(-1)) of the tautological
274
bundle on the moduli space of multi-scale differentials from [BCGGM3].
275
Currently only implemented for k=1 and with method = 'diffstrata'.
276
277
- ``method`` -- string (default: `'pull'`); when computing a stratum of 1-differentials
278
with residue conditions, there are two choices here: 'pull' will compute it via boundary
279
pullbacks of higher genus strata, 'diffstrata' will use the package `diffstrata`, which
280
iteratively replaces residue conditions with equivalent divisorial conditions. The two
281
results should be equal.
282
283
- ``spin`` -- bool (default: `False`); if true, we will compute the spin stratum class
284
285
- ``spin_conj`` -- bool (default: `False`); if we assume the conjecture in [CSS21]_ to be true,
286
we will compute the spin stratum class by first compute the holomorphic strata of the same genus,
287
then use the conjecture to recursively compute the input stratum class
288
289
WARNING::
290
291
Imposing residue conditions at poles of high order leads to very long computations,
292
since the method works by computing strata of differentials on high-genus moduli
293
spaces.
294
295
TESTS::
296
297
sage: from admcycles import Hyperell, Biell
298
sage: from admcycles.stratarecursion import Strataclass
299
sage: L=Strataclass(2,1,(3,-1)); L.is_zero()
300
True
301
sage: L=Strataclass(3,1,(5,-1)); L.is_zero() # doctest: +SKIP
302
True
303
sage: L=Strataclass(2,1,(2,)); (L-Hyperell(2,1)).is_zero()
304
True
305
306
In g=2, the locus Hbar_2(2,2) decomposes into the codimension 1 set of Hbar_2(1,1) and the
307
codimension 2 set of curves (C,p,q) with p,q Weierstrass points. The latter is equal to the cycle
308
Hyperell(2,2). We can obtain it by subtracting the virtual cycle for the partition (1,1) from the
309
virtual cycle for the partition (2,2)::
310
311
sage: H1 = Strataclass(2, 2, (2, 2), virt = True)
312
sage: H2 = Strataclass(2, 1, (1, 1), virt = True)
313
sage: T = H1 - H2 - Hyperell(2, 2)
314
sage: T.is_zero()
315
True
316
317
In g=1, the locus Hbar_1(2,-2) is the locus of curves (E,p,q) with p-q being 2-torsion in E.
318
Equivalently, this is the locus of bielliptic curves with a pair of bielliptic conjugate points::
319
320
sage: (Strataclass(1,1,(2,-2)) - Biell(1,0,1)).is_zero()
321
True
322
323
Some tests of computations involving residue conditions::
324
325
sage: from admcycles import Strataclass
326
sage: OmegaR = Strataclass(1,1,(6,-4,-2),res_cond=(3,))
327
sage: OmegaRalt = Strataclass(1,1,(6,-4,-2),res_cond=(2,)) # long time
328
sage: (OmegaR - OmegaRalt).is_zero() # long time
329
True
330
sage: (OmegaR.forgetful_pushforward([2,3])).fund_evaluate()
331
42
332
sage: a=4; (a+2)**2 + a**2 - 10 # formula from [Castorena-Gendron, Cor. 5.5]
333
42
334
sage: OmegaR2 = Strataclass(1,1,(4,-2,-2),res_cond=(3,))
335
sage: (OmegaR2.forgetful_pushforward([2,3])).fund_evaluate()
336
10
337
sage: OmegaR3 = Strataclass(1,1,(5,-3,-2),res_cond=(2,)) # not tested
338
sage: (OmegaR3.forgetful_pushforward([2,3])).fund_evaluate() # not tested
339
24
340
sage: a=3; (a+2)**2 + a**2 - 10 # formula from [Castorena-Gendron, Cor. 5.5] # not tested
341
24
342
sage: OmegaR5 = Strataclass(2,1,(5,-1,-2),res_cond=(3,)) # not tested
343
sage: OmegaR5.is_zero() # vanishes by residue theorem # not tested
344
True
345
346
We can also check that the two ways of computing residue conditions (via pullbacks and
347
via the package diffstrata) coincide::
348
349
sage: a = Strataclass(1,1,(4,-2,-2), res_cond=(2,))
350
sage: b = Strataclass(1,1,(4,-2,-2), res_cond=(2,), method='diffstrata')
351
sage: (a-b).is_zero()
352
True
353
354
The following computes the locus of genus 1 curves admitting a differential with multiplicity
355
vector (8,-2,-2,-2,-2) at the markings such that the sum of residues at p2 and p3 equals zero::
356
357
sage: c = Strataclass(1,1,(8,-2,-2,-2,-2), res_cond=((0,1,1,0,0),))
358
359
Using the parameter xi_power, we can observe an interesting relationship between strata with
360
xi-insertions and higher terms in Pixton's formula of the double ramification cycle::
361
362
sage: from admcycles import DR_cycle
363
sage: g=0; mu = [11,4,-8,-2,-5,-2]; A = [a+1 for a in mu]
364
sage: n = len(mu); adddeg = 1
365
sage: D = DR_cycle(g,A,rpoly=True,d=g+adddeg,chiodo_coeff=True)
366
sage: v = vector(QQ,[a[adddeg] for a in D.basis_vector()])
367
sage: S = Strataclass(g,1,mu,xi_power=adddeg)
368
sage: print(v); print(-S.basis_vector())
369
(0, 0, -4, 7, 2, 4, 2, 0, -1, 0, 0, 0, 0, 2, 0, 2)
370
(0, 0, -4, 7, 2, 4, 2, 0, -1, 0, 0, 0, 0, 2, 0, 2)
371
372
We can use the function to compute spin stratum class, with or without the assumption of the
373
conjecture of spin Pixton formula::
374
375
sage: cl1=Strataclass(2,1,(8,-4,-2),spin=True)
376
sage: cl1.basis_vector()
377
(-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)
378
sage: cl2=Strataclass(2,1,(8,-4,-2),spin=True,spin_conj=True)
379
sage: cl1==cl2
380
True
381
382
If we assume the conjecture above, we can also compute spin strata of meromorphic k-differentials
383
for k > 1. Below we check that they restrict under a boundary gluing map as expected::
384
385
sage: from admcycles import StableGraph
386
sage: from admcycles.admcycles import prodtautclass
387
sage: H = Strataclass(2,3,[10,-4],spin=True,spin_conj=True)
388
sage: gamma = StableGraph([1,1],[[1,3],[2,4]],[(3, 4)])
389
sage: pb = gamma.boundary_pullback(H)
390
sage: H1 = Strataclass(1,3,[10,-10],spin=True,spin_conj=True)
391
sage: H2 = Strataclass(1,3,[-4,4],spin=True,spin_conj=True)
392
sage: prot = prodtautclass(gamma, protaut=[H1, H2])
393
sage: pb.totensorTautbasis(2) == prot.totensorTautbasis(2)
394
True
395
"""
396
n = len(mu)
397
R = TautologicalRing(g, n)
398
k = ZZ(k)
399
if sum(mu) != k * (2 * g - 2):
400
raise ValueError('mu must be a partition of k*(2g-2).')
401
402
# Hbar has codimension g
403
meromorphic = any(not k.divides(m) or m < 0 for m in mu)
404
405
if spin:
406
method = 'diffstrata'
407
if any(m % 2 != 0 for m in mu):
408
raise ValueError('The signature is not of spin type')
409
if k > 1:
410
if k % 2 != 1:
411
raise ValueError('The integer k has to be odd if we want to have spin structure.')
412
if not spin_conj or not meromorphic or res_cond != () or xi_power != 0:
413
raise NotImplementedError
414
method = 'pull' # for k>1, we do not use diffstrata
415
elif res_cond == () and meromorphic and spin_conj:
416
method = 'pull' # in this cases, we do not use diffstrata
417
418
if (g == 0) and not (res_cond or xi_power):
419
return R.fundamental_class()
420
421
if all(m == 0 for m in mu) and not (res_cond or xi_power):
422
return -R.fundamental_class() if spin else R.fundamental_class()
423
424
if method == 'diffstrata' or xi_power > 0 or any(isinstance(rc, Iterable) for rc in res_cond):
425
# preprocessing residue conditions
426
fancy_res_cond = []
427
for rc in res_cond:
428
if isinstance(rc, Iterable):
429
if not all(a == 0 or a == 1 for a in rc):
430
raise NotImplementedError('Only residue conditions with coefficients 0,1 implemented.')
431
fancy_res_cond.append([(0, i) for i, a in enumerate(rc) if a == 1])
432
else:
433
fancy_res_cond.append([(0, rc - 1)])
434
435
X = admcycles.diffstrata.generalisedstratum.GeneralisedStratum(
436
sig_list=[admcycles.diffstrata.sig.Signature(tuple(mu))], res_cond=fancy_res_cond)
437
438
elgtclass = X.xi ** xi_power # ELGTautClass of the xi class with power
439
440
if spin: # if spin, we turn the ELGTautClass into spin version
441
elgtspin = admcycles.diffstrata.spinstratum.ELGT_addspin(elgtclass)
442
return elgtspin.to_prodtautclass_spin().pushforward()
443
444
return elgtclass.to_prodtautclass().pushforward()
445
446
if len(res_cond) > 0:
447
# res_cond = tuple(res_cond) # make sure it is of type tuple
448
if not k == 1:
449
raise NotImplementedError('Residue conditions only implemented for k=1')
450
if virt:
451
raise ValueError('Residue conditions not compatible with virt=True')
452
453
res_cond = sorted(res_cond)
454
poles = [i for (i, mui) in enumerate(mu) if mui < 0] # indices of poles
455
456
if len(poles) == 1: # residue conditions are automatic
457
return Strataclass(g, k, mu, virt=virt)
458
459
if any(mu[i - 1] >= 0 for i in res_cond): # try imposing residue cond. at non-pole
460
raise ValueError('Residue conditions can only be imposed at poles')
461
if any(mu[i - 1] == -1 for i in res_cond): # try imposing residue cond. at simple pole
462
return R.zero()
463
464
num_add_marks = len([1 for i in res_cond if mu[i - 1] % 2]) # additional markings
465
maxleg = n + num_add_marks + 2
466
467
genera = [g] + [ceil(-mu[i - 1] / 2) for i in res_cond]
468
munew = []
469
outermus = []
470
markcounter = 1
471
legs = [[]]
472
edges = [(maxleg + 2 * j, maxleg + 2 * j + 1) for j in range(len(res_cond))]
473
474
for i in range(1, n + 1):
475
if i not in res_cond:
476
legs[0].append(markcounter)
477
munew.append(mu[i - 1])
478
markcounter += 1
479
else:
480
legs[0].append(maxleg)
481
if mu[i - 1] % 2 == 0:
482
legs.append([maxleg + 1])
483
outermus.append([-mu[i - 1] - 2])
484
else:
485
legs.append([maxleg + 1, markcounter])
486
outermus.append([-mu[i - 1] - 2, 1])
487
munew.append(1)
488
markcounter += 1
489
maxleg += 2
490
gamma = StableGraph(genera, legs, edges)
491
492
outerg = sum(genera)
493
outerclass = Strataclass(outerg, k, munew, virt=False)
494
495
pullb = gamma.boundary_pullback(outerclass)
496
return pullb.factor_reconstruct(0, [Strataclass(genera[j + 1], 1, outermus[j]) for j in range(len(res_cond))])
497
498
if k > 1 and not meromorphic and not virt:
499
# all entries divisible by k and nonnegative AND user wants a codim g-1 class
500
# return the corresponding stratum of abelian differentials
501
return Strataclass(g, 1, tuple(m // k for m in mu))
502
503
ordering_permutation = Word(mu).standard_permutation().inverse()
504
ordering_dict = {i + 1: j for i, j in enumerate(ordering_permutation)}
505
# this is the dictionary such that renaming the answer with standard-ordering according to ordering_dict gives correct answer
506
sortmu = tuple(sorted(mu))
507
508
try:
509
v = StrataDB.cached(g, k, sortmu, virt, spin)
510
if meromorphic or virt: # return codim g class
511
ans_preord = R.from_vector(v, g)
512
else:
513
ans_preord = R.from_vector(v, g - 1)
514
ans_preord.rename_legs(ordering_dict)
515
return ans_preord
516
except KeyError:
517
pass
518
519
# at this point, we actually have to compute
520
# we will compute the answer with standard ordering, store its vector in StrataDB and then return the renamed answer
521
if meromorphic or virt:
522
# return codimension g class
523
bdry_list = twistenum(g, k, sortmu)
524
indfind1 = tuple((i for i, l in enumerate(bdry_list) if l[0][0] == g))
525
assert len(indfind1) == 1, (g, k, mu, tuple(indfind1))
526
bdry_list.pop(indfind1[0]) # remove the trivial graph
527
528
# right hand side of recursion
529
result = DR_cycle(g, tuple((m + k for m in sortmu)), spin=spin)
530
result -= Strataboundarysum(g, k, sortmu, bdry_list=bdry_list, spin=spin, spin_conj=spin_conj)
531
532
v = result.vector(g)
533
StrataDB.set_cache(v, *(g, k, sortmu, virt, spin))
534
result = R.from_vector(v, g) # gives simplified version of result
535
result.rename_legs(ordering_dict)
536
return result
537
else:
538
# return codimension g-1 class
539
# by previous check can assume that k=1
540
assert k == 1, (g, k, mu)
541
542
sortmuprime = list(sortmu) + [-1]
543
sortmuprime[n - 1] += 1
544
sortmuprime = tuple(sortmuprime)
545
546
bdry_list = twistenum(g, k, sortmuprime)
547
548
indfind1 = tuple((i for i, l in enumerate(bdry_list) if l[0][0] == g))
549
assert len(indfind1) == 1, (g, k, mu, tuple(indfind1))
550
bdry_list.pop(indfind1[0]) # remove the trivial graph
551
552
indfind2 = tuple((i for i, l in enumerate(bdry_list) if len(
553
l) == 2 and l[0][0] == 0 and l[0][1] == (n, n + 1) and l[1][0] == g))
554
assert len(indfind2) == 1, (g, k, mu)
555
bdry_list.pop(indfind2[0])
556
557
preresult = DR_cycle(g, tuple(m + k for m in sortmuprime))
558
preresult -= Strataboundarysum(g, k, sortmuprime, bdry_list=bdry_list)
559
result = preresult.forgetful_pushforward([n + 1])
560
result *= (1 / sortmuprime[n - 1])
561
562
v = result.vector(g - 1)
563
StrataDB.set_cache(v, *(g, k, sortmu, virt, spin))
564
result = R.from_vector(v, g - 1) # gives simplified version of result
565
result.rename_legs(ordering_dict)
566
return result
567
568
569
def Strataboundarysum(g, k, mu, bdry_list=None, termsout=False, spin=False, spin_conj=False):
570
r"""
571
Returns sum of boundary terms in Conjecture A for entries of bdry_list.
572
These entries have the format from the output of twistenum, but might be a subset of these.
573
574
INPUT:
575
576
- g (integer): genus
577
578
- k (integer): power of canonical bundle
579
580
- bdry_list (list, default=None): a sublist of all the twisted stable graphs; if not
581
specified, it will just use the whole list
582
583
- termsout (bool, default=False): if true, then the output will not be simplified
584
585
- spin (bool, default=False): if true, then compute the spin strata boundary sum
586
587
- spin_conj (bool, default=False): if true, then we will assume the spin Conjecture A
588
to be true and the summand will be computed by recursion on this formula
589
590
EXAMPLES::
591
592
sage: from admcycles.stratarecursion import Strataboundarysum
593
sage: from admcycles.double_ramification_cycle import DR_cycle
594
sage: Strataboundarysum(2,1,(6,-4),spin=True).basis_vector()
595
(-139, 46, -109, -99, 219, 194, 174, 136, -318, 91/2, -45/2, -15, -46, -25/2)
596
597
"""
598
if spin:
599
if any(m % 2 != 0 for m in mu):
600
raise ValueError('This signature is not of spin type')
601
if all(m >= 0 for m in mu) and all(k.divides(m) for m in mu):
602
raise ValueError('The boundary sum with spin should not be called by such signature')
603
if k > 1:
604
if k % 2 != 1:
605
raise ValueError('The integer k has to be odd if we want to have spin structure.')
606
if not spin_conj:
607
raise NotImplementedError
608
609
if bdry_list is None:
610
bdry_list = twistenum(g, 1, mu)
611
612
resultterms = []
613
n = len(mu)
614
for vdata in bdry_list:
615
genera = [b[0] for b in vdata]
616
legs = [list(b[1]) for b in vdata] # just markings at the moment
617
edges = []
618
maxleg = n + 1
619
# stores twist at legs and half-edges
620
twist = {i: mu[i - 1] for i in range(1, n + 1)}
621
for v, (_, _, twistvect) in list(enumerate(vdata))[1:]:
622
for I in twistvect:
623
legs[0].append(maxleg)
624
legs[v].append(maxleg + 1)
625
edges.append((maxleg, maxleg + 1))
626
twist[maxleg] = -I - k
627
twist[maxleg + 1] = I - k
628
maxleg += 2
629
bdry_graph = StableGraph(genera, legs, edges)
630
coeff = prod([I for (_, _, twistvect) in vdata[1:] for I in twistvect])
631
coeff /= k**(len(genera) - 1) * automorphism_number_fixing_twist(bdry_graph, twist)
632
633
if spin and any(y % 2 != 0 for _, y in twist.items()):
634
continue
635
else:
636
vertterms = [Strataclass(genera[0], k, [twist[l] for l in legs[0]], spin=spin, spin_conj=spin_conj)]
637
vertterms += [Strataclass(genera[v], 1, [twist[l] // k for l in legs[v]], spin=spin, spin_conj=spin_conj)
638
for v in range(1, len(genera))]
639
bdry_term = bdry_graph.boundary_pushforward(vertterms)
640
resultterms += [coeff * bdry_term]
641
642
if termsout:
643
return resultterms
644
else:
645
result = sum(resultterms)
646
result.simplify()
647
return result
648
649
650
@cached_function
651
def StrataDB(g, k, mu):
652
raise NotImplementedError('StrataDB is just an internal database '
653
'for strata classes, use Strataclass instead')
654
655
656
def automorphism_number_fixing_twist(gr, I):
657
r"""
658
Return number of automorphisms of gr leaving the twist I on gr invariant.
659
660
EXAMPLES::
661
662
sage: from admcycles import StableGraph
663
sage: from admcycles.stratarecursion import automorphism_number_fixing_twist
664
sage: gr = StableGraph([0,0],[[1,2,3],[4,5,6]],[(1,2),(3,4),(5,6)])
665
sage: twist = {i:0 for i in range(7)}
666
sage: automorphism_number_fixing_twist(gr,twist)
667
8
668
sage: twist[1] = 1
669
sage: automorphism_number_fixing_twist(gr,twist)
670
2
671
sage: twist[2] = 1
672
sage: automorphism_number_fixing_twist(gr,twist)
673
4
674
"""
675
halfedges = gr.halfedges()
676
G = gr.leg_automorphism_group()
677
return len([1 for g in G if all(I[g(h)] == I[h] for h in halfedges)])
678
679