Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
180 views
unlisted
ubuntu2004
1
r"""
2
Code for computations involving Witten's r-spin class.
3
Authors: Felix Janda (main author), Aaron Pixton (code improvements), Johannes Schmitt (integration into admcycles)
4
"""
5
6
7
from collections.abc import Iterable
8
import itertools
9
import numbers
10
11
from admcycles.admcycles import tautclass
12
from admcycles.DR import X, MODULI_ST, MODULI_RT, MODULI_CT, MODULI_SM, single_stratum, autom_count, interpolate, num_strata, convert_vector_to_monomial_basis
13
14
from admcycles import TautologicalRing
15
16
# from sage.combinat.subset import Subsets
17
from sage.arith.all import factorial, lcm
18
from sage.functions.other import ceil
19
from sage.rings.all import PolynomialRing, QQ, ZZ
20
from sage.modules.free_module_element import vector
21
# from sage.rings.polynomial.polynomial_ring import polygen
22
from sage.rings.polynomial.multi_polynomial_element import MPolynomial
23
from sage.rings.integer import Integer
24
from sage.calculus.var import var
25
from sage.misc.functional import symbolic_sum
26
from sage.symbolic.ring import SR
27
from copy import copy
28
from sage.misc.cachefunc import cached_function
29
30
# Computation of Witten's rspin class for large r
31
32
33
def rspin_leg_factor(d, a):
34
if a < 0:
35
a = X + a
36
return Pmpolynomial(d).subs(a=a)
37
38
39
def rspin_edge_factor(w1, m, d1, d2):
40
R = PolynomialRing(QQ, 1, 'X')
41
X = R.gens()[0]
42
d = d1 + d2 + 1
43
S = 0
44
for i in range(d + 1):
45
S += R(rspin_leg_factor(i, (w1 + i) % (m - 1))(X=m) *
46
rspin_leg_factor(d - i, (m - 2 - i - w1) % (m - 1))(X=m) * X**i)
47
S /= X + 1
48
assert S.denominator() == 1
49
S = S.numerator()
50
# print(-S[d1])
51
return -S[d1]
52
53
54
def rspin_coeff_setup(num, g, r, n=0, dvector=(), moduli_type=MODULI_ST):
55
markings = tuple(range(1, n + 1))
56
G = single_stratum(num, g, r, markings, moduli_type)
57
nr = G.M.nrows()
58
nc = G.M.ncols()
59
edge_list = []
60
exp_list = []
61
scalar_factor = 1 / autom_count(num, g, r, markings, moduli_type)
62
given_weights = [1 - G.M[i + 1, 0][0] for i in range(nr - 1)]
63
for i in range(1, nr):
64
for j in range(1, G.M[i, 0].degree() + 1):
65
scalar_factor /= factorial(G.M[i, 0][j])
66
scalar_factor *= (-1)**G.M[i, 0][j]
67
scalar_factor *= (rspin_leg_factor(j, 0))**(G.M[i, 0][j])
68
given_weights[i - 1] -= j * G.M[i, 0][j]
69
for j in range(1, nc):
70
ilist = [i for i in range(1, nr) if G.M[i, j] != 0]
71
if G.M[0, j] == 0:
72
if len(ilist) == 1:
73
i1 = ilist[0]
74
i2 = ilist[0]
75
exp1 = G.M[i1, j][1]
76
exp2 = G.M[i1, j][2]
77
else:
78
i1 = ilist[0]
79
i2 = ilist[1]
80
exp1 = G.M[i1, j][1]
81
exp2 = G.M[i2, j][1]
82
edge_list.append([i1 - 1, i2 - 1])
83
exp_list.append(exp1)
84
exp_list.append(exp2)
85
else:
86
exp1 = G.M[ilist[0], j][1]
87
scalar_factor *= rspin_leg_factor(exp1, dvector[G.M[0, j][0] - 1])
88
given_weights[ilist[0] - 1] += dvector[G.M[0, j][0] - 1] - exp1
89
return edge_list, exp_list, given_weights, scalar_factor
90
91
92
def rspin_coeff(num, g, r, n=0, dvector=(), r_coeff=None, step=1,
93
m0given=-1, deggiven=-1, moduli_type=MODULI_ST):
94
markings = tuple(range(1, n + 1))
95
G = single_stratum(num, g, r, markings, moduli_type)
96
nr = G.M.nrows()
97
nc = G.M.ncols()
98
edge_list, exp_list, given_weights, scalar_factor = rspin_coeff_setup(
99
num, g, r, n, dvector, moduli_type)
100
if m0given == -1:
101
m0 = (ceil(sum([abs(i.subs(X=0))
102
for i in dvector]) / 2) + g * 1 + 1) * step
103
else:
104
m0 = m0given
105
h0 = nc - nr - n + 1
106
if deggiven == -1:
107
deg = 2 * sum(exp_list) + 2 * len(edge_list)
108
else:
109
deg = deggiven
110
if r_coeff is None:
111
mrange = list(range(m0 + step, m0 + step * deg + step + 1, step))
112
else:
113
mrange = [r_coeff] # just evaluate at a single value m = r_coeff
114
mvalues = []
115
for m in mrange:
116
given_weights_m = [ZZ(i.subs(X=m)) for i in given_weights]
117
total = 0
118
for weight_data in itertools.product(
119
*[list(range(m - 1)) for i in range(len(edge_list))]):
120
vertex_weights = copy(given_weights_m)
121
for i in range(len(edge_list)):
122
vertex_weights[edge_list[i][0]] += weight_data[i]
123
vertex_weights[edge_list[i][1]] -= weight_data[i] + \
124
2 + exp_list[2 * i] + exp_list[2 * i + 1]
125
if len([i for i in vertex_weights if i % (m - 1) != 0]) > 0:
126
continue
127
term = 1
128
for i in range(len(edge_list)):
129
term *= rspin_edge_factor(weight_data[i],
130
m, exp_list[2 * i], exp_list[2 * i + 1])
131
total += term
132
if r_coeff is None:
133
mvalues.append(total * ZZ(m - 1)**(-h0))
134
else:
135
# print(m-1)
136
# undo the rescaling by r**degree
137
mvalues.append(ZZ(-1)**(r - g) * total * ZZ(m - 1)
138
** (-r + g - h0) * ZZ(m)**(-r))
139
# print(mrange,mvalues, scalar_factor, r, h0)
140
mpoly = ZZ(-1)**(r - g) * (interpolate(mrange, mvalues).subs(x=X)
141
* scalar_factor).simplify_rational()
142
if r_coeff is not None:
143
mpoly = QQ(mpoly.subs(X=r_coeff))
144
return mpoly
145
146
147
def rspin_compute(g, r, n=0, dvector=(), r_coeff=None,
148
step=1, m0=-1, deg=-1, moduli_type=MODULI_ST):
149
answer = []
150
markings = tuple(range(1, n + 1))
151
for i in range(num_strata(g, r, markings, moduli_type)):
152
answer.append(rspin_coeff(i, g, r, n, dvector,
153
r_coeff, step, m0, deg, moduli_type))
154
return vector(answer)
155
156
157
def rspin_degree_test(g, r, n=0, dvector=(), step=1,
158
m0=-1, deg=-1, moduli_type=MODULI_ST):
159
answer = []
160
markings = tuple(range(1, n + 1))
161
for i in range(num_strata(g, r, markings, moduli_type)):
162
answer.append(rspin_coeff(i, g, r, n, dvector,
163
step, m0, deg, moduli_type).degree(X))
164
return answer
165
166
167
def rspin_constant(g, r, n=0, dvector=(), step=1, m0=-
168
1, deg=-1, moduli_type=MODULI_ST):
169
answer = []
170
markings = tuple(range(1, n + 1))
171
for i in range(num_strata(g, r, markings, moduli_type)):
172
answer.append(rspin_coeff(i, g, r, n, dvector,
173
step, m0, deg, moduli_type).subs(X=0))
174
return vector(answer)
175
176
177
def Wittenrspin(g, Avector, r_coeff=None, d=None, rpoly=False, moduli='st'):
178
r"""
179
Returns the polynomial limit of Witten's r-spin class in genus g with input Avector,
180
as discussed in the appendix of [Pandharipande-Pixton-Zvonkine '16].
181
182
More precisely, Avector is expected to be a vector of linear polynomials in QQ[r],
183
with leading coefficients being rational numbers in the interval [0,1] and constant
184
coefficients being integers. Elements of Avector should sum to C * r + 2g-2 for some
185
nonnegative integer C. Then Wittenrspin returns the tautological class obtained as
186
the limit of
187
188
r^(g-1+C) W_{g,n}^r(Avector)
189
190
for r >> 0 sufficiently large and divisible, evaluated at r=0.
191
192
INPUT:
193
194
- ``g`` -- integer; underlying genus
195
196
- ``Avector`` -- tuple ; a tuple of either integers, elements of a polynomial
197
ring QQ[r] in some variable r or tuples (C_i, D_i) which are interpreted as
198
polynomials C_i * r + D_i. For the entries with C_i = 1 we assume D_i <=-2.
199
200
- ``r_coeff`` -- integer or None (default: `None`); if a particular integer
201
r_coeff = r is specified, the function will return the (unscaled) class W_{g,n}^r(Avector),
202
not taking a limit for large r.
203
204
- ``d`` -- integer; desired degree in tautological ring; will be set to g-1+C by
205
default
206
207
- ``rpoly`` -- bool (default: `False`); if True, return the limit of
208
r^(g-1+C) W_{g,n}^r(Avector) without evaluating at r=0, as a tautclass with
209
coefficients being polynomials in r
210
211
EXAMPLES:
212
213
We start by verifying the conjecture from the appendix of [Pandharipande-Pixton-Zvonkine '16]
214
for g = 2 and mu = (2)::
215
216
sage: from admcycles import Wittenrspin, Strataclass
217
sage: H1 = Wittenrspin(2, (2,))
218
sage: H2 = Strataclass(2, 1, (2,))
219
sage: (H1-H2).is_zero()
220
True
221
222
We can also verify a new conjecture for classes of strata of meromorphic differentials for
223
g = 1 and mu = (3,-1,-2). The argument (1/2,-1) stands for an insertion 1/2 * r -1::
224
225
sage: H1 = Wittenrspin(1, (3, (1/2,-1), (1/2,-2)))
226
sage: H2 = Strataclass(1, 1, (3,-1,-2))
227
sage: (H1+H2).is_zero()
228
True
229
230
As a variant of this, we also verify that insertions r-b stand for poles of order b with
231
vanishing residues::
232
233
sage: R.<r> = PolynomialRing(QQ,1)
234
sage: H1 = Wittenrspin(1, (5,r-2, r-3))
235
sage: H2 = Strataclass(1, 1, (5,-2,-3), res_cond=(2,))
236
sage: (H1+H2).is_zero()
237
True
238
239
We can also compute the (scaled) Witten's class without substituting r=0::
240
241
sage: Wittenrspin(1,(2,r-2),rpoly=True)
242
Graph : [1] [[1, 2]] []
243
Polynomial : (1/12*r^2 - 5/24*r + 1/12)*(kappa_1)_0 + (-1/12*r^2 + 29/24*r - 37/12)*psi_1 + (-1/12*r^2 + 17/24*r - 13/12)*psi_2
244
<BLANKLINE>
245
Graph : [0] [[4, 5, 1, 2]] [(4, 5)]
246
Polynomial : -1/48*r + 1/24
247
<BLANKLINE>
248
Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]
249
Polynomial : 1/12*r^2 - 17/24*r + 13/12
250
251
Instead of calculating the asymptotic, polynomial behaviour of Witten's class,
252
we can also input a concrete value for r, using the option r_coeff. Below we
253
verify the CohFT property of Witten's class, first for a separating boundary
254
divisor::
255
256
sage: from admcycles import StableGraph
257
sage: A = Wittenrspin(2,(1,1),r_coeff=4)
258
sage: gr = StableGraph([1,1],[[1,2,3],[4]],[(3,4)])
259
sage: pb = gr.boundary_pullback(A)
260
sage: vector(pb.totensorTautbasis(1)[1])
261
(0, 3/4, 0, 3/4, 3/4)
262
sage: B = Wittenrspin(1,(1,1,2),r_coeff=4)
263
sage: B.basis_vector()
264
(0, 1/4, 0, 1/4, 1/4)
265
sage: C = Wittenrspin(1,(0,),r_coeff=4)
266
sage: C.basis_vector()
267
(3)
268
269
Then for a nonseparating boundary divisor::
270
271
sage: B = Wittenrspin(2,(2,),r_coeff=4)
272
sage: gr = StableGraph([1],[[1,2,3]],[(2,3)])
273
sage: pb = gr.boundary_pullback(B)
274
sage: pb.totensorTautbasis(1)
275
(0, 1/2, 1/4, 1/4, -1/4)
276
sage: A1 = Wittenrspin(1,(2,1,1),r_coeff=4)
277
sage: A2 = Wittenrspin(1,(2,0,2),r_coeff=4)
278
sage: A3 = Wittenrspin(1,(2,2,0),r_coeff=4)
279
sage: L=[t.basis_vector() for t in [A1,A2,A3]]; L
280
[(0, 1/2, 0, 0, 1/4), (0, 0, 0, 1/4, -1/4), (0, 0, 1/4, 0, -1/4)]
281
sage: sum(L)
282
(0, 1/2, 1/4, 1/4, -1/4)
283
284
285
We can also check manually, that interpolating the above results for
286
large r precisely gives the output of the option rpoly=True::
287
288
sage: from admcycles.double_ramification_cycle import interpolate
289
sage: H1 = Wittenrspin(1, (3, (1/2,-1), (1/2,-2)), rpoly=True)
290
sage: H1.basis_vector()
291
(0, -1/4*r^2 + 5/2*r - 3, 1/4*r^2 - r, 1/4*r^2 - r - 3, -1/4*r^2 + 1/2*r + 5)
292
sage: res=[]
293
sage: pts = list(range(6,11,2))
294
sage: for r in pts:
295
....: res.append(r**(1-1+1)*Wittenrspin(1, (3, 1/2*r-1, 1/2*r-2),r_coeff=r).basis_vector(1))
296
sage: v = vector([interpolate(pts, [a[i] for a in res],'r') for i in range(5)])
297
sage: v-H1.basis_vector()
298
(0, 0, 0, 0, 0)
299
"""
300
n = len(Avector)
301
302
if r_coeff is None:
303
polyentries = [a for a in Avector if isinstance(a, MPolynomial)]
304
if len(polyentries) > 0:
305
R = polyentries[0].parent()
306
r = R.gens()[0]
307
else:
308
R = PolynomialRing(QQ, 1, 'r')
309
r = R.gens()[0]
310
311
X = SR.var('X')
312
AvectorX = []
313
Cvector = []
314
Dvector = []
315
316
# Extract entries of Avector into a unified format (AvectorX)
317
# Collect linear and constant coefficients of entries of AvectorX in
318
# Cvector, Dvector
319
for a in Avector:
320
if isinstance(a, numbers.Integral):
321
AvectorX.append(Integer(a))
322
Cvector.append(QQ(0))
323
Dvector.append(Integer(a))
324
elif isinstance(a, MPolynomial):
325
AvectorX.append(QQ(a[1]) * X + QQ(a[0]))
326
Cvector.append(QQ(a[1]))
327
Dvector.append(QQ(a[0]))
328
elif isinstance(a, Iterable):
329
AvectorX.append(QQ(a[0]) * X + QQ(a[1]))
330
Cvector.append(QQ(a[0]))
331
Dvector.append(QQ(a[1]))
332
else:
333
raise ValueError(
334
'Entries of Avector must be Integers, Polynomials or tuples (C_i, D_i)')
335
336
# For C_i = 1, D_i = -1 we require a simple pole with vanishing residue
337
# => return zero class
338
if any((Cvector[i] == 1) and (Dvector[i] == -1) for i in range(n)):
339
return tautclass([])
340
341
step = lcm(c.denom() for c in Cvector)
342
C = sum(Cvector)
343
assert C in ZZ
344
assert sum(Dvector) == 2 * g - 2
345
else:
346
AvectorX = [ZZ(a) for a in Avector]
347
step = 0
348
349
if d is None:
350
if r_coeff is None:
351
d = ZZ(g - 1 + C)
352
else:
353
denom = ZZ((r_coeff - 2) * (g - 1) + sum(Avector))
354
if denom % r_coeff != 0:
355
# Witten's class vanishes unless this congruence is satisfied
356
return tautclass([])
357
else:
358
d = ZZ(denom / ZZ(r_coeff))
359
moddict = {'sm': MODULI_SM, 'rt': MODULI_RT,
360
'ct': MODULI_CT, 'st': MODULI_ST}
361
modu = moddict[moduli]
362
363
rvect = rspin_compute(g, d, n, tuple(AvectorX), r_coeff,
364
step, m0=-1, deg=-1, moduli_type=modu)
365
if not rpoly:
366
rvect = rvect.subs(X=0)
367
rvect = convert_vector_to_monomial_basis(
368
rvect, g, d, tuple(range(1, n + 1)), modu)
369
370
if rpoly:
371
rvect = vector((b.subs(X=r) for b in rvect))
372
373
R = TautologicalRing(g, n, moduli=moduli)
374
return R.from_vector(rvect, d)
375
# return Tautv_to_tautclass(rvect, g, n, d, moduli=moduli)
376
377
378
@cached_function
379
def Pmpolynomial(m):
380
r"""
381
Returns the expression P_m(X,a) as defined in [Pandharipande-Pixton-Zvonkine '16, Section 4.5].
382
383
TESTS::
384
385
sage: from admcycles.witten import Pmpolynomial
386
sage: Pmpolynomial(0)
387
1
388
sage: Pmpolynomial(1)
389
-1/12*X^2 + 1/2*(X - 1)*a - 1/2*a^2 + 5/24*X - 1/12
390
sage: Pmpolynomial(2)
391
1/288*X^4 - 1/12*(5*X - 1)*a^3 + 1/8*a^4 + 7/288*X^3 + 1/48*(20*X^2 - 5*X - 4)*a^2 - 29/384*X^2 - 1/48*(6*X^3 + X^2 - 9*X + 2)*a + 7/288*X + 1/288
392
"""
393
(b, X, a) = var('b,X,a')
394
if m == 0:
395
return 1 + 0 * X
396
return (QQ(1) / 2 * symbolic_sum((2 * m * X - X - 2 * b) * Pmpolynomial(m - 1).subs(a=b - 1), b, 1, a) - QQ(1) / (4 * m * X * (X - 1)) *
397
symbolic_sum((X - 1 - b) * (2 * m * X - b) * (2 * m * X - X - 2 * b) * Pmpolynomial(m - 1).subs(a=b - 1), b, 1, X - 2)).simplify_rational()
398
399