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
Recursive computation of strata of k-differentials
3
4
This is following [Farkas-Pandharipande; Schmitt]
5
"""
6
from __future__ import absolute_import, print_function
7
from six.moves import range
8
from admcycles.admcycles import (Tautv_to_tautclass, fundclass, tautclass)
9
from admcycles.stable_graph import StableGraph
10
from admcycles.double_ramification_cycle import DR_cycle
11
from admcycles.diffstrata.sig import Signature
12
from admcycles.diffstrata.levelstratum import GeneralisedStratum
13
14
import itertools
15
from copy import deepcopy
16
from collections import Iterable
17
18
from sage.misc.misc import subsets
19
from sage.combinat.integer_vector import IntegerVectors
20
from sage.combinat.partition import Partitions
21
from sage.misc.misc_c import prod
22
from sage.rings.all import ZZ
23
from sage.misc.cachefunc import cached_function
24
from sage.combinat.words.word import Word
25
from sage.functions.other import ceil
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'):
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
INPUT:
239
240
- ``g`` -- integer ; genus of the curves
241
242
- ``k`` -- integer ; power of the canonical line bundle in definition of stratum
243
244
- ``mu`` -- tuple ; tuple of integers of length n giving zero and pole multiplicities
245
of k-differential, required to sum up to k*(2g-2)
246
247
- ``virt`` -- bool (default: `False`); if True, k=1 and all entries of mu nonnegative, this
248
computes the virtual codimension g class supported on the codimension g-1 stratum of
249
holomorphic 1-differentials.
250
251
- ``res_cond`` -- tuple (default: `()`); tuple of residue conditions. Each entry of
252
res_cond can be of one of the following two types:
253
254
- an integer i from 1, ..., n indicating a marking pi with mu[i]<0, such that the
255
differential eta is required to have a pole with vanishing residue at the marking.
256
257
- a tuple (c1, ..., cn) of rational numbers indicating that a condition
258
c1 * Res_{p1}(eta) + ... + cn * Res_{pn}(eta) = 0
259
is imposed. Currently only implemented for ci in {0,1}.
260
261
The function then computes the class of the closure of the locus of smooth curves
262
having such a differential eta. Currently only implemented for k=1.
263
264
- ``xi_power`` -- integer (default: `0`); if positive, returns the pushforward of
265
the corresponding power of the first Chern class xi = c_1(O(-1)) of the tautological
266
bundle on the moduli space of multi-scale differentials from [BCGGM3].
267
Currently only implemented for k=1 and with method = 'diffstrata'.
268
269
- ``method`` -- string (default: `'pull'`); when computing a stratum of 1-differentials
270
with residue conditions, there are two choices here: 'pull' will compute it via boundary
271
pullbacks of higher genus strata, 'diffstrata' will use the package `diffstrata`, which
272
iteratively replaces residue conditions with equivalent divisorial conditions. The two
273
results should be equal.
274
275
NOTE::
276
277
The class is computed using a formula from papers by Farkas-Pandharipande and Schmitt,
278
proven by [Holmes-Schmitt 19] and [Bae-Holmes-Pandharipande-Schmitt-Schwarz 20].
279
The formula for differentials with residue conditions is based on unpublished work
280
relying on [Bainbridge-Chen-Gendron-Grushevsky-Moeller 16].
281
282
WARNING::
283
284
Imposing residue conditions at poles of high order leads to very long computations,
285
since the method works by computing strata of differentials on high-genus moduli
286
spaces.
287
288
TESTS::
289
290
sage: from admcycles import Hyperell, Biell
291
sage: from admcycles.stratarecursion import Strataclass
292
sage: L=Strataclass(2,1,(3,-1)); L.is_zero()
293
True
294
sage: L=Strataclass(3,1,(5,-1)); L.is_zero() # doctest: +SKIP
295
True
296
sage: L=Strataclass(2,1,(2,)); (L-Hyperell(2,1)).is_zero()
297
True
298
299
In g=2, the locus Hbar_2(2,2) decomposes into the codimension 1 set of Hbar_2(1,1) and the
300
codimension 2 set of curves (C,p,q) with p,q Weierstrass points. The latter is equal to the cycle
301
Hyperell(2,2). We can obtain it by subtracting the virtual cycle for the partition (1,1) from the
302
virtual cycle for the partition (2,2)::
303
304
sage: H1 = Strataclass(2, 2, (2, 2), virt = True)
305
sage: H2 = Strataclass(2, 1, (1, 1), virt = True)
306
sage: T = H1 - H2 - Hyperell(2, 2)
307
sage: T.is_zero()
308
True
309
310
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.
311
Equivalently, this is the locus of bielliptic curves with a pair of bielliptic conjugate points::
312
313
sage: (Strataclass(1,1,(2,-2)) - Biell(1,0,1)).is_zero()
314
True
315
316
Some tests of computations involving residue conditions::
317
318
sage: from admcycles import Strataclass
319
sage: OmegaR = Strataclass(1,1,(6,-4,-2),res_cond=(3,))
320
sage: OmegaRalt = Strataclass(1,1,(6,-4,-2),res_cond=(2,)) # long time
321
sage: (OmegaR - OmegaRalt).is_zero() # long time
322
True
323
sage: (OmegaR.forgetful_pushforward([2,3])).fund_evaluate()
324
42
325
sage: a=4; (a+2)**2 + a**2 - 10 # formula from [Castorena-Gendron, Cor. 5.5]
326
42
327
sage: OmegaR2 = Strataclass(1,1,(4,-2,-2),res_cond=(3,))
328
sage: (OmegaR2.forgetful_pushforward([2,3])).fund_evaluate()
329
10
330
sage: OmegaR3 = Strataclass(1,1,(5,-3,-2),res_cond=(2,)) # not tested
331
sage: (OmegaR3.forgetful_pushforward([2,3])).fund_evaluate() # not tested
332
24
333
sage: a=3; (a+2)**2 + a**2 - 10 # formula from [Castorena-Gendron, Cor. 5.5] # not tested
334
24
335
sage: OmegaR5 = Strataclass(2,1,(5,-1,-2),res_cond=(3,)) # not tested
336
sage: OmegaR5.is_zero() # vanishes by residue theorem # not tested
337
True
338
339
We can also check that the two ways of computing residue conditions (via pullbacks and
340
via the package diffstrata) coincide::
341
342
sage: a = Strataclass(1,1,(4,-2,-2), res_cond=(2,))
343
sage: b = Strataclass(1,1,(4,-2,-2), res_cond=(2,), method='diffstrata')
344
sage: (a-b).is_zero()
345
True
346
347
The following computes the locus of genus 1 curves admitting a differential with multiplicity
348
vector (8,-2,-2,-2,-2) at the markings such that the sum of residues at p2 and p3 equals zero::
349
350
sage: c = Strataclass(1,1,(8,-2,-2,-2,-2), res_cond=((0,1,1,0,0),))
351
"""
352
n = len(mu)
353
k = ZZ(k)
354
if sum(mu) != k*(2*g-2):
355
raise ValueError('mu must be a partition of k*(2g-2).')
356
357
358
359
if method == 'diffstrata' or xi_power > 0 or any(isinstance(rc,Iterable) for rc in res_cond):
360
# preprocessing residue conditions
361
fancy_res_cond = []
362
for rc in res_cond:
363
if isinstance(rc,Iterable):
364
if not all(a == 0 or a == 1 for a in rc):
365
raise NotImplementedError('Only residue conditions with coefficients 0,1 implemented.')
366
fancy_res_cond.append([(0,i) for i, a in enumerate(rc) if a == 1])
367
else:
368
fancy_res_cond.append([(0,rc-1)])
369
370
X = GeneralisedStratum(sig_list = [Signature(tuple(mu))], res_cond = fancy_res_cond)
371
return (X.xi**xi_power).to_prodtautclass().pushforward()
372
373
374
if len(res_cond)>0:
375
# res_cond = tuple(res_cond) # make sure it is of type tuple
376
if not k==1:
377
raise NotImplementedError('Residue conditions only implemented for k=1')
378
if virt:
379
raise ValueError('Residue conditions not compatible with virt=True')
380
381
res_cond = sorted(res_cond)
382
poles = [i for (i,mui) in enumerate(mu) if mui<0] # indices of poles
383
384
if len(poles)==1: # residue conditions are automatic
385
return Strataclass(g, k, mu, virt=virt)
386
387
if any(mu[i-1]>=0 for i in res_cond): # try imposing residue cond. at non-pole
388
raise ValueError('Residue conditions can only be imposed at poles')
389
if any(mu[i-1]==-1 for i in res_cond): # try imposing residue cond. at simple pole
390
return tautclass([])
391
392
num_add_marks = len([1 for i in res_cond if mu[i-1]%2]) # additional markings
393
maxleg = n + num_add_marks+2
394
395
genera = [g] + [ceil(-mu[i-1]/2) for i in res_cond]
396
munew = []
397
outermus = []
398
markcounter = 1
399
legs = [[]]
400
edges = [(maxleg+2*j, maxleg+2*j+1) for j in range(len(res_cond))]
401
402
for i in range(1,n+1):
403
if i not in res_cond:
404
legs[0].append(markcounter)
405
munew.append(mu[i-1])
406
markcounter+=1
407
else:
408
legs[0].append(maxleg)
409
if mu[i-1]%2 == 0:
410
legs.append([maxleg+1])
411
outermus.append([-mu[i-1]-2])
412
else:
413
legs.append([maxleg+1,markcounter])
414
outermus.append([-mu[i-1]-2,1])
415
munew.append(1)
416
markcounter+=1
417
maxleg+=2
418
gamma = StableGraph(genera, legs, edges)
419
420
outerg = sum(genera)
421
outerclass = Strataclass(outerg, k, munew, virt=False)
422
423
pullb = gamma.boundary_pullback(outerclass)
424
return pullb.factor_reconstruct(0,[Strataclass(genera[j+1],1,outermus[j]) for j in range(len(res_cond))])
425
426
if (g == 0) or all(m == 0 for m in mu):
427
return fundclass(g, n)
428
429
# Hbar has codimension g
430
meromorphic = any(not k.divides(m) or m < 0 for m in mu)
431
432
if k > 1 and not meromorphic and not virt:
433
# all entries divisible by k and nonnegative AND user wants a codim g-1 class
434
# return the corresponding stratum of abelian differentials
435
return Strataclass(g, 1, tuple(m // k for m in mu))
436
437
ordering_permutation = Word(mu).standard_permutation().inverse()
438
ordering_dict = {i+1: j for i, j in enumerate(ordering_permutation)}
439
# this is the dictionary such that renaming the answer with standard-ordering according to ordering_dict gives correct answer
440
sortmu = tuple(sorted(mu))
441
442
try:
443
v = StrataDB.cached(g, k, sortmu, virt)
444
if meromorphic or virt: # return codim g class
445
ans_preord = Tautv_to_tautclass(v, g, n, g)
446
else:
447
ans_preord = Tautv_to_tautclass(v, g, n, g-1)
448
ans_preord.rename_legs(ordering_dict, rename=True)
449
return ans_preord
450
except KeyError:
451
pass
452
453
# at this point, we actually have to compute
454
# we will compute the answer with standard ordering, store its vector in StrataDB and then return the renamed answer
455
if meromorphic or virt:
456
# return codimension g class
457
bdry_list = twistenum(g, k, sortmu)
458
indfind1 = tuple((i for i, l in enumerate(bdry_list) if l[0][0] == g))
459
assert len(indfind1) == 1, (g, k, mu, tuple(indfind1))
460
bdry_list.pop(indfind1[0]) # remove the trivial graph
461
462
# right hand side of recursion
463
result = DR_cycle(g, tuple((m+k for m in sortmu)))
464
result -= Strataboundarysum(g, k, sortmu, bdry_list)
465
466
v = result.toTautvect(g, n, g)
467
StrataDB.set_cache(v, *(g, k, sortmu, virt))
468
result = Tautv_to_tautclass(v, g, n, g) # gives simplified version of result
469
result.rename_legs(ordering_dict, rename=True)
470
return result
471
else:
472
# return codimension g-1 class
473
# by previous check can assume that k=1
474
assert k == 1, (g, k, mu)
475
476
sortmuprime = list(sortmu) + [-1]
477
sortmuprime[n-1] += 1
478
sortmuprime = tuple(sortmuprime)
479
480
bdry_list = twistenum(g, k, sortmuprime)
481
482
indfind1 = tuple((i for i, l in enumerate(bdry_list) if l[0][0] == g))
483
assert len(indfind1) == 1, (g, k, mu, tuple(indfind1))
484
bdry_list.pop(indfind1[0]) # remove the trivial graph
485
486
indfind2 = tuple((i for i, l in enumerate(bdry_list) if len(
487
l) == 2 and l[0][0] == 0 and l[0][1] == (n, n+1) and l[1][0] == g))
488
assert len(indfind2) == 1, (g, k, mu)
489
bdry_list.pop(indfind2[0])
490
491
preresult = DR_cycle(g, tuple(m + k for m in sortmuprime))
492
preresult -= Strataboundarysum(g, k, sortmuprime, bdry_list)
493
result = preresult.forgetful_pushforward([n+1])
494
result *= (1/sortmuprime[n-1])
495
496
v = result.toTautvect(g, n, g-1)
497
StrataDB.set_cache(v, *(g, k, sortmu, virt))
498
result = Tautv_to_tautclass(v, g, n, g-1) # gives simplified version of result
499
result.rename_legs(ordering_dict, rename=True)
500
return result
501
502
503
def Strataboundarysum(g, k, mu, bdry_list, termsout=False):
504
r""" Returns sum of boundary terms in Conjecture A for entries of bdry_list. These entries have the format from the output of twistenum, but might be a subset of these.
505
"""
506
resultterms = []
507
n = len(mu)
508
for vdata in bdry_list:
509
genera = [b[0] for b in vdata]
510
legs = [list(b[1]) for b in vdata] # just markings at the moment
511
edges = []
512
maxleg = n+1
513
# stores twist at legs and half-edges
514
twist = {i: mu[i-1] for i in range(1, n+1)}
515
for v, (_, _, twistvect) in list(enumerate(vdata))[1:]:
516
for I in twistvect:
517
legs[0].append(maxleg)
518
legs[v].append(maxleg+1)
519
edges.append((maxleg, maxleg+1))
520
twist[maxleg] = -I-k
521
twist[maxleg+1] = I-k
522
maxleg += 2
523
bdry_graph = StableGraph(genera, legs, edges)
524
525
vertterms = [Strataclass(genera[0], k, [twist[l] for l in legs[0]])]
526
vertterms += [Strataclass(genera[v], 1, [twist[l] // k for l in legs[v]])
527
for v in range(1, len(genera))]
528
529
bdry_term = bdry_graph.boundary_pushforward(vertterms)
530
coeff = prod([I for (_, _, twistvect) in vdata[1:] for I in twistvect])
531
coeff /= k**(len(genera)-1) * automorphism_number_fixing_twist(bdry_graph, twist)
532
resultterms += [coeff * bdry_term]
533
if termsout:
534
return resultterms
535
else:
536
result = sum(resultterms)
537
result.simplify()
538
return result
539
540
541
@cached_function
542
def StrataDB(g, k, mu):
543
raise NotImplementedError('StrataDB is just an internal database '
544
'for strata classes, use Strataclass instead')
545
546
547
def automorphism_number_fixing_twist(gr, I):
548
r"""
549
Return number of automorphisms of gr leaving the twist I on gr invariant.
550
551
EXAMPLES::
552
553
sage: from admcycles import StableGraph
554
sage: from admcycles.stratarecursion import automorphism_number_fixing_twist
555
sage: gr = StableGraph([0,0],[[1,2,3],[4,5,6]],[(1,2),(3,4),(5,6)])
556
sage: twist = {i:0 for i in range(7)}
557
sage: automorphism_number_fixing_twist(gr,twist)
558
8
559
sage: twist[1] = 1
560
sage: automorphism_number_fixing_twist(gr,twist)
561
2
562
sage: twist[2] = 1
563
sage: automorphism_number_fixing_twist(gr,twist)
564
4
565
"""
566
halfedges = gr.halfedges()
567
G = gr.leg_automorphism_group()
568
return len([1 for g in G if all(I[g(h)] == I[h] for h in halfedges)])
569
570