Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
180 views
unlisted
ubuntu2004
1
# -*- coding: utf-8 -*-
2
r"""
3
Tautological subring of the cohomology ring of the moduli space of curves.
4
"""
5
6
import numbers
7
8
from sage.misc.cachefunc import cached_method
9
from sage.misc.misc_c import prod
10
11
from sage.structure.unique_representation import UniqueRepresentation
12
from sage.structure.richcmp import op_EQ, op_NE
13
from sage.structure.element import ModuleElement, parent
14
from sage.structure.all import coercion_model
15
16
17
from sage.categories.functor import Functor
18
from sage.categories.pushout import ConstructionFunctor
19
from sage.categories.algebras import Algebras
20
from sage.categories.rings import Rings
21
22
from sage.arith.all import factorial, bernoulli, multinomial
23
from sage.combinat.all import Partitions
24
from sage.combinat.integer_vector import IntegerVectors
25
from sage.rings.ring import Algebra
26
from sage.rings.integer_ring import ZZ
27
from sage.rings.rational_field import QQ
28
from sage.modules.free_module_element import vector
29
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
30
from sage.rings.polynomial.term_order import TermOrder
31
from sage.combinat.integer_vector_weighted import WeightedIntegerVectors
32
from sage.matrix.constructor import matrix
33
34
from .moduli import _moduli_to_str, _str_to_moduli, MODULI_TL, get_moduli, socle_degree
35
from .admcycles import decstratum
36
from .stable_graph import StableGraph
37
from .identify_classes import identify_class
38
39
40
_CommutativeRings = Rings().Commutative()
41
42
43
# NOTE: replaces admcycles.tautclass
44
class TautologicalClass(ModuleElement):
45
r"""
46
An element of a tautological ring.
47
48
Internally, it is represented by a list ``terms`` of objects of type
49
:class:`admcycles.admcycles.decstratum`. Such element should never
50
be constructed directly by calling :class:`TautologicalClass`. Instead
51
use the parent class :class:`TautologicalRing` or dedicated functions
52
as in the examples below
53
54
EXAMPLES::
55
56
sage: from admcycles import *
57
sage: R = TautologicalRing(3,1)
58
sage: gamma = StableGraph([1,2],[[1,2],[3]],[(2,3)])
59
sage: ds1 = R(gamma, kappa=[[1],[]])
60
sage: ds1
61
Graph : [1, 2] [[1, 2], [3]] [(2, 3)]
62
Polynomial : (kappa_1)_0
63
sage: ds2 = R(gamma, kappa=[[],[1]])
64
sage: ds2
65
Graph : [1, 2] [[1, 2], [3]] [(2, 3)]
66
Polynomial : (kappa_1)_1
67
sage: t = ds1 + ds2
68
sage: (t - R(gamma) * R.kappa(1)).is_zero()
69
True
70
71
Constructing a tautological class from dedicated functions::
72
73
sage: psiclass(1, 2, 1) # psi_1 on M_{2,1}
74
Graph : [2] [[1]] []
75
Polynomial : psi_1
76
"""
77
78
def __init__(self, parent, terms, clean=True):
79
r"""
80
INPUT:
81
82
- parent : a class:`TautologicalRing`
83
- terms : a list of class:`~admcycles.admcycles.decstratum`
84
- clean: boolean (default ``True``)
85
whether to apply ``dimension_filter`` and ``consolidate`` on the input
86
"""
87
ModuleElement.__init__(self, parent)
88
89
if isinstance(terms, (tuple, list)):
90
self._terms = {}
91
for t in terms:
92
if not isinstance(t, decstratum):
93
raise TypeError
94
if t.gamma.is_mutable():
95
raise ValueError('mutable stable graph in a decstratum when building TautologicalClass')
96
if t.gamma.vanishes(parent._moduli):
97
continue
98
if clean:
99
t.dimension_filter(parent._moduli)
100
t.consolidate()
101
if t.gamma in self._terms:
102
self._terms[t.gamma].poly += t.poly
103
if not self._terms[t.gamma]:
104
del self._terms[t.gamma]
105
else:
106
self._terms[t.gamma] = t
107
elif isinstance(terms, dict):
108
if clean:
109
self._terms = {}
110
for gamma, term in terms.items():
111
if gamma != term.gamma:
112
raise ValueError
113
if gamma.vanishes(parent._moduli):
114
continue
115
term.dimension_filter(parent._moduli)
116
term.consolidate()
117
if term:
118
if gamma in self._terms:
119
self._terms[gamma] += term
120
else:
121
self._terms[gamma] = term
122
else:
123
self._terms = terms
124
else:
125
raise TypeError
126
127
def copy(self):
128
P = self.parent()
129
return P.element_class(P, {g: t.copy() for g, t in self._terms.items()})
130
131
# TODO: change the name
132
# TODO: this should depend on the moduli (though this global check is deactivated in decstratum.dimension_filter)
133
def dimension_filter(self):
134
for g in list(self._terms):
135
self._terms[g].dimension_filter()
136
if not self._terms[g]:
137
del self._terms[g]
138
139
def _repr_(self):
140
if not self._terms:
141
return '0'
142
return '\n\n'.join(repr(self._terms[g]) for g in sorted(self._terms))
143
144
def _unicode_art_(self):
145
r"""
146
Return unicode art for the tautological class.
147
148
EXAMPLES::
149
150
sage: from admcycles import *
151
sage: D = DR_cycle(1,(2,-2))
152
sage: unicode_art(D)
153
Graph :
154
<BLANKLINE>
155
╭──╮
156
│1 │
157
╰┬┬╯
158
12
159
<BLANKLINE>
160
Polynomial : 2*ψ₁ + 2*ψ₂
161
Graph :
162
╭╮
163
45
164
╭┴┴╮
165
│0 │
166
╰┬┬╯
167
12
168
<BLANKLINE>
169
Polynomial : -1/24
170
"""
171
from sage.typeset.unicode_art import unicode_art, UnicodeArt
172
if not self._terms:
173
return unicode_art('0')
174
return prod((self._terms[g]._unicode_art_() for g in sorted(self._terms)), UnicodeArt())
175
176
def is_empty(self):
177
return not self._terms
178
179
def is_nilpotent(self):
180
r"""
181
Return whether this element is nilpotent.
182
183
EXAMPLES::
184
185
sage: from admcycles import TautologicalRing
186
sage: R = TautologicalRing(1, 1)
187
sage: R.one().is_nilpotent()
188
False
189
sage: R.psi(1).is_nilpotent()
190
True
191
"""
192
return self.constant_coefficient().is_nilpotent()
193
194
def is_unit(self):
195
r"""
196
Return whether this element is a unit.
197
198
EXAMPLES::
199
200
sage: from admcycles import TautologicalRing
201
sage: R = TautologicalRing(1, 1)
202
sage: R.one().is_unit()
203
True
204
sage: R.psi(1).is_unit()
205
False
206
"""
207
return self.constant_coefficient().is_unit()
208
209
def inverse_of_unit(self):
210
r"""
211
Return the inverse of this element.
212
213
EXAMPLES::
214
215
sage: from admcycles import TautologicalRing
216
sage: R = TautologicalRing(0, 5)
217
sage: (R.one() + R.psi(1)).inverse_of_unit()
218
Graph : [0] [[1, 2, 3, 4, 5]] []
219
Polynomial : 1 - psi_1 + psi_1^2
220
"""
221
try:
222
from sage.rings.polynomial.misc import inverse_of_unit
223
except ImportError:
224
# NOTE: this is only in beta versions of sage
225
# see https://trac.sagemath.org/ticket/33499
226
from .misc import inverse_of_unit
227
return inverse_of_unit(self)
228
229
# TODO: shouldn't this be called homogeneous_component?
230
def degree_part(self, d):
231
r"""
232
Return the homogeneous component of degree ``d``.
233
234
EXAMPLES::
235
236
sage: from admcycles import TautologicalRing
237
sage: R = TautologicalRing(1, 2)
238
sage: f = (1 - 2 * R.psi(1) + 3 * R.psi(2)**2)**2
239
sage: f.degree_part(0)
240
Graph : [1] [[1, 2]] []
241
Polynomial : 1
242
sage: f.degree_part(1)
243
Graph : [1] [[1, 2]] []
244
Polynomial : -4*psi_1
245
sage: f.degree_part(2)
246
Graph : [1] [[1, 2]] []
247
Polynomial : 6*psi_2^2 + 4*psi_1^2
248
"""
249
P = self.parent()
250
new_terms = {}
251
for g, term in self._terms.items():
252
term = term.degree_part(d)
253
if term:
254
new_terms[g] = term
255
return P.element_class(P, new_terms)
256
257
def constant_coefficient(self):
258
r"""
259
Return the coefficient in degree zero as an element in the base ring.
260
261
EXAMPLES::
262
263
sage: from admcycles import TautologicalRing
264
sage: R = TautologicalRing(2, 1)
265
sage: (1/3 - R.psi(1) + R.kappa(2)).constant_coefficient()
266
1/3
267
268
sage: A.<x, y> = QQ[]
269
sage: R = TautologicalRing(2, 1, base_ring=A)
270
sage: f = (1 - x * R.psi(1)) * (1/3 + y * R.kappa(2))
271
sage: f.constant_coefficient()
272
1/3
273
274
TESTS::
275
276
sage: from admcycles import TautologicalRing, StableGraph
277
sage: R = TautologicalRing(3,2)
278
sage: Gamma = StableGraph([3],[[2,1]],[]); Gamma
279
[3] [[2, 1]] []
280
sage: a = R.one() + R(Gamma); a
281
Graph : [3] [[1, 2]] []
282
Polynomial : 1
283
<BLANKLINE>
284
Graph : [3] [[2, 1]] []
285
Polynomial : 1
286
sage: a.constant_coefficient()
287
2
288
"""
289
return self.fund_evaluate()
290
291
def degree_cap(self, dmax):
292
r"""
293
Return the class where we drop components of degree above ``dmax``.
294
295
EXAMPLES::
296
297
sage: from admcycles import TautologicalRing
298
sage: R = TautologicalRing(1, 2)
299
sage: f = (1 - 2 * R.psi(1) + 3 * R.psi(2)**2)**2
300
sage: f.degree_cap(1)
301
Graph : [1] [[1, 2]] []
302
Polynomial : 1 - 4*psi_1
303
"""
304
P = self.parent()
305
new_terms = {}
306
for g, term in self._terms.items():
307
term = term.degree_cap(dmax)
308
if term:
309
new_terms[g] = term
310
return P.element_class(P, new_terms)
311
312
def subs(self, *args, **kwds):
313
r"""
314
Perform substitution on coefficients.
315
316
EXAMPLES::
317
318
sage: from admcycles import TautologicalRing
319
sage: A.<a1, a2> = PolynomialRing(QQ,2)
320
sage: R = TautologicalRing(2, 2, base_ring=A)
321
sage: t = a1 * R.psi(1) + a2 * R.psi(2)
322
sage: t
323
Graph : [2] [[1, 2]] []
324
Polynomial : a1*psi_1 + a2*psi_2
325
sage: t.subs({a2: 1 - a1})
326
Graph : [2] [[1, 2]] []
327
Polynomial : a1*psi_1 + (-a1 + 1)*psi_2
328
sage: t
329
Graph : [2] [[1, 2]] []
330
Polynomial : a1*psi_1 + a2*psi_2
331
332
sage: t = (a1 - a2) * R.psi(1) + (a1 + a2) * R.psi(2)
333
sage: t.subs({a1: a2})
334
Graph : [2] [[1, 2]] []
335
Polynomial : 2*a2*psi_2
336
sage: t.subs({a1: 0, a2: 0})
337
0
338
sage: t
339
Graph : [2] [[1, 2]] []
340
Polynomial : (a1 - a2)*psi_1 + (a1 + a2)*psi_2
341
"""
342
new_terms = {}
343
for g, term in self._terms.items():
344
term = term.copy(mutable=False)
345
coeffs = term.poly.coeff
346
monom = term.poly.monom
347
i = 0
348
while i < len(coeffs):
349
coeffs[i] = coeffs[i].subs(*args, **kwds)
350
if not coeffs[i]:
351
coeffs.pop(i)
352
monom.pop(i)
353
else:
354
i += 1
355
if i:
356
new_terms[g] = term
357
P = self.parent()
358
return P.element_class(P, new_terms)
359
360
# TODO: change the name, maybe simplify_full?
361
# TODO: the argument r is very bad as it mutates the tautological class
362
def FZsimplify(self, r=None):
363
r"""
364
Return representation of self as a tautclass formed by a linear combination of
365
the preferred tautological basis.
366
367
If ``r`` is given, only take degree r part.
368
369
EXAMPLES::
370
371
sage: from admcycles import TautologicalRing
372
sage: R = TautologicalRing(0, 4)
373
sage: t = 7 + R.psi(1) + 3 * R.psi(2) - R.psi(3)
374
sage: t.FZsimplify()
375
Graph : [0] [[1, 2, 3, 4]] []
376
Polynomial : 7 + 3*(kappa_1)_0
377
378
sage: t.FZsimplify(r=0)
379
Graph : [0] [[1, 2, 3, 4]] []
380
Polynomial : 7
381
"""
382
if self.is_empty():
383
return self
384
385
R = self.parent()
386
if r is not None:
387
return R.from_basis_vector(self.basis_vector(r), r)
388
else:
389
result = R.zero()
390
for r in self.degree_list():
391
result += R.from_basis_vector(self.basis_vector(r), r)
392
return result
393
394
# TODO: change the name?
395
def toprodtautclass(self, g=None, n=None):
396
P = self.parent()
397
if g is not None or n is not None:
398
from .superseded import deprecation
399
deprecation(109, "the arguments 'g' and 'n' of TautologicalClass.toprodtautclass are deprecated.")
400
if (g is not None and g != P._g) or (n is not None and n != P._n):
401
raise ValueError('invalid (g,n) (got ({},{}) instead of ({},{}))'.format(g, n, P._g, P._n))
402
403
from .admcycles import prodtautclass
404
return prodtautclass(P.trivial_graph(), [[t.copy()] for t in self._terms.values()])
405
406
# TODO: the method tautclass.simplify used to have more arguments simplify(self,g=None,n=None,r=None)
407
# TODO: should we really deprecate the argument r? (one can always do degree_cap followed by a simplify)
408
def simplify(self, g=None, n=None, r=None):
409
r"""
410
Simplifies self by combining terms with same tautological generator, returns self.
411
412
EXAMPLES::
413
414
sage: from admcycles import TautologicalRing
415
sage: R = TautologicalRing(2, 1)
416
sage: t = R.psi(1) + 11*R.psi(1)
417
sage: t
418
Graph : [2] [[1]] []
419
Polynomial : 12*psi_1
420
sage: t.simplify()
421
Graph : [2] [[1]] []
422
Polynomial : 12*psi_1
423
"""
424
P = self.parent()
425
if g is not None or n is not None or r is not None:
426
from .superseded import deprecation
427
deprecation(109, "the arguments 'g, n, r' of TautologicalClass.simplify are deprecated.")
428
if r is not None:
429
D = [r]
430
else:
431
D = self.degree_list()
432
433
if self.is_empty():
434
# TODO: if elements were immutable we could return self
435
return self.copy()
436
result = P.zero()
437
for d in D:
438
result += P.from_vector(self.vector(d), d)
439
self._terms = result._terms
440
return self
441
442
def simplified(self, g=None, n=None, r=None):
443
r"""
444
Return a simplified version of self by combining terms with same tautological generator.
445
"""
446
P = self.parent()
447
if g is not None or n is not None:
448
from .superseded import deprecation
449
deprecation(109, "the arguments 'g' and 'n' of TautologicalClass.simplified are deprecated.")
450
if (g is not None and g != P._g) or (n is not None and n != P._n):
451
raise ValueError('invalid g,n (got ({},{}) instead of ({},{}))'.format(g, n, P._g, P._n))
452
if self.is_empty():
453
# TODO: if elements were immutable we could return self
454
return self.copy()
455
if r is not None:
456
return P.from_vector(self.vector(r), r)
457
self.simplify()
458
return self
459
460
def __neg__(self):
461
if self.is_empty():
462
# TODO: if elements were immutable we could return self
463
return self.copy()
464
P = self.parent()
465
return P.element_class(P, {g: -term for g, term in self._terms.items()}, clean=False)
466
467
def _add_(self, other):
468
r"""
469
TESTS::
470
471
sage: from admcycles import TautologicalRing
472
sage: R = TautologicalRing(1, 1)
473
sage: R(1) + 1
474
Graph : [1] [[1]] []
475
Polynomial : 2
476
sage: 1 + R(1)
477
Graph : [1] [[1]] []
478
Polynomial : 2
479
"""
480
if self.is_empty():
481
# TODO: if elements were immutable we could return other
482
return other.copy()
483
if other.is_empty():
484
# TODO: if elements were immutable we could return self
485
return self.copy()
486
P = self.parent()
487
new_terms = {g: t.copy() for g, t in self._terms.items()}
488
for g, term in other._terms.items():
489
if g in new_terms:
490
new_terms[g].poly += term.poly
491
if not new_terms[g]:
492
del new_terms[g]
493
else:
494
new_terms[g] = term
495
return P.element_class(P, new_terms, clean=False)
496
497
def _lmul_(self, other):
498
r"""
499
Scalar multiplication by ``other``.
500
501
TESTS::
502
503
sage: from admcycles import TautologicalRing
504
sage: R = TautologicalRing(2, 2)
505
sage: -3/5 * R.psi(1)
506
Graph : [2] [[1, 2]] []
507
Polynomial : -3/5*psi_1
508
sage: 0 * R.psi(1) == 1 * R.zero() == R.zero()
509
True
510
sage: assert (0 * R.psi(1)).parent() is R
511
sage: assert (1 * R.zero()).parent() is R
512
"""
513
P = self.parent()
514
assert parent(other) is self.base_ring()
515
if other.is_zero():
516
return P.zero()
517
elif other.is_one():
518
# TODO: if elements were immutable we could return self
519
return self.copy()
520
new_terms = {g: other * term for g, term in self._terms.items()}
521
return P.element_class(P, new_terms, clean=False)
522
523
def _mul_(self, other):
524
r"""
525
TESTS::
526
527
sage: from admcycles import TautologicalRing
528
sage: R = TautologicalRing(2, 2)
529
sage: R.psi(1) * R.psi(2)
530
Graph : [2] [[1, 2]] []
531
Polynomial : psi_1*psi_2
532
533
sage: R.generators(1)[3] * R.generators(2)[23]
534
Graph : [0, 1, 1] [[1, 2, 4], [5, 6], [7]] [(4, 5), (6, 7)]
535
Polynomial : (kappa_1)_2
536
537
sage: (R.zero() * R.one()) == (R.one() * R.zero()) == R.zero()
538
True
539
sage: R.one() * R.one() == R.one()
540
True
541
542
Multiplication with non-standard markings::
543
544
sage: from admcycles import StableGraph
545
sage: R2 = TautologicalRing(1, [4, 7])
546
sage: g2 = StableGraph([0], [[1,2,4,7]], [(1,2)])
547
sage: R2(g2) * (1 + R2.psi(4)) * (1 + R2.psi(7))
548
Graph : [0] [[1, 2, 4, 7]] [(1, 2)]
549
Polynomial : 1 + psi_7 + psi_4
550
"""
551
if self.is_empty() or other.is_empty():
552
return self.parent().zero()
553
P = self.parent()
554
if P._markings and P._markings[-1] != P._n:
555
# TODO: maybe we want something more efficient for non-standard markings?
556
dic_to_std = {j: i + 1 for i, j in enumerate(P._markings)}
557
dic_from_std = {i + 1: j for i, j in enumerate(P._markings)}
558
return (self.rename_legs(dic_to_std, inplace=False) * other.rename_legs(dic_to_std, inplace=False)).rename_legs(dic_from_std, inplace=False)
559
560
new_terms = {}
561
for t1 in self._terms.values():
562
for t2 in other._terms.values():
563
for g, term in t1.multiply(t2, P)._terms.items():
564
if g.vanishes(P._moduli):
565
continue
566
term.dimension_filter(moduli=P._moduli)
567
term.poly.consolidate(force=False)
568
if term:
569
if g in new_terms:
570
new_terms[g].poly += term.poly
571
else:
572
new_terms[g] = term
573
for g in list(new_terms):
574
if not new_terms[g]:
575
del new_terms[g]
576
return P.element_class(P, new_terms, clean=False)
577
578
def exp(self):
579
r"""
580
Return the exponential of this tautological class.
581
582
The exponential is defined as ``exp(x) = 1 + x + 1/2*x^2 + ... ``.
583
It is well defined as long as ``x`` has no degree zero term.
584
585
EXAMPLES::
586
587
sage: from admcycles import TautologicalRing
588
sage: R = TautologicalRing(1, 2)
589
sage: R.psi(1).exp()
590
Graph : [1] [[1, 2]] []
591
Polynomial : 1 + psi_1 + 1/2*psi_1^2
592
593
TESTS::
594
595
sage: from admcycles import TautologicalRing
596
sage: R = TautologicalRing(0, 3)
597
sage: R.zero().exp() == R.one()
598
True
599
"""
600
if self.vector(0):
601
raise ValueError('non-zero degree zero term')
602
P = self.parent()
603
f = P.one()
604
dmax = P.socle_degree()
605
if dmax == 0:
606
return f
607
result = f + self
608
y = self
609
for k in range(2, dmax + 1):
610
y = y * self
611
result += QQ((1, factorial(k))) * y
612
return result
613
614
# TODO: full support for all moduli
615
def evaluate(self, moduli=None):
616
r"""
617
Computes integral against the fundamental class of the corresponding moduli space,
618
e.g. the degree of the zero-cycle part of the tautological class (for moduli='st').
619
620
For non-standard moduli, this computes the socle-evaluation, which is given by
621
the integral against
622
623
* lambda_g for moduli='ct'
624
* lambda_g * lambda_{g-1} for moduli='rt' (and moduli='sm' for n=0)
625
626
EXAMPLES::
627
628
sage: from admcycles import TautologicalRing
629
sage: R = TautologicalRing(1, 1)
630
sage: R.kappa(1).evaluate()
631
1/24
632
633
Some non-standard moduli::
634
635
sage: from admcycles import *
636
sage: R = TautologicalRing(2,1,moduli='ct')
637
sage: R.kappa(2).evaluate()
638
7/5760
639
sage: (kappaclass(2,2,1)*lambdaclass(2,2,1)).evaluate()
640
7/5760
641
sage: R.kappa(2).evaluate(moduli='st') # wrong degree
642
0
643
644
TESTS::
645
646
sage: R = TautologicalRing(2,1,moduli='ct')
647
sage: Rst = TautologicalRing(2,1)
648
sage: L = lambdaclass(2,2,1)
649
sage: all((Rst(t)*L).evaluate() == t.evaluate() for t in R.generators(2))
650
True
651
sage: R = TautologicalRing(2,2,moduli='rt')
652
sage: Rst = TautologicalRing(2,2)
653
sage: LL = Rst.lambdaclass(2)*Rst.lambdaclass(1)
654
sage: all((Rst(t)*LL).evaluate() == t.evaluate() for t in R.generators(2))
655
True
656
"""
657
P = self.parent()
658
if moduli is None:
659
moduli = _moduli_to_str[P._moduli]
660
if moduli == 'tl':
661
raise ValueError('no well-defined socle evaluation for space of treelike curves')
662
if P._moduli != get_moduli(moduli):
663
R = TautologicalRing(P._g, P._markings, base_ring=P.base_ring(), moduli=moduli)
664
return R(self).evaluate()
665
return sum((t.evaluate(moduli=moduli) for t in self._terms.values()), P.base_ring().zero())
666
667
# TODO: change the name
668
def fund_evaluate(self):
669
r"""
670
Computes degree zero part of the class as multiple of the fundamental class.
671
672
EXAMPLES::
673
674
sage: from admcycles import TautologicalRing
675
sage: R = TautologicalRing(2, 1)
676
sage: t = R.psi(1)
677
sage: s = t.forgetful_pushforward([1])
678
sage: s
679
Graph : [2] [[]] []
680
Polynomial : 2
681
sage: s.fund_evaluate()
682
2
683
"""
684
return self.vector(0)[0]
685
686
def _richcmp_(self, other, op):
687
r"""
688
Implementation of comparisons (``==`` and ``!=``).
689
690
TESTS::
691
692
sage: from admcycles import TautologicalRing
693
sage: R = TautologicalRing(2,2)
694
sage: R.zero() == R.zero()
695
True
696
sage: R.zero() == R.psi(1)
697
False
698
sage: R.psi(1) == R.zero()
699
False
700
sage: R.psi(1) == R.psi(1)
701
True
702
sage: R.psi(1) == R.psi(2)
703
False
704
705
sage: R.zero() != R.zero()
706
False
707
sage: R.zero() != R.psi(1)
708
True
709
sage: R.psi(1) != R.zero()
710
True
711
sage: R.psi(1) != R.psi(1)
712
False
713
sage: R.psi(1) != R.psi(2)
714
True
715
sage: R = TautologicalRing(1,1)
716
sage: A = R(1) + R.psi(1) - R.kappa(1)
717
sage: A == R.zero()
718
False
719
sage: A == R.one()
720
True
721
"""
722
if op != op_EQ and op != op_NE:
723
raise TypeError('incomparable')
724
725
# TODO: is there a smarter choice of ordering?
726
D = set(self.degree_list())
727
D.update(other.degree_list())
728
D = sorted(D)
729
730
# NOTE: .vector(d) is much cheaper than .basis_vector(d) so we do that first
731
equal = all((self.vector(d) == other.vector(d) or self.basis_vector(d) == other.basis_vector(d)) for d in D)
732
return equal == (op == op_EQ)
733
734
def __bool__(self):
735
r"""
736
TESTS::
737
738
sage: from admcycles import TautologicalRing
739
sage: R = TautologicalRing(1, 1)
740
sage: bool(R.psi(1))
741
True
742
sage: bool(R.zero())
743
False
744
"""
745
# NOTE: .vector(d) is much cheaper than .basis_vector(d) so we do that first
746
return not all(self.vector(d).is_zero() or self.basis_vector(d).is_zero() for d in self.degree_list())
747
748
def is_zero(self, moduli=None):
749
r"""
750
Return whether this class is a known tautological relation (using
751
Pixton's implementation of the generalized Faber-Zagier relations).
752
753
If optional argument `moduli` is given, it checks the vanishing on
754
an open subset of the current moduli of stable curves.
755
756
EXAMPLES::
757
758
sage: from admcycles import TautologicalRing
759
sage: R = TautologicalRing(3, 0)
760
sage: diff = R.kappa(1) - 12 * R.lambdaclass(1)
761
sage: diff.is_zero()
762
False
763
764
sage: S = TautologicalRing(3, 0, moduli='sm')
765
sage: S(diff).is_zero()
766
True
767
sage: diff.is_zero(moduli='sm')
768
True
769
sage: S(diff).is_zero(moduli='st')
770
Traceback (most recent call last):
771
...
772
ValueError: moduli='st' is larger than the moduli 'sm' of the parent
773
"""
774
P = self.parent()
775
moduli = get_moduli(moduli, P._moduli)
776
if moduli > P._moduli:
777
raise ValueError("moduli={!r} is larger than the moduli {!r} of the parent".format(
778
_moduli_to_str[moduli], _moduli_to_str[P._moduli]))
779
if moduli != P._moduli:
780
R = TautologicalRing(P._g, P._markings, moduli, base_ring=P.base_ring())
781
return not R(self)
782
else:
783
return not self
784
785
def degree_list(self):
786
r"""
787
EXAMPLES::
788
789
sage: from admcycles import TautologicalRing
790
sage: R = TautologicalRing(2, 3)
791
sage: R.psi(1).degree_list()
792
[1]
793
sage: (1 + R.psi(1)**2).degree_list()
794
[0, 2]
795
sage: ((1 + R.psi(1))**2).degree_list()
796
[0, 1, 2]
797
"""
798
return sorted(set(d for t in self._terms.values() for g, n, d in t.gnr_list()))
799
800
def forgetful_pullback(self, forget=None):
801
r"""
802
Return the pullback of this tautological class under a forgetful map
803
`\bar M_{g, n'} --> \bar M_{g,n}`
804
805
INPUT:
806
807
forget : list of integers
808
legs that are forgotten by the map forgetful map
809
810
EXAMPLES::
811
812
sage: from admcycles import TautologicalRing
813
814
sage: R = TautologicalRing(1, 2)
815
sage: b = R.psi(2).forgetful_pullback([3])
816
sage: b
817
Graph : [1] [[1, 2, 3]] []
818
Polynomial : psi_2
819
<BLANKLINE>
820
Graph : [1, 0] [[1, 4], [2, 3, 5]] [(4, 5)]
821
Polynomial : -1
822
sage: b.parent()
823
TautologicalRing(g=1, n=3, moduli='st') over Rational Field
824
"""
825
P = self.parent()
826
if forget is None:
827
# TODO: if elements were immutable we could return self
828
return self
829
elif isinstance(forget, numbers.Integral):
830
k = ZZ(forget)
831
if k < 0:
832
raise ValueError("forget can only be non-negative")
833
elif isinstance(forget, (tuple, list)):
834
k = ZZ(len(forget))
835
if set(forget) != set(range(P._n + 1, P._n + k + 1)):
836
raise ValueError('can only forget the highest markings, got forget={} inside {}'.format(forget, P))
837
if k == 0:
838
return self
839
840
R = TautologicalRing(P._g, P._n + k, moduli=P._moduli, base_ring=P.base_ring())
841
return sum((R.element_class(R, c.forgetful_pullback_list(forget)) for c in self._terms.values()), R.zero())
842
843
def forgetful_pushforward(self, forget=None):
844
r"""
845
Return the pushforward of a given tautological class under a forgetful
846
map `\bar M_{g,n} \to \bar M_{g,n'}`.
847
848
INPUT:
849
850
forget : list of integers
851
legs that are forgotten by the map forgetful map
852
853
EXAMPLES::
854
855
sage: from admcycles import TautologicalRing
856
857
sage: R = TautologicalRing(1, 3)
858
sage: s1 = R.psi(3)^2
859
sage: s1.forgetful_pushforward([3])
860
Graph : [1] [[1, 2]] []
861
Polynomial : (kappa_1)_0
862
sage: s1.forgetful_pushforward([2,3])
863
Graph : [1] [[1]] []
864
Polynomial : 1
865
866
sage: R = TautologicalRing(1, [2, 5, 6])
867
sage: s1 = R.psi(5)^2
868
sage: s1.forgetful_pushforward([5])
869
Graph : [1] [[2, 6]] []
870
Polynomial : (kappa_1)_0
871
sage: s1.forgetful_pushforward([5]).parent()
872
TautologicalRing(g=1, n=(2, 6), moduli='st') over Rational Field
873
874
sage: R = TautologicalRing(2, 2)
875
sage: gens1 = R.generators(1)
876
sage: t = gens1[1] + 2*gens1[3]
877
sage: t.forgetful_pushforward([2])
878
Graph : [2] [[1]] []
879
Polynomial : 3
880
sage: t.forgetful_pushforward([2]).parent()
881
TautologicalRing(g=2, n=1, moduli='st') over Rational Field
882
883
TESTS::
884
885
sage: from admcycles import TautologicalRing
886
sage: TautologicalRing(1, [2, 4]).psi(4).forgetful_pushforward([5])
887
Traceback (most recent call last):
888
...
889
ValueError: invalid marking 5 in forget
890
sage: TautologicalRing(1, [2, 4]).psi(4).forgetful_pushforward(2)
891
Traceback (most recent call last):
892
...
893
ValueError: unstable pair (g,n) = (1, 0)
894
sage: TautologicalRing(1, [2, 4]).psi(4).forgetful_pushforward(3)
895
Traceback (most recent call last):
896
...
897
ValueError: forget must be between 0 and the number of markings
898
"""
899
P = self.parent()
900
if forget is None:
901
# TODO: if elements were immutable we could return self
902
return self.copy()
903
elif isinstance(forget, numbers.Integral):
904
k = ZZ(forget)
905
if k < 0 or k > P._n:
906
raise ValueError("forget must be between 0 and the number of markings")
907
if k == 0:
908
return self
909
forget = P._markings[-k:]
910
elif isinstance(forget, (tuple, list)):
911
k = ZZ(len(forget))
912
forget = sorted(forget)
913
for i in range(len(forget) - 1):
914
if forget[i] == forget[i + 1]:
915
raise ValueError('multiple marking {} in forget'.format(forget[i]))
916
for i in forget:
917
if i not in P._markings:
918
raise ValueError('invalid marking {} in forget'.format(i))
919
else:
920
raise TypeError('invalid input forget')
921
922
if k == 0:
923
return self
924
925
new_markings = [i for i in P._markings if i not in forget]
926
R = TautologicalRing(P._g, new_markings, moduli=P._moduli, base_ring=P.base_ring())
927
return R.element_class(R, [term.forgetful_pushforward(forget) for term in self._terms.values()])
928
929
# TODO: we should not do inplace operation if there is no support for mutable/immutable
930
# (this needs some cleaning on the StableGraph side as well)
931
# TODO: should it really be called rename_legs? it is a bit misleading are we are just
932
# relabelling the markings in this function (same in decstratum and StableGraph)
933
def rename_legs(self, dic, rename=None, inplace=True):
934
r"""
935
Rename the legs according to the dictionary ``dic``.
936
937
EXAMPLES::
938
939
sage: from admcycles import TautologicalRing
940
941
sage: R = TautologicalRing(1, 2)
942
sage: cl = R.psi(1) * R.psi(2)
943
sage: cl.rename_legs({2:5}, inplace=False)
944
Graph : [1] [[1, 5]] []
945
Polynomial : psi_1*psi_5
946
sage: parent(cl.rename_legs({2:5}, inplace=False))
947
TautologicalRing(g=1, n=(1, 5), moduli='st') over Rational Field
948
949
Note that inplace operation is forbidden if the relabellings of the markings
950
is not a bijection::
951
952
sage: _ = cl.rename_legs({2:5}, inplace=True)
953
Traceback (most recent call last):
954
...
955
ValueError: invalid inplace operation: change of markings (1, 2) -> (1, 5)
956
957
TESTS::
958
959
sage: from admcycles import *
960
sage: G1 = StableGraph([1,1],[[1,2,4],[3,5]],[(4,5)])
961
sage: A = G1.to_tautological_class()
962
sage: _ = A.rename_legs({1:3,3:1})
963
sage: list(A._terms)
964
[[1, 1] [[2, 3, 4], [1, 5]] [(4, 5)]]
965
sage: for g, t in A._terms.items():
966
....: assert g == t.gamma
967
"""
968
if rename is not None:
969
from .superseded import deprecation
970
deprecation(109, 'the rename keyword in TautologicalClass.rename_legs is deprecated. Please set rename=None')
971
972
P = self.parent()
973
clean_dic = {i: dic.get(i, i) for i in P._markings}
974
new_markings = tuple(sorted(clean_dic.values()))
975
if new_markings != P._markings:
976
if inplace:
977
raise ValueError(
978
'invalid inplace operation: change of markings {} -> {}'.format(P._markings, new_markings))
979
R = TautologicalRing(P._g, new_markings, P._moduli, base_ring=P.base_ring())
980
else:
981
R = P
982
983
if inplace:
984
l = list(self._terms.values())
985
self._terms.clear()
986
for t in l:
987
t.rename_legs(dic, inplace=True)
988
self._terms[t.gamma] = t
989
return self
990
else:
991
return R([t.rename_legs(dic, inplace=False) for t in self._terms.values()])
992
993
def permutation_action(self, g):
994
r"""
995
Applies the permutation ``g`` to the markings.
996
997
INPUT:
998
999
g: permutation in the symmetric group `S_n`
1000
1001
EXAMPLES::
1002
1003
sage: from admcycles import TautologicalRing
1004
sage: R = TautologicalRing(1, 2)
1005
sage: G = SymmetricGroup(2)
1006
sage: g = G('(1,2)')
1007
sage: R.psi(1).permutation_action(g) == R.psi(2)
1008
True
1009
"""
1010
return self.rename_legs(g.dict(), inplace=False)
1011
1012
def is_symmetric(self, G=None):
1013
r"""
1014
Return whether this tautological class is symmetric under the action of G.
1015
1016
INPUT:
1017
1018
G: (optional) subgroup of symmetric group
1019
if no argument is given take Sn
1020
1021
EXAMPLES::
1022
1023
sage: from admcycles import TautologicalRing
1024
sage: R = TautologicalRing(1, 2)
1025
sage: (R.psi(1) * R.psi(2)).is_symmetric()
1026
True
1027
sage: R.psi(1).is_symmetric() # psi(1) = psi(2) in H^*(M_{1,2})
1028
True
1029
1030
sage: R = TautologicalRing(2, 2)
1031
sage: R.psi(1).is_symmetric()
1032
False
1033
1034
sage: from admcycles import TautologicalRing
1035
sage: R = TautologicalRing(2, 3)
1036
sage: G = PermutationGroup([(1,2)])
1037
sage: a = R.psi(1) + R.psi(2)
1038
sage: a.is_symmetric(G)
1039
True
1040
sage: a.is_symmetric()
1041
False
1042
"""
1043
if not self._terms: # if tautclass is 0, return True
1044
return True
1045
num_leg = self.parent()._n
1046
if G is None:
1047
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
1048
G = SymmetricGroup(num_leg)
1049
for g in G.gens():
1050
if self.permutation_action(g) != self:
1051
return False
1052
return True
1053
1054
def symmetrize(self, G=None):
1055
r"""
1056
Return the symmetrization of self under the action of ``G``.
1057
1058
INPUT:
1059
1060
G: (optional) subgroup of symmetric group
1061
if no argument is given take Sn
1062
1063
EXAMPLES::
1064
1065
sage: from admcycles import TautologicalRing
1066
sage: R = TautologicalRing(2, 2)
1067
sage: R.psi(1).symmetrize()
1068
Graph : [2] [[1, 2]] []
1069
Polynomial : 1/2*psi_1 + 1/2*psi_2
1070
"""
1071
P = self.parent()
1072
res = P.zero()
1073
if not self._terms: # if tautclass is 0, return 0
1074
return res
1075
if G is None:
1076
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
1077
G = SymmetricGroup(P._n)
1078
1079
return P.sum(self.permutation_action(g) for g in G) / G.cardinality()
1080
1081
def standard_markings(self):
1082
r"""
1083
Return an isomorphic tautological class where standard markings `{1, 2, ..., n}`
1084
are used.
1085
1086
EXAMPLES::
1087
1088
sage: from admcycles import StableGraph, TautologicalRing
1089
sage: R = TautologicalRing(4, [4,7])
1090
sage: g = StableGraph([3], [[4,7,1,2]], [(1,2)])
1091
sage: a = R(g, psi={4:1}) + R(g, psi={7:2}) + R(g, kappa=[[0,0,1]])
1092
sage: a.standard_markings()
1093
Graph : [3] [[1, 2, 4, 7]] [(4, 7)]
1094
Polynomial : psi_1 + psi_2^2 + (kappa_3)_0
1095
sage: a.standard_markings().parent()
1096
TautologicalRing(g=4, n=2, moduli='st') over Rational Field
1097
"""
1098
P = self.parent()
1099
if P._markings and P._markings[-1] == P._n:
1100
return self
1101
return self.rename_legs({j: i + 1 for i, j in enumerate(P._markings)}, inplace=False)
1102
1103
def vector(self, r=None):
1104
r"""
1105
EXAMPLES:
1106
1107
We consider the special case of the moduli space ``g=1`` and ``n=2``::
1108
1109
sage: from admcycles import TautologicalRing, StableGraph
1110
sage: R = TautologicalRing(1, 2)
1111
1112
Degree zero::
1113
1114
sage: R(2/3).vector()
1115
(2/3)
1116
sage: R.from_vector([2/3], 0)
1117
Graph : [1] [[1, 2]] []
1118
Polynomial : 2/3
1119
1120
Degree one examples::
1121
1122
sage: kappa1 = R.kappa(1)
1123
sage: kappa1.vector()
1124
(1, 0, 0, 0, 0)
1125
sage: R.from_vector([1,0,0,0,0], 1)
1126
Graph : [1] [[1, 2]] []
1127
Polynomial : (kappa_1)_0
1128
1129
sage: psi1 = R.psi(1)
1130
sage: psi1.vector()
1131
(0, 1, 0, 0, 0)
1132
sage: gamma2 = StableGraph([0], [[1,2,3,4]], [(3,4)])
1133
sage: R(gamma2).vector()
1134
(0, 0, 0, 0, 1)
1135
1136
Some degree two examples::
1137
1138
sage: kappa2 = R.kappa(2)
1139
sage: kappa2.vector()
1140
(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
1141
sage: (kappa1*psi1).vector()
1142
(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
1143
sage: R(gamma2, kappa=[[1],[]]).vector()
1144
(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0)
1145
sage: gamma3 = StableGraph([0,0], [[1,2,3],[4,5,6]], [(3,4),(5,6)])
1146
sage: R(gamma3).vector()
1147
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)
1148
1149
Empty class::
1150
1151
sage: R(0).vector(1)
1152
(0, 0, 0, 0, 0)
1153
1154
Non-standard markings::
1155
1156
sage: R = TautologicalRing(1, [4, 7])
1157
sage: R.psi(4).vector()
1158
(0, 1, 0, 0, 0)
1159
sage: R.from_vector((0, 1, 0, 0, 0), 1)
1160
Graph : [1] [[4, 7]] []
1161
Polynomial : psi_4
1162
1163
sage: TautologicalRing(1, 1).zero().vector()
1164
Traceback (most recent call last):
1165
...
1166
ValueError: specify degree to obtain vector of empty class
1167
1168
TESTS:
1169
1170
Non-standard moduli::
1171
1172
sage: R = TautologicalRing(2,2,moduli='ct')
1173
sage: all(u.vector() == vector(QQ,23,{i:1}) for i,u in enumerate(R.generators(2)))
1174
True
1175
"""
1176
self = self.standard_markings()
1177
P = self.parent()
1178
if self.is_empty():
1179
if r is None:
1180
raise ValueError('specify degree to obtain vector of empty class')
1181
return vector(QQ, P.num_gens(r))
1182
if r is None:
1183
# assume it is homogeneous
1184
r = self.degree_list()
1185
if len(r) != 1:
1186
raise ValueError('for non-homogeneous term, set r to the desired degree')
1187
r = r[0]
1188
from .admcycles import converttoTautvect
1189
return sum(converttoTautvect(term, P._g, P._n, r, P._moduli) for term in self._terms.values())
1190
1191
def basis_vector(self, r=None, moduli=None):
1192
r"""
1193
Return a vector expressing the class in the basis of the tautological ring.
1194
1195
The corresponding basis is obtained from
1196
:meth:~`TautologicalRing.basis`.
1197
1198
EXAMPLES::
1199
1200
sage: from admcycles import TautologicalRing, StableGraph
1201
sage: R = TautologicalRing(2, 1)
1202
sage: gamma = StableGraph([1],[[1,2,3]],[(2,3)])
1203
sage: b = R(gamma)
1204
sage: b.basis_vector()
1205
(10, -10, -14)
1206
1207
sage: R.from_basis_vector((10, -10, -14), 1) == b
1208
True
1209
1210
sage: Rct = TautologicalRing(2, 1, moduli='ct')
1211
sage: Rct(b).basis_vector(1)
1212
(0, 0)
1213
sage: Rct(b).basis_vector(1,moduli='tl')
1214
Traceback (most recent call last):
1215
...
1216
ValueError: moduli='tl' is larger than the moduli 'ct' of the parent
1217
1218
sage: R = TautologicalRing(2, 2)
1219
sage: c = R.psi(1)**2
1220
sage: for moduli in ('st', 'tl', 'ct', 'rt'):
1221
....: Rmod = TautologicalRing(2, 2, moduli=moduli)
1222
....: print(Rmod(c).basis_vector())
1223
(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
1224
(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)
1225
(5/6, -1/6, 1/3, 0, 0)
1226
(1)
1227
sage: Rsm = TautologicalRing(2, 2, moduli='sm')
1228
sage: Rsm(c).basis_vector(2)
1229
()
1230
1231
Compatibility test with non-standard markings::
1232
1233
sage: R1 = TautologicalRing(1, 2)
1234
sage: g1 = StableGraph([0], [[1,2,3,4]], [(3,4)])
1235
sage: a1 = R1(g1, psi={1:1}) + R1(g1, psi={2:2}) + R1(g1, kappa=[[0,0,1]])
1236
1237
sage: R2 = TautologicalRing(1, [4,7])
1238
sage: g2 = StableGraph([0], [[4,7,1,2]], [(1,2)])
1239
sage: a2 = R2(g2, psi={4:1}) + R2(g2, psi={7:2}) + R2(g2, kappa=[[0,0,1]])
1240
1241
sage: a1.basis_vector(0) == a2.basis_vector(0)
1242
True
1243
sage: a1.basis_vector(1) == a2.basis_vector(1)
1244
True
1245
sage: a1.basis_vector(2) == a2.basis_vector(2)
1246
True
1247
sage: a1.basis_vector(3) == a2.basis_vector(3)
1248
True
1249
"""
1250
self = self.standard_markings()
1251
P = self.parent()
1252
moduli = get_moduli(moduli, P._moduli)
1253
if moduli > P._moduli:
1254
raise ValueError("moduli={!r} is larger than the moduli {!r} of the parent".format(
1255
_moduli_to_str[moduli], _moduli_to_str[P._moduli]))
1256
1257
if r is None:
1258
r = self.degree_list()
1259
if len(r) != 1:
1260
raise ValueError('for non-homogeneous term, set r to the desired degree')
1261
r = r[0]
1262
1263
if moduli != P._moduli:
1264
P = self.parent()
1265
R = TautologicalRing(P._g, P._markings, moduli, base_ring=P.base_ring())
1266
return R(self).basis_vector(r)
1267
1268
P = self.parent()
1269
from .admcycles import Tautvecttobasis
1270
# TODO: Tautvecttobasis uses the 'string' version of moduli
1271
return Tautvecttobasis(self.vector(r), P._g, P._n, r, _moduli_to_str[P._moduli])
1272
1273
###########################################################################
1274
# Deprecated methods from the former tautclass #
1275
# See https://gitlab.com/modulispaces/admcycles/-/merge_requests/109 #
1276
###########################################################################
1277
def toTautvect(self, g=None, n=None, r=None):
1278
from .superseded import deprecation
1279
deprecation(109, 'toTautvect is deprecated. Please use vector instead.')
1280
P = self.parent()
1281
if (g is not None and g != P._g) or (n is not None and n != P._n):
1282
raise ValueError('invalid g,n')
1283
return self.vector(r)
1284
1285
def toTautbasis(self, g=None, n=None, r=None, moduli='st'):
1286
from .superseded import deprecation
1287
deprecation(109, 'toTautbasis is deprecated. Please use basis_vector instead.')
1288
P = self.parent()
1289
if (g is not None and g != P._g) or (n is not None and n != P._n):
1290
raise ValueError('invalid g,n')
1291
if _str_to_moduli[moduli] != P._moduli:
1292
P = TautologicalRing(P._g, P._n, moduli, base_ring=P.base_ring())
1293
self = P(self)
1294
return self.basis_vector(r)
1295
1296
def gnr_list(self):
1297
from .superseded import deprecation
1298
deprecation(109, 'gnr_list is deprecated. Please use degree_list instead.')
1299
P = self.parent()
1300
return [(P._g, P._n, d) for d in self.degree_list()]
1301
1302
def coeff_subs(self, *args, **kwds):
1303
from .superseded import deprecation
1304
deprecation(109, "coeff_subs is deprecated. Please use subs instead.")
1305
result = self.subs(*args, **kwds)
1306
self._terms = result._terms
1307
return self
1308
1309
1310
class TautologicalRing(UniqueRepresentation, Algebra):
1311
r"""
1312
The tautological subgring of the (even degree) cohomology of the moduli space of curves.
1313
1314
EXAMPLES::
1315
1316
sage: from admcycles import TautologicalRing
1317
sage: TautologicalRing(2)
1318
TautologicalRing(g=2, n=0, moduli='st') over Rational Field
1319
sage: TautologicalRing(1, 2)
1320
TautologicalRing(g=1, n=2, moduli='st') over Rational Field
1321
sage: TautologicalRing(1, 1, 'tl')
1322
TautologicalRing(g=1, n=1, moduli='tl') over Rational Field
1323
1324
sage: from admcycles.moduli import MODULI_ST
1325
sage: TautologicalRing(0, 5, MODULI_ST, QQ)
1326
TautologicalRing(g=0, n=5, moduli='st') over Rational Field
1327
1328
Since tautological rings are commutative algebras one can use them as basic building
1329
blocks of further algebraic constructors in SageMath::
1330
1331
sage: R = TautologicalRing(2, 0)
1332
1333
sage: Rxy.<x,y> = PolynomialRing(R, ['x', 'y'])
1334
sage: Rxy
1335
Multivariate Polynomial Ring in x, y over TautologicalRing(g=2, n=0, moduli='st') over Rational Field
1336
sage: R.kappa(1) * x + R.kappa(2) * y # TODO: horrible display
1337
(Graph : [2] [[]] []
1338
Polynomial : (kappa_1)_0)*x + (Graph : [2] [[]] []
1339
Polynomial : (kappa_2)_0)*y
1340
1341
sage: V = FreeModule(R, 2)
1342
sage: V
1343
Ambient free module of rank 2 over TautologicalRing(g=2, n=0, moduli='st') over Rational Field
1344
sage: V((R.kappa(1), R.kappa(2))) # TODO: horrible display
1345
(Graph : [2] [[]] []
1346
Polynomial : (kappa_1)_0, Graph : [2] [[]] []
1347
Polynomial : (kappa_2)_0)
1348
1349
TESTS::
1350
1351
sage: from admcycles import TautologicalRing
1352
sage: TestSuite(TautologicalRing(1, 1)).run()
1353
sage: TestSuite(TautologicalRing(1, [3])).run()
1354
sage: TestSuite(TautologicalRing(1, 1, 'tl', QQ['x,y'])).run()
1355
1356
sage: cm = get_coercion_model()
1357
sage: cm.get_action(QQ, TautologicalRing(2,0))
1358
Left scalar multiplication by Rational Field on TautologicalRing(g=2, n=0, moduli='st') over Rational Field
1359
1360
Test for https://gitlab.com/modulispaces/admcycles/-/issues/70::
1361
1362
sage: from admcycles import TautologicalRing
1363
sage: W = TautologicalRing(1,1)
1364
sage: V = PolynomialRing(W, 'x,y')
1365
sage: V.one()
1366
Graph : [1] [[1]] []
1367
Polynomial : 1
1368
1369
Test for https://gitlab.com/modulispaces/admcycles/-/issues/79::
1370
1371
sage: from admcycles import TautologicalRing
1372
sage: TautologicalRing(2, 0).gens()
1373
(...)
1374
"""
1375
1376
Element = TautologicalClass
1377
1378
@staticmethod
1379
def __classcall__(cls, *args, **kwds):
1380
g = kwds.pop('g', None)
1381
n = kwds.pop('n', None)
1382
moduli = kwds.pop('moduli', None)
1383
base_ring = kwds.pop('base_ring', None)
1384
if kwds:
1385
raise ValueError('unknown arguments {}'.format(list(kwds.keys())))
1386
if len(args) >= 1:
1387
if g is not None:
1388
raise ValueError('genus g specified twice')
1389
g = args[0]
1390
if len(args) >= 2:
1391
# g and n
1392
if n is not None:
1393
raise ValueError('number of marked points n specified twice')
1394
n = args[1]
1395
if len(args) >= 3:
1396
if moduli is not None:
1397
raise ValueError('moduli specified twice')
1398
moduli = args[2]
1399
if len(args) >= 4:
1400
if base_ring is not None:
1401
raise ValueError('base_ring specified twice')
1402
base_ring = args[3]
1403
if len(args) > 4:
1404
raise ValueError('too many arguments for TautologicalRing: {}'.format(args))
1405
1406
moduli = get_moduli(moduli)
1407
1408
if not isinstance(g, numbers.Integral) or g < 0:
1409
raise ValueError('g must be a non-negative integer')
1410
g = ZZ(g)
1411
if n is None:
1412
n = ZZ.zero()
1413
markings = ()
1414
elif isinstance(n, (tuple, list)):
1415
markings = [ZZ(i) for i in sorted(n)]
1416
n = len(markings)
1417
markings.sort()
1418
if markings and markings[0] <= 0:
1419
raise ValueError('markings must be positive integers')
1420
if any(markings[i] == markings[i + 1] for i in range(n - 1)):
1421
raise ValueError('repeated marking')
1422
markings = tuple(markings)
1423
elif isinstance(n, numbers.Integral):
1424
if n < 0:
1425
raise ValueError('n must be a non-negative integer')
1426
n = ZZ(n)
1427
markings = tuple(range(1, n + 1))
1428
else:
1429
raise TypeError('invalid input')
1430
1431
if base_ring is None:
1432
base_ring = QQ
1433
elif base_ring not in _CommutativeRings:
1434
raise ValueError('base_ring (={}) must be a commutative ring'.format(base_ring))
1435
1436
if (g, n) in [(0, 0), (0, 1), (0, 2), (1, 0)]:
1437
raise ValueError('unstable pair (g,n) = ({}, {})'.format(g, n))
1438
1439
return super().__classcall__(cls, g, markings, moduli, base_ring)
1440
1441
def __init__(self, g, markings, moduli, base_ring):
1442
r"""
1443
INPUT:
1444
1445
g : integer
1446
genus
1447
markings : tuple of distinct positive integers
1448
list of markings
1449
moduli : integer
1450
code for the moduli
1451
base_ring : ring
1452
base ring
1453
"""
1454
Algebra.__init__(self, base_ring, category=Algebras(base_ring).Commutative().FiniteDimensional())
1455
self._g = g
1456
self._markings = markings
1457
self._n = len(markings)
1458
self._moduli = moduli
1459
1460
# NOTE: even though the category contains the axiom Commutative(), is_commutative is reimplemented in sage.rings.ring.Ring
1461
# see https://gitlab.com/modulispaces/admcycles/-/issues/70
1462
# see https://trac.sagemath.org/ticket/32810
1463
def is_commutative(self):
1464
return True
1465
1466
@cached_method
1467
def standard_markings(self):
1468
r"""
1469
Return an equivalent moduli space with the standard markings `{1, 2, \ldots, n}`.
1470
1471
EXAMPLES::
1472
1473
sage: from admcycles import TautologicalRing
1474
sage: R = TautologicalRing(2, [4, 7])
1475
sage: R
1476
TautologicalRing(g=2, n=(4, 7), moduli='st') over Rational Field
1477
sage: R.standard_markings()
1478
TautologicalRing(g=2, n=2, moduli='st') over Rational Field
1479
"""
1480
if not self._markings or self._markings[-1] == self._n:
1481
return self
1482
return TautologicalRing(self._g, self._n, moduli=self._moduli, base_ring=self.base_ring())
1483
1484
def is_integral_domain(self):
1485
r"""
1486
Return ``False`` unless the tautological ring is supported in degree 0 and the base ring
1487
is an integral domain.
1488
1489
This uses that any element of positive order is torsion.
1490
1491
EXAMPLES::
1492
1493
sage: from admcycles import TautologicalRing
1494
sage: TautologicalRing(1,3,moduli='ct').socle_degree()
1495
2
1496
sage: TautologicalRing(1,3,moduli='ct').is_integral_domain()
1497
False
1498
sage: TautologicalRing(0,3).is_integral_domain()
1499
True
1500
sage: TautologicalRing(0,3,base_ring=ZZ.quotient(8)).is_integral_domain()
1501
False
1502
"""
1503
return self.socle_degree() == 0 and self.base_ring().is_integral_domain()
1504
1505
def is_field(self):
1506
r"""
1507
Return ``False`` unless the tautological ring is supported in degree 0 and the base ring
1508
is a field.
1509
1510
This uses that any element of positive order is torsion.
1511
1512
EXAMPLES::
1513
1514
sage: from admcycles import TautologicalRing
1515
sage: TautologicalRing(1,3,moduli='ct').socle_degree()
1516
2
1517
sage: TautologicalRing(1,3,moduli='ct').is_field()
1518
False
1519
sage: TautologicalRing(0,3).is_field()
1520
True
1521
sage: TautologicalRing(0,3,base_ring=ZZ.quotient(5)).is_field()
1522
True
1523
"""
1524
return self.socle_degree() == 0 and self.base_ring().is_field()
1525
1526
def is_prime_field(self):
1527
r"""
1528
Return ``False`` unless the tautological ring is supported in degree 0 and the base ring
1529
is a prime field.
1530
1531
This uses that any element of positive order is torsion.
1532
1533
EXAMPLES::
1534
1535
sage: from admcycles import TautologicalRing
1536
sage: TautologicalRing(1,3,moduli='ct', base_ring=GF(5)).is_prime_field()
1537
False
1538
sage: TautologicalRing(0,3,base_ring=QQ).is_prime_field()
1539
True
1540
sage: TautologicalRing(0,3,base_ring=GF(5)).is_prime_field()
1541
True
1542
"""
1543
return self.socle_degree() == 0 and self.base_ring().is_prime_field()
1544
1545
def _coerce_map_from_(self, other):
1546
r"""
1547
TESTS::
1548
1549
sage: from admcycles import TautologicalRing
1550
sage: TautologicalRing(1, 1, base_ring=QQ['x','y']).has_coerce_map_from(TautologicalRing(1, 1, base_ring=QQ))
1551
True
1552
1553
sage: M11st = TautologicalRing(1, 1, moduli='st')
1554
sage: M11ct = TautologicalRing(1, 1, moduli='ct')
1555
sage: M11st.has_coerce_map_from(M11ct)
1556
False
1557
sage: M11ct.has_coerce_map_from(M11st)
1558
True
1559
"""
1560
if isinstance(other, TautologicalRing) and \
1561
self._g == other._g and \
1562
self._markings == other._markings and \
1563
self._moduli <= other._moduli and \
1564
self.base_ring().has_coerce_map_from(other.base_ring()):
1565
return True
1566
1567
def construction(self):
1568
r"""
1569
Return a functorial construction (when applied to a base ring).
1570
1571
This function is mostly intended to make the tautological ring behave nicely
1572
with Sage ecosystem.
1573
1574
EXAMPLES::
1575
1576
sage: from admcycles import TautologicalRing
1577
sage: R = TautologicalRing(1, 1)
1578
sage: QQ['x'].gen() * R.psi(1) # indirect doctest
1579
Graph : [1] [[1]] []
1580
Polynomial : x*psi_1
1581
1582
sage: A = TautologicalRing(3, 2, moduli='st', base_ring=QQ['x'])
1583
sage: B = TautologicalRing(3, 2, moduli='tl', base_ring=QQ)
1584
sage: A.psi(1) + B.psi(2) # indirect doctest
1585
Graph : [3] [[1, 2]] []
1586
Polynomial : psi_1 + psi_2
1587
sage: (A.psi(1) + B.psi(2)).parent()
1588
TautologicalRing(g=3, n=2, moduli='tl') over Univariate Polynomial Ring in x over Rational Field
1589
"""
1590
return TautologicalRingFunctor(self._g, self._markings, self._moduli), self.base_ring()
1591
1592
def _repr_(self):
1593
if self._markings == tuple(range(1, self._n + 1)):
1594
return 'TautologicalRing(g={}, n={}, moduli={!r}) over {}'.format(self._g, self._n, _moduli_to_str[self._moduli], self.base_ring())
1595
else:
1596
return 'TautologicalRing(g={}, n={}, moduli={!r}) over {}'.format(self._g, self._markings, _moduli_to_str[self._moduli], self.base_ring())
1597
1598
def socle_degree(self):
1599
r"""
1600
Return the socle degree of this tautological ring.
1601
1602
The socle degree is the maximal degree whose corresponding graded
1603
component is non-empty. It is currently not implemented for the
1604
moduli of tree like curves.
1605
1606
The formulas were computed in:
1607
1608
- [GrVa01]_, [GrVa05]_ and [FaPa05]_ for compact type
1609
- [Lo95]_, [Fa99]_ and [FaPa00]_ for rational tail
1610
- [Lo95]_, [Io02]_ for smooth curves
1611
1612
EXAMPLES::
1613
1614
sage: from admcycles import TautologicalRing
1615
1616
We display below the socle degree for various `(g, n)` for the moduli of
1617
smooth curves, rational tails, compact types and stable curves::
1618
1619
sage: for (g,n) in [(0,3), (0,4), (0, 5), (1,1), (1,2), (2,0), (2, 1)]:
1620
....: dims = []
1621
....: for moduli in ['sm', 'rt', 'ct', 'st']:
1622
....: R = TautologicalRing(g, n, moduli=moduli)
1623
....: dims.append(R.socle_degree())
1624
....: print("g={} n={}: {}".format(g, n, " ".join(map(str, dims))))
1625
g=0 n=3: 0 0 0 0
1626
g=0 n=4: 0 1 1 1
1627
g=0 n=5: 0 2 2 2
1628
g=1 n=1: 0 0 0 1
1629
g=1 n=2: 0 1 1 2
1630
g=2 n=0: 0 0 1 3
1631
g=2 n=1: 1 1 2 4
1632
1633
The socle degree is overestimated for tree-like::
1634
1635
sage: R = TautologicalRing(2, moduli='tl')
1636
sage: R.socle_degree()
1637
3
1638
sage: R.kappa(3).is_zero() # generator for \bar M_2
1639
True
1640
"""
1641
return socle_degree(self._g, self._n, self._moduli)
1642
1643
@cached_method
1644
def trivial_graph(self):
1645
r"""
1646
Return the stable graph corresponding to the full stratum.
1647
1648
EXAMPLES::
1649
1650
sage: from admcycles import TautologicalRing
1651
sage: TautologicalRing(2, [1, 3, 4]).trivial_graph()
1652
[2] [[1, 3, 4]] []
1653
"""
1654
return StableGraph([self._g], [list(self._markings)], [], mutable=False)
1655
1656
def _an_element_(self):
1657
return self.one()
1658
1659
def some_elements(self):
1660
r"""
1661
Return some elements in this tautological ring.
1662
1663
This is mostly used for sage test system.
1664
1665
EXAMPLES::
1666
1667
sage: from admcycles import TautologicalRing
1668
sage: _ = TautologicalRing(0, [4, 5, 7]).some_elements()
1669
sage: _ = TautologicalRing(2, [4, 7]).some_elements()
1670
"""
1671
base_elts = [self(s) for s in self.base_ring().some_elements()]
1672
elts = base_elts[:2] + base_elts[-2:]
1673
elts.append(self.irreducible_boundary_divisor())
1674
if self._markings:
1675
elts.append(self.psi(self._markings[0]))
1676
if self._g > 0:
1677
elts.append(self.kappa(1))
1678
return elts
1679
1680
# TODO: if immutable, we could cache the method
1681
# @cached_method
1682
def zero(self):
1683
r"""
1684
Return the zero element.
1685
1686
EXAMPLES::
1687
1688
sage: from admcycles import TautologicalRing
1689
sage: TautologicalRing(0, 4).zero()
1690
0
1691
"""
1692
return self.element_class(self, [])
1693
1694
def sum(self, classes):
1695
r"""
1696
Return the sum of ``classes``.
1697
1698
EXAMPLES::
1699
1700
sage: from admcycles import TautologicalRing, psiclass
1701
sage: R = TautologicalRing(1, 2)
1702
sage: R.sum([R(1),R(1),R(0),R(1)])
1703
Graph : [1] [[1, 2]] []
1704
Polynomial : 3
1705
sage: R.sum([psiclass(1,1,2),psiclass(2,1,2)]) == psiclass(1,1,2) + psiclass(2,1,2)
1706
True
1707
sage: R.sum([1,1])
1708
Graph : [1] [[1, 2]] []
1709
Polynomial : 2
1710
1711
Your elements must be coercible into the tautological ring::
1712
1713
sage: R.sum(['a', 'b', 'c'])
1714
Traceback (most recent call last):
1715
...
1716
TypeError: no canonical coercion from <... 'str'> to TautologicalRing(g=1, n=2, moduli='st') over Rational Field
1717
"""
1718
result = self.zero()
1719
for t in classes:
1720
t = self.coerce(t)
1721
for g, term in t._terms.items():
1722
# TODO: implement a sum for list of KappaPsiPolynomial
1723
if g in result._terms:
1724
result._terms[g].poly += term.poly
1725
if not result._terms[g]:
1726
del result._terms[g]
1727
else:
1728
result._terms[g] = term.copy()
1729
return result
1730
1731
# TODO: if immutable, we could cache the method
1732
# @cached_method
1733
def fundamental_class(self):
1734
r"""
1735
Return the fundamental class as a cohomology class.
1736
1737
The fundamental class is the unit of the ring.
1738
1739
EXAMPLES::
1740
1741
sage: from admcycles import TautologicalRing
1742
sage: R = TautologicalRing(2, 1)
1743
sage: R.fundamental_class()
1744
Graph : [2] [[1]] []
1745
Polynomial : 1
1746
1747
sage: R.fundamental_class() ** 2 == R.fundamental_class()
1748
True
1749
"""
1750
return self(self.trivial_graph())
1751
1752
one = fundamental_class
1753
1754
def psi(self, i):
1755
r"""
1756
Return the class `\psi_i` on `\bar M_{g,n}`.
1757
1758
Alternatively, you could use the function :func:`~admcycles.admcycles.psiclass`.
1759
1760
INPUT:
1761
1762
i : integer
1763
the leg number associated to the psi class
1764
1765
EXAMPLES::
1766
1767
sage: from admcycles import TautologicalRing, StableGraph
1768
1769
sage: R = TautologicalRing(2, 3)
1770
sage: R.psi(2)
1771
Graph : [2] [[1, 2, 3]] []
1772
Polynomial : psi_2
1773
sage: R.psi(3)
1774
Graph : [2] [[1, 2, 3]] []
1775
Polynomial : psi_3
1776
1777
sage: R = TautologicalRing(3, 2)
1778
sage: R.psi(1)
1779
Graph : [3] [[1, 2]] []
1780
Polynomial : psi_1
1781
1782
TESTS::
1783
1784
sage: from admcycles import TautologicalRing
1785
1786
sage: R = TautologicalRing(3, 2)
1787
sage: R.psi(3)
1788
Traceback (most recent call last):
1789
...
1790
ValueError: unknown marking 3 for psi
1791
"""
1792
return self(self.trivial_graph(), psi={i: 1})
1793
1794
def kappa(self, a):
1795
r"""
1796
Return the (Arbarello-Cornalba) kappa-class `\kappa_a` on `\bar M_{g,n}` defined by
1797
1798
`\kappa_a= \pi_*(\psi_{n+1}^{a+1})`
1799
1800
where `pi` is the morphism `\bar M_{g,n+1} \to \bar M_{g,n}`.
1801
1802
INPUT:
1803
1804
a : integer
1805
the degree a of the kappa class
1806
1807
EXAMPLES::
1808
1809
sage: from admcycles import TautologicalRing
1810
1811
sage: R = TautologicalRing(3, 1)
1812
sage: R.kappa(2)
1813
Graph : [3] [[1]] []
1814
Polynomial : (kappa_2)_0
1815
1816
sage: R = TautologicalRing(3, 2)
1817
sage: R.kappa(1)
1818
Graph : [3] [[1, 2]] []
1819
Polynomial : (kappa_1)_0
1820
"""
1821
if a == 0:
1822
return (2 * self._g - 2 + self._n) * self.one()
1823
elif a < 0:
1824
return self.zero()
1825
else:
1826
return self(self.trivial_graph(), kappa=[[0] * (a - 1) + [1]])
1827
1828
def lambdaclass(self, d, pull=True):
1829
r"""
1830
Return the tautological class `\lambda_d` on `\bar M_{g,n}`.
1831
1832
The `\lambda_d` class is defined as the d-th Chern class
1833
1834
`\lambda_d = c_d(E)`
1835
1836
of the Hodge bundle `E`. The result is represented as a sum of stable
1837
graphs with kappa and psi classes.
1838
1839
INPUT:
1840
1841
d : integer
1842
the degree
1843
pull : boolean (optional, default to ``True``)
1844
whether the class is computed as pullback from `\bar M_{g}`
1845
1846
EXAMPLES::
1847
1848
sage: from admcycles import TautologicalRing
1849
1850
sage: R = TautologicalRing(2, 0)
1851
sage: R.lambdaclass(1)
1852
Graph : [2] [[]] []
1853
Polynomial : 1/12*(kappa_1)_0
1854
<BLANKLINE>
1855
Graph : [1] [[2, 3]] [(2, 3)]
1856
Polynomial : 1/24
1857
<BLANKLINE>
1858
Graph : [1, 1] [[2], [3]] [(2, 3)]
1859
Polynomial : 1/24
1860
1861
sage: R = TautologicalRing(1, 1)
1862
sage: R.lambdaclass(1)
1863
Graph : [1] [[1]] []
1864
Polynomial : 1/12*(kappa_1)_0 - 1/12*psi_1
1865
<BLANKLINE>
1866
Graph : [0] [[3, 4, 1]] [(3, 4)]
1867
Polynomial : 1/24
1868
1869
TESTS::
1870
1871
sage: from admcycles import lambdaclass
1872
sage: inputs = [(0,0,4), (1,1,3), (1,2,1), (2,2,1), (3,2,1), (-1,2,1), (2,3,2)]
1873
sage: for d,g,n in inputs:
1874
....: R = TautologicalRing(g, n)
1875
....: assert (R.lambdaclass(d) - R.lambdaclass(d,pull=False)).is_zero()
1876
1877
Check for https://gitlab.com/modulispaces/admcycles/issues/58::
1878
1879
sage: R = TautologicalRing(4,0)
1880
sage: L = R.lambdaclass(4)
1881
sage: L == R.double_ramification_cycle(())
1882
True
1883
sage: L.basis_vector()
1884
(-229/4, 28/3, 55/24, -13/12, 1/24, 0, 0, 0, 0, 0, 0, 0, -1/2, 1/6, 1, -1/3, -1/3, 2/3, 2/3, -4/3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4/3, 1/3, -5/3, 20/3, 16/9, -4/3, 2/9, 1/6, 0, 0, 0, 0, 0, 0, 0, 0, 0)
1885
"""
1886
g = self._g
1887
n = self._n
1888
if d > g or d < 0:
1889
return self.zero()
1890
1891
if n > 0 and pull:
1892
if g == 0:
1893
return self.fundamental_class()
1894
elif g == 1:
1895
newmarks = list(range(2, n + 1))
1896
R = TautologicalRing(1, 1, moduli=self._moduli)
1897
return R.lambdaclass(d, pull=False).forgetful_pullback(newmarks)
1898
else:
1899
newmarks = list(range(1, n + 1))
1900
R = TautologicalRing(g, 0, moduli=self._moduli)
1901
return R.lambdaclass(d, pull=False).forgetful_pullback(newmarks)
1902
1903
return self.chern_character_to_class(d, self.hodge_chern_character)
1904
1905
def irreducible_boundary_divisor(self):
1906
r"""
1907
Return the pushforward of the fundamental class under the irreducible
1908
boundary gluing map `\bar M_{g-1,n+2} \to \bar M_{g,n}`.
1909
1910
EXAMPLES::
1911
1912
sage: from admcycles import *
1913
1914
sage: R = TautologicalRing(2, 5)
1915
sage: R.irreducible_boundary_divisor()
1916
Graph : [1] [[1, 2, 3, 4, 5, 6, 7]] [(6, 7)]
1917
Polynomial : 1
1918
1919
sage: R = TautologicalRing(3, 0)
1920
sage: R.irreducible_boundary_divisor()
1921
Graph : [2] [[1, 2]] [(1, 2)]
1922
Polynomial : 1
1923
"""
1924
if self._g == 0:
1925
return self.zero()
1926
g = self._g
1927
last = self._markings[-1] if self._markings else 0
1928
G = StableGraph([g - 1], [list(self._markings) + [last + 1, last + 2]], [(last + 1, last + 2)])
1929
return self(G)
1930
1931
# alias
1932
irrbdiv = irreducible_boundary_divisor
1933
1934
def separable_boundary_divisor(self, h, A):
1935
r"""
1936
Return the pushforward of the fundamental class under the boundary
1937
gluing map `\bar M_{h,A} \times \bar M_{g-h,{1,...,n} \ A} \to \bar M_{g,n}`.
1938
1939
INPUT:
1940
1941
h : integer
1942
genus of the first vertex
1943
A : list
1944
list of markings on the first vertex
1945
1946
EXAMPLES::
1947
1948
sage: from admcycles import *
1949
1950
sage: R = TautologicalRing(2, 5)
1951
sage: R.separable_boundary_divisor(1, (1,3,4))
1952
Graph : [1, 1] [[1, 3, 4, 6], [2, 5, 7]] [(6, 7)]
1953
Polynomial : 1
1954
1955
sage: R = TautologicalRing(3, 3)
1956
sage: R.separable_boundary_divisor(1, (2,))
1957
Graph : [1, 2] [[2, 4], [1, 3, 5]] [(4, 5)]
1958
Polynomial : 1
1959
1960
sage: R = TautologicalRing(2, [4, 6])
1961
sage: R.separable_boundary_divisor(1, (6,))
1962
Graph : [1, 1] [[6, 7], [4, 8]] [(7, 8)]
1963
Polynomial : 1
1964
"""
1965
g = self._g
1966
last = self._markings[-1] if self._markings else 1
1967
G = StableGraph([h, g - h], [list(A) + [last + 1], sorted(set(self._markings) -
1968
set(A)) + [last + 2]], [(last + 1, last + 2)])
1969
return self(G)
1970
1971
# alias
1972
sepbdiv = separable_boundary_divisor
1973
1974
# TODO: if immutable, we could cache the result
1975
# (this was wrongly cached before merge request !109)
1976
# @cached_method
1977
def hodge_chern_character(self, d):
1978
r"""
1979
Return the chern character `ch_d(E)` of the Hodge bundle `E` on `\bar
1980
M_{g,n}`.
1981
1982
This function implements the formula from [Mu83]_.
1983
1984
INPUT:
1985
1986
d : integer
1987
The degree of the Chern character.
1988
1989
EXAMPLES::
1990
1991
sage: from admcycles import TautologicalRing
1992
sage: R = TautologicalRing(1, 2)
1993
sage: R.hodge_chern_character(1)
1994
Graph : [1] [[1, 2]] []
1995
Polynomial : 1/12*(kappa_1)_0 - 1/12*psi_1 - 1/12*psi_2
1996
<BLANKLINE>
1997
Graph : [0] [[1, 2, 3, 4]] [(3, 4)]
1998
Polynomial : 1/24
1999
<BLANKLINE>
2000
Graph : [0, 1] [[1, 2, 3], [4]] [(3, 4)]
2001
Polynomial : 1/12
2002
"""
2003
if self._markings and self._markings[-1] != len(self._markings):
2004
raise NotImplementedError('hodge_chern_character unsupported with non-standard markings')
2005
2006
g = self._g
2007
n = self._n
2008
if g == 0:
2009
return self.zero()
2010
from .admcycles import list_strata
2011
bdries = list_strata(g, n, 1)
2012
irrbdry = bdries.pop(0)
2013
2014
d = ZZ(d)
2015
2016
if d == 0:
2017
return g * self.fundamental_class()
2018
elif d % 2 == 0 or d < 0:
2019
return self.zero()
2020
2021
from .admcycles import psicl
2022
psipsisum_onevert = sum(((-1)**i) * (psicl(n + 1, 1)**i) * (psicl(n + 2, 1)**(d - 1 - i)) for i in range(d))
2023
psipsisum_twovert = sum(((-1)**i) * (psicl(n + 1, 2)**i) * (psicl(n + 2, 2)**(d - 1 - i)) for i in range(d))
2024
2025
contrib = self.kappa(d) - sum(self.psi(i)**d for i in range(1, n + 1))
2026
2027
# old: contrib=kappaclass(d,g,n)-sum([psiclass(i,g,n) for i in range(1,n+1)])
2028
contrib += (QQ(1) / 2) * self(irrbdry, poly=psipsisum_onevert)
2029
contrib += sum(QQ((1, ind.automorphism_number())) * self(ind, poly=psipsisum_twovert) for ind in bdries)
2030
2031
contrib.dimension_filter()
2032
2033
return bernoulli(d + 1) / factorial(d + 1) * contrib
2034
2035
# TODO
2036
# The function below is useful in some computations and does not need to know about the parent TautologicalRing
2037
# I think it makes more sense as a standalone function e.g. in admcycles.py
2038
def chern_character_to_class(self, t, char):
2039
r"""
2040
Return the Chern class associated to the Chern character.
2041
2042
INPUT:
2043
2044
t : integer
2045
degree of the output Chern class
2046
char : tautological class or a function
2047
Chern character, either represented as a (mixed-degree) tautological class or as
2048
a function m -> ch_m
2049
2050
EXAMPLES::
2051
2052
sage: from admcycles import TautologicalRing
2053
sage: R = TautologicalRing(1, 1)
2054
sage: R.chern_character_to_class(1, R.hodge_chern_character)
2055
Graph : [1] [[1]] []
2056
Polynomial : 1/12*(kappa_1)_0 - 1/12*psi_1
2057
<BLANKLINE>
2058
Graph : [0] [[3, 4, 1]] [(3, 4)]
2059
Polynomial : 1/24
2060
2061
Note that the output of generalized_hodge_chern takes the form of a chern character::
2062
2063
sage: from admcycles import *
2064
sage: from admcycles.GRRcomp import *
2065
sage: g=2;n=2;l=0;d=[1,-1];a=[[1,[1],-1]]
2066
sage: chern_char_to_class(1,generalized_hodge_chern(l,d,a,1,g,n)) # known bug
2067
Graph : [2] [[1, 2]] []
2068
Polynomial : 1/12*(kappa_1^1 )_0
2069
<BLANKLINE>
2070
Graph : [2] [[1, 2]] []
2071
Polynomial : (-13/12)*psi_1^1
2072
<BLANKLINE>
2073
Graph : [2] [[1, 2]] []
2074
Polynomial : (-1/12)*psi_2^1
2075
<BLANKLINE>
2076
Graph : [0, 2] [[1, 2, 4], [5]] [(4, 5)]
2077
Polynomial : 1/12*
2078
<BLANKLINE>
2079
Graph : [1, 1] [[4], [1, 2, 5]] [(4, 5)]
2080
Polynomial : 1/12*
2081
<BLANKLINE>
2082
Graph : [1, 1] [[2, 4], [1, 5]] [(4, 5)]
2083
Polynomial : 13/12*
2084
<BLANKLINE>
2085
Graph : [1] [[4, 5, 1, 2]] [(4, 5)]
2086
Polynomial : 1/24*
2087
"""
2088
if isinstance(char, TautologicalClass):
2089
arg = [(-1)**(s - 1) * factorial(s - 1) * char.simplified(r=s) for s in range(1, t + 1)]
2090
else:
2091
arg = [(-1)**(s - 1) * factorial(s - 1) * char(s) for s in range(1, t + 1)]
2092
2093
exp = sum(multinomial(s.to_exp()) / factorial(len(s)) * prod(arg[k - 1] for k in s) for s in Partitions(t))
2094
if t == 0:
2095
return exp * self.fundamental_class()
2096
return exp.simplified(r=t)
2097
2098
def _check_stable_graph(self, stg):
2099
if stg.g() != self._g or stg.n() != self._n:
2100
raise ValueError('invalid stable graph (has g={}, n={} instead of g={}, n={})'.format(
2101
stg.g(), stg.n(), self._g, self._n))
2102
markings = tuple(sorted(stg.list_markings()))
2103
if markings != self._markings:
2104
raise ValueError('invalid stable graph (has markings={} instead of {})'.format(markings, self._markings))
2105
2106
def _element_constructor_(self, arg, kappa=None, psi=None, poly=None):
2107
r"""
2108
TESTS::
2109
2110
sage: from admcycles import TautologicalRing, StableGraph
2111
sage: R = TautologicalRing(2,1)
2112
sage: R(2/3)
2113
Graph : [2] [[1]] []
2114
Polynomial : 2/3
2115
2116
From a stable graph with decorations::
2117
2118
sage: g = StableGraph([1], [[1,2,3]], [(2,3)])
2119
sage: R(g)
2120
Graph : [1] [[1, 2, 3]] [(2, 3)]
2121
Polynomial : 1
2122
2123
A stable graph outside of the open set defines by the moduli gives zero::
2124
2125
sage: Rct = TautologicalRing(2, 1, moduli='ct')
2126
sage: Rct(g)
2127
0
2128
2129
From a sequence of decorated strata::
2130
2131
sage: from admcycles.admcycles import decstratum
2132
sage: gg = StableGraph([2], [[1]], [])
2133
sage: arg = [decstratum(g), decstratum(gg, kappa=[[1]])]
2134
sage: R(arg)
2135
Graph : [2] [[1]] []
2136
Polynomial : (kappa_1)_0
2137
<BLANKLINE>
2138
Graph : [1] [[1, 2, 3]] [(2, 3)]
2139
Polynomial : 1
2140
2141
Empty argument::
2142
2143
sage: from admcycles import TautologicalRing
2144
sage: TautologicalRing(1, 1)([])
2145
0
2146
"""
2147
if isinstance(arg, TautologicalClass):
2148
P = arg.parent()
2149
if P._g != self._g or P._n != self._n:
2150
raise ValueError('incompatible moduli spaces')
2151
return self.element_class(self, {g: term.copy() for g, term in arg._terms.items()})
2152
elif not arg:
2153
return self.zero()
2154
elif isinstance(arg, StableGraph):
2155
self._check_stable_graph(arg)
2156
if arg.vanishes(self._moduli):
2157
return self.zero()
2158
if psi is not None:
2159
if not isinstance(psi, dict):
2160
raise ValueError('psi must be a dictionary')
2161
for i in psi:
2162
if not any(i in arg._legs[v] for v in range(arg.num_verts())):
2163
raise ValueError('unknown marking {} for psi'.format(i))
2164
dec = decstratum(arg, kappa=kappa, psi=psi, poly=poly)
2165
return self.element_class(self, [dec])
2166
elif isinstance(arg, (tuple, list)):
2167
# a sequence of decstratum
2168
for term in arg:
2169
if not isinstance(term, decstratum):
2170
raise TypeError('must be a sequence of decstrata')
2171
self._check_stable_graph(term.gamma)
2172
return self.element_class(self, arg)
2173
else:
2174
raise NotImplementedError('unknown argument of type arg={}'.format(arg))
2175
2176
# NOTE: replaces admcycles.Tautv_to_tautclass
2177
def from_vector(self, v, r):
2178
r"""
2179
Return the tautological class associated to the vector ``v`` and degree ``r`` on this tautological ring.
2180
2181
See also :meth:`~TautlogicalRing.generators` and :meth:`~TautologicalRing.from_basis_vector`.
2182
2183
EXAMPLES::
2184
2185
sage: from admcycles import TautologicalRing
2186
sage: R = TautologicalRing(1, 2)
2187
sage: R.from_vector((0, -1, 0, 1, 0), 1)
2188
Graph : [1] [[1, 2]] []
2189
Polynomial : -psi_1
2190
<BLANKLINE>
2191
Graph : [0, 1] [[1, 2, 4], [5]] [(4, 5)]
2192
Polynomial : 1
2193
2194
sage: R = TautologicalRing(1, [4, 7])
2195
sage: R.from_vector((0, -1, 0, 1, 0), 1)
2196
Graph : [1] [[4, 7]] []
2197
Polynomial : -psi_4
2198
<BLANKLINE>
2199
Graph : [0, 1] [[1, 4, 7], [5]] [(1, 5)]
2200
Polynomial : 1
2201
2202
This also works with non-standard moduli::
2203
2204
sage: R = TautologicalRing(2,0,moduli='ct')
2205
sage: R.generators(1)
2206
[Graph : [2] [[]] []
2207
Polynomial : (kappa_1)_0,
2208
Graph : [1, 1] [[2], [3]] [(2, 3)]
2209
Polynomial : 1]
2210
sage: R.from_vector((1,2),1)
2211
Graph : [2] [[]] []
2212
Polynomial : (kappa_1)_0
2213
<BLANKLINE>
2214
Graph : [1, 1] [[2], [3]] [(2, 3)]
2215
Polynomial : 2
2216
2217
TESTS::
2218
2219
sage: R = TautologicalRing(2,0)
2220
sage: x = polygen(ZZ)
2221
sage: v = vector((x,0,0,0,0,0,0,0))
2222
sage: R.from_vector(v, 2).parent().base_ring()
2223
Univariate Polynomial Ring in x over Rational Field
2224
"""
2225
from .admcycles import Graphtodecstratum
2226
from . import DR
2227
strata = DR.all_strata(self._g, r, tuple(range(1, self._n + 1)),
2228
moduli_type=get_moduli(self._moduli, DRpy=True))
2229
stratmod = []
2230
if len(v) != len(strata):
2231
raise ValueError('input v has wrong length')
2232
v = vector(v)
2233
BR = v.parent().base_ring()
2234
try:
2235
TR = coercion_model.common_parent(self, BR)
2236
except TypeError:
2237
TR = TautologicalRing(self._g, self._n, moduli=self._moduli, base_ring=BR)
2238
for i in range(len(v)):
2239
if not v[i]:
2240
continue
2241
currstrat = Graphtodecstratum(strata[i])
2242
currstrat.poly *= v[i]
2243
stratmod.append(currstrat)
2244
if self._markings and self._markings[-1] != self._n:
2245
dic = {i + 1: j for i, j in enumerate(self._markings)}
2246
R = TR.standard_markings()
2247
return R.element_class(R, stratmod).rename_legs(dic, inplace=False)
2248
else:
2249
return TR.element_class(TR, stratmod)
2250
2251
def dimension(self, r):
2252
r"""
2253
INPUT:
2254
2255
r : integer
2256
the degree
2257
2258
EXAMPLES::
2259
2260
sage: from admcycles import *
2261
sage: R = TautologicalRing(2, 1)
2262
sage: [R.dimension(r) for r in range(5)]
2263
[1, 3, 5, 3, 1]
2264
sage: R = TautologicalRing(2, 1, moduli='ct')
2265
sage: [R.dimension(r) for r in range(5)]
2266
[1, 2, 1, 0, 0]
2267
"""
2268
if r < 0 or r > self.socle_degree():
2269
return 0
2270
2271
if self._moduli == MODULI_TL:
2272
raise NotImplementedError('dimension not implemented for the moduli of treelike curves')
2273
2274
# TODO: is there something smarter?
2275
from .admcycles import generating_indices
2276
return len(generating_indices(self._g, self._n, r, moduli=_moduli_to_str[self._moduli]))
2277
2278
# NOTE: replaces admcycles.Tautbv_to_tautclass
2279
# TODO: full support for all moduli
2280
def from_basis_vector(self, v, r):
2281
r"""
2282
Return the tautological class of degree ``r`` corresponding to the
2283
vector ``v`` expressing the coefficients in the basis.
2284
2285
See also :meth:`~TautologicalRing.basis` and :meth:`~TautologicalRing.from_vector`.
2286
2287
INPUT:
2288
2289
v : vector
2290
r : degree
2291
2292
EXAMPLES::
2293
2294
sage: from admcycles import TautologicalRing
2295
2296
sage: R = TautologicalRing(2, 1)
2297
sage: R.from_basis_vector((1,0,-2), 1)
2298
Graph : [2] [[1]] []
2299
Polynomial : (kappa_1)_0
2300
<BLANKLINE>
2301
Graph : [1, 1] [[3], [1, 4]] [(3, 4)]
2302
Polynomial : -2
2303
2304
sage: R = TautologicalRing(2, [3])
2305
sage: R.from_basis_vector((1,0,-2), 1)
2306
Graph : [2] [[3]] []
2307
Polynomial : (kappa_1)_0
2308
<BLANKLINE>
2309
Graph : [1, 1] [[1], [3, 4]] [(1, 4)]
2310
Polynomial : -2
2311
2312
TESTS::
2313
2314
sage: for mo in ['st', 'ct', 'rt', 'sm']:
2315
....: R = TautologicalRing(2,2,moduli=mo)
2316
....: for a in R.generators(2):
2317
....: assert a == R.from_basis_vector(a.basis_vector(),2)
2318
"""
2319
if self._moduli == MODULI_TL:
2320
raise NotImplementedError('from_basis not implemented for the moduli of treelike curves')
2321
from .admcycles import Graphtodecstratum, generating_indices
2322
from . import DR
2323
genind = generating_indices(self._g, self._n, r, moduli=_moduli_to_str[self._moduli])
2324
# TODO: maybe use the method TautologicalRing.generators?
2325
strata = DR.all_strata(self._g, r, tuple(range(1, self._n + 1)),
2326
moduli_type=get_moduli(self._moduli, DRpy=True))
2327
stratmod = []
2328
if len(v) != len(genind):
2329
from warnings import warn
2330
warn('vector v has wrong length {}; should have been {}'.format(len(v), len(strata)), DeprecationWarning)
2331
for i in range(len(v)):
2332
if not v[i]:
2333
continue
2334
currstrat = Graphtodecstratum(strata[genind[i]])
2335
currstrat.poly *= v[i]
2336
stratmod.append(currstrat)
2337
2338
if self._markings and self._markings[-1] != self._n:
2339
dic = {i + 1: j for i, j in enumerate(self._markings)}
2340
R = self.standard_markings()
2341
return R.element_class(R, stratmod).rename_legs(dic, inplace=False)
2342
else:
2343
return self.element_class(self, stratmod)
2344
2345
# TODO: if elements were immutable, this could be cached
2346
def generators(self, r=None):
2347
r"""
2348
INPUT:
2349
2350
r : integer (optional)
2351
the degree. If provided, only returns the generators of a given degree.
2352
2353
EXAMPLES::
2354
2355
sage: from admcycles import TautologicalRing
2356
sage: R = TautologicalRing(2,0)
2357
sage: R.generators(1)
2358
[Graph : [2] [[]] []
2359
Polynomial : (kappa_1)_0,
2360
Graph : [1, 1] [[2], [3]] [(2, 3)]
2361
Polynomial : 1,
2362
Graph : [1] [[2, 3]] [(2, 3)]
2363
Polynomial : 1]
2364
sage: R = TautologicalRing(2,0,moduli='ct')
2365
sage: R.generators(1)
2366
[Graph : [2] [[]] []
2367
Polynomial : (kappa_1)_0,
2368
Graph : [1, 1] [[2], [3]] [(2, 3)]
2369
Polynomial : 1]
2370
sage: R = TautologicalRing(2,0,moduli='sm')
2371
sage: R.generators(1)
2372
[]
2373
2374
TESTS::
2375
2376
sage: R = TautologicalRing(1, 2)
2377
sage: for v in R.generators(0): print(v.vector())
2378
(1)
2379
sage: for v in R.generators(1): print(v.vector())
2380
(1, 0, 0, 0, 0)
2381
(0, 1, 0, 0, 0)
2382
(0, 0, 1, 0, 0)
2383
(0, 0, 0, 1, 0)
2384
(0, 0, 0, 0, 1)
2385
sage: for v in R.generators(2): print(v.vector())
2386
(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
2387
(0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
2388
(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
2389
(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
2390
(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
2391
(0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
2392
(0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)
2393
(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0)
2394
(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)
2395
(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0)
2396
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0)
2397
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0)
2398
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0)
2399
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)
2400
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
2401
2402
Test compatibility with nonstandard markings::
2403
2404
sage: R = TautologicalRing(1,[2,4])
2405
sage: R.generators(1)
2406
[Graph : [1] [[2, 4]] []
2407
Polynomial : (kappa_1)_0, Graph : [1] [[2, 4]] []
2408
Polynomial : psi_2, Graph : [1] [[2, 4]] []
2409
Polynomial : psi_4, Graph : [0, 1] [[1, 2, 4], [5]] [(1, 5)]
2410
Polynomial : 1, Graph : [0] [[1, 2, 4, 5]] [(1, 5)]
2411
Polynomial : 1]
2412
"""
2413
if r is None:
2414
result = []
2415
for r in range(self.socle_degree() + 1):
2416
result.extend(self.generators(r))
2417
return result
2418
2419
r = ZZ(r)
2420
if r < 0 or r > self.socle_degree():
2421
return []
2422
2423
from .DR import all_strata
2424
from .admcycles import Graphtodecstratum
2425
res = []
2426
if self._markings and self._markings[-1] == self._n:
2427
for stratum in all_strata(self._g, r, tuple(range(1, self._n + 1)), moduli_type=get_moduli(self._moduli, DRpy=True)):
2428
res.append(self.element_class(self, [Graphtodecstratum(stratum)]))
2429
else:
2430
for stratum in all_strata(self._g, r, tuple(range(1, self._n + 1)), moduli_type=get_moduli(self._moduli, DRpy=True)):
2431
res.append(self.element_class(self, [Graphtodecstratum(stratum).rename_legs({i + 1: j for i, j in enumerate(self._markings)})]))
2432
return res
2433
2434
# NOTE: by convention we make gens return a tuple. We do not use the generic gens
2435
# which performs some caching and does not allow extra arguments.
2436
def gens(self, r=None):
2437
r"""
2438
Return a tuple whose entries are module generators for this tautological ring.
2439
2440
INPUT:
2441
2442
r: integer (optional)
2443
If provided, return generators in the given degree only.
2444
2445
EXAMPLES::
2446
2447
sage: from admcycles import TautologicalRing
2448
sage: R = TautologicalRing(2,0)
2449
sage: R.gens(1)
2450
(Graph : [2] [[]] []
2451
Polynomial : (kappa_1)_0,
2452
Graph : [1, 1] [[2], [3]] [(2, 3)]
2453
Polynomial : 1,
2454
Graph : [1] [[2, 3]] [(2, 3)]
2455
Polynomial : 1)
2456
"""
2457
return tuple(self.generators(r))
2458
2459
def ngens(self, r=None):
2460
r"""
2461
Return the number of generators.
2462
2463
INPUT:
2464
2465
r: integer (optional)
2466
If provided, return the number of generators of the given degree.
2467
2468
EXAMPLES::
2469
2470
sage: from admcycles import TautologicalRing
2471
sage: R = TautologicalRing(2, 0)
2472
sage: R.ngens()
2473
29
2474
sage: R.ngens(2)
2475
8
2476
2477
TESTS::
2478
2479
sage: R = TautologicalRing(2,0,moduli='ct')
2480
sage: R.socle_degree()
2481
1
2482
sage: R.ngens()
2483
3
2484
sage: len(R.generators(1))
2485
2
2486
"""
2487
if r is None:
2488
return sum(self.ngens(r) for r in range(self.socle_degree() + 1))
2489
2490
r = ZZ(r)
2491
if r < 0 or r > self.socle_degree():
2492
return ZZ.zero()
2493
2494
try:
2495
ngens = self._ngens
2496
except AttributeError:
2497
ngens = self._ngens = [None] * (self.socle_degree() + 1)
2498
2499
from .DR import num_strata
2500
if ngens[r] is None:
2501
ngens[r] = ZZ(num_strata(self._g, r, tuple(range(1, self._n + 1)), moduli_type=get_moduli(self._moduli, DRpy=True)))
2502
2503
return ngens[r]
2504
2505
def gen(self, i=0, r=None):
2506
r"""
2507
Return the ``i``-th generator.
2508
2509
INPUT:
2510
2511
i : integer (default ``0``)
2512
The number of the generator.
2513
2514
r : integer (optional)
2515
If provided, return the ``i``-th generator in degree ``r``.
2516
2517
EXAMPLES::
2518
2519
sage: from admcycles import TautologicalRing
2520
sage: R = TautologicalRing(2, 0)
2521
sage: R.gen(23)
2522
Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]
2523
Polynomial : (kappa_1)_1
2524
sage: [R.ngens(i) for i in range(4)]
2525
[1, 3, 8, 17]
2526
sage: R.gen(11,3)
2527
Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]
2528
Polynomial : (kappa_1)_1
2529
sage: R.gen(2, 3)
2530
Graph : [2] [[]] []
2531
Polynomial : (kappa_1^3)_0
2532
2533
TESTS::
2534
2535
sage: from admcycles import TautologicalRing
2536
sage: R = TautologicalRing(2, 0)
2537
sage: R.generators() == [R.gen(i) for i in range(R.ngens())]
2538
True
2539
sage: R.generators(3) == [R.gen(i, 3) for i in range(R.ngens(3))]
2540
True
2541
"""
2542
if i < 0 or i > self.ngens(r):
2543
raise ValueError('undefined generator')
2544
2545
if r is None:
2546
r = 0
2547
while i >= self.ngens(r):
2548
i -= self.ngens(r)
2549
r += 1
2550
return self.generators(r)[i]
2551
2552
def list_generators(self, r):
2553
r"""
2554
Lists all tautological classes of degree r in the tautological ring.
2555
2556
INPUT:
2557
2558
r : integer
2559
The degree r of of the classes.
2560
2561
EXAMPLES::
2562
2563
sage: from admcycles import TautologicalRing
2564
sage: R = TautologicalRing(2,0)
2565
sage: R.list_generators(2)
2566
[0] : Graph : [2] [[]] []
2567
Polynomial : (kappa_2)_0
2568
[1] : Graph : [2] [[]] []
2569
Polynomial : (kappa_1^2)_0
2570
[2] : Graph : [1, 1] [[2], [3]] [(2, 3)]
2571
Polynomial : (kappa_1)_0
2572
[3] : Graph : [1, 1] [[2], [3]] [(2, 3)]
2573
Polynomial : psi_2
2574
[4] : Graph : [1] [[2, 3]] [(2, 3)]
2575
Polynomial : (kappa_1)_0
2576
[5] : Graph : [1] [[2, 3]] [(2, 3)]
2577
Polynomial : psi_2
2578
[6] : Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]
2579
Polynomial : 1
2580
[7] : Graph : [0] [[3, 4, 5, 6]] [(3, 4), (5, 6)]
2581
Polynomial : 1
2582
"""
2583
L = self.generators(r)
2584
for i in range(len(L)):
2585
print('[' + repr(i) + '] : ' + repr(L[i]))
2586
2587
# TODO: if elements were immutable, this could be cached
2588
# TODO: full support for all moduli
2589
def basis(self, d=None):
2590
r"""
2591
Return a basis.
2592
2593
INPUT:
2594
2595
d : ``None`` (default) or integer
2596
if ``None`` return a full basis if ``d`` is provided, return a basis
2597
of the homogeneous component of degree ``d``
2598
2599
EXAMPLES::
2600
2601
sage: from admcycles import TautologicalRing
2602
sage: R = TautologicalRing(2, 0)
2603
sage: R.basis(2)
2604
[Graph : [2] [[]] []
2605
Polynomial : (kappa_2)_0,
2606
Graph : [2] [[]] []
2607
Polynomial : (kappa_1^2)_0]
2608
sage: R2 = TautologicalRing(2, 1, moduli='ct')
2609
sage: R2.basis(1)
2610
[Graph : [2] [[1]] []
2611
Polynomial : (kappa_1)_0,
2612
Graph : [2] [[1]] []
2613
Polynomial : psi_1]
2614
"""
2615
if d is None:
2616
result = []
2617
for d in range(self.socle_degree() + 1):
2618
result.extend(self.basis(d))
2619
return result
2620
2621
if self._moduli == MODULI_TL:
2622
raise NotImplementedError('basis not implemented for treelike curves')
2623
2624
from .admcycles import generating_indices
2625
gens = self.generators(d)
2626
return [gens[i] for i in generating_indices(self._g, self._n, d, moduli=_moduli_to_str[self._moduli])]
2627
2628
def kappa_psi_polynomials(self, d, combout=False):
2629
r"""
2630
Iterator over the polynomials in kappa and psi classes of degree ``d``.
2631
2632
For combout=True it returns a generator of triples
2633
(kappalist, psidict, kppoly) of
2634
2635
* a list kappalist of exponents of kappa_i,
2636
* a dictionary psidict sending i to the exponent of psi_i,
2637
* the associated TautologicalClass for this monomial.
2638
2639
EXAMPLES::
2640
2641
sage: from admcycles import TautologicalRing
2642
sage: for a in TautologicalRing(0, 4).kappa_psi_polynomials(1): print(a)
2643
Graph : [0] [[1, 2, 3, 4]] []
2644
Polynomial : (kappa_1)_0
2645
Graph : [0] [[1, 2, 3, 4]] []
2646
Polynomial : psi_1
2647
Graph : [0] [[1, 2, 3, 4]] []
2648
Polynomial : psi_2
2649
Graph : [0] [[1, 2, 3, 4]] []
2650
Polynomial : psi_3
2651
Graph : [0] [[1, 2, 3, 4]] []
2652
Polynomial : psi_4
2653
2654
sage: for a in TautologicalRing(1, [2,7]).kappa_psi_polynomials(1): print(a)
2655
Graph : [1] [[2, 7]] []
2656
Polynomial : (kappa_1)_0
2657
Graph : [1] [[2, 7]] []
2658
Polynomial : psi_2
2659
Graph : [1] [[2, 7]] []
2660
Polynomial : psi_7
2661
2662
Here is the option where the combinatorial data is output separately::
2663
2664
sage: for a in TautologicalRing(1, [2,7]).kappa_psi_polynomials(1, True): print(a)
2665
([1], {}, Graph : [1] [[2, 7]] []
2666
Polynomial : (kappa_1)_0)
2667
([], {2: 1}, Graph : [1] [[2, 7]] []
2668
Polynomial : psi_2)
2669
([], {7: 1}, Graph : [1] [[2, 7]] []
2670
Polynomial : psi_7)
2671
"""
2672
triv = self.trivial_graph()
2673
for V in IntegerVectors(d, 1 + self._n):
2674
psi = {i: a for i, a in zip(self._markings, V[1:]) if a}
2675
for kV in Partitions(V[0]):
2676
kappa = []
2677
for i, a in enumerate(kV.to_exp()):
2678
if not a:
2679
continue
2680
kappa.extend([0] * (i + 1 - len(kappa)))
2681
kappa[i] = a
2682
cl = self(triv, psi=psi, kappa=[kappa])
2683
yield (kappa, psi, cl) if combout else cl
2684
2685
def num_gens(self, r):
2686
r"""
2687
INPUT:
2688
2689
r : integer
2690
degree
2691
2692
TESTS::
2693
2694
sage: from admcycles import *
2695
sage: R = TautologicalRing(1,2)
2696
sage: R.num_gens(1)
2697
5
2698
sage: len(R.generators(1))
2699
5
2700
sage: R = TautologicalRing(1,2,moduli='ct')
2701
sage: R.num_gens(1)
2702
4
2703
"""
2704
from .DR import num_strata
2705
return num_strata(self._g, r, tuple(range(1, self._n + 1)), self._moduli)
2706
2707
@cached_method
2708
def pairing_matrix(self, d, basis=False, ind_d=None, ind_dcomp=None):
2709
r"""
2710
Computes the matrix of the intersection pairing of generators in
2711
degree d (rows) against generators of opposite degree (columns).
2712
2713
INPUT:
2714
2715
d : integer
2716
degree of the classes associated to rows
2717
2718
basis : bool (default: False)
2719
compute pairing of basis elements in the two degrees
2720
2721
ind_d, ind_dcomp: tuple (optional)
2722
lists of indices of generators in degrees d and its complementary degree
2723
2724
NOTE:
2725
2726
The matrix is returned as an immutable object, to allow caching.
2727
2728
EXAMPLES::
2729
2730
sage: from admcycles import TautologicalRing
2731
sage: R = TautologicalRing(1,2)
2732
sage: R.pairing_matrix(1)
2733
[ 1/8 1/12 1/12 1/24 1]
2734
[ 1/12 1/24 1/24 0 1]
2735
[ 1/12 1/24 1/24 0 1]
2736
[ 1/24 0 0 -1/24 1]
2737
[ 1 1 1 1 0]
2738
sage: R.pairing_matrix(1, basis=True)
2739
[ 1/8 1/12]
2740
[1/12 1/24]
2741
sage: R.pairing_matrix(1, ind_d=(0,1), ind_dcomp=(1,2,3))
2742
[1/12 1/12 1/24]
2743
[1/24 1/24 0]
2744
"""
2745
dcomp = self.socle_degree() - d
2746
2747
if ind_d is not None:
2748
allgens = self.generators(d)
2749
gens = [allgens[i] for i in ind_d]
2750
elif basis:
2751
gens = self.basis(d)
2752
else:
2753
gens = self.generators(d)
2754
2755
if ind_dcomp is not None:
2756
allcogens = self.generators(dcomp)
2757
cogens = [allcogens[i] for i in ind_dcomp]
2758
elif basis:
2759
cogens = self.basis(dcomp)
2760
else:
2761
cogens = self.generators(dcomp)
2762
2763
# TODO: for d = dcomp we only need to compute half of these numbers
2764
M = matrix(QQ, [[(a * b).evaluate() for b in cogens] for a in gens])
2765
M.set_immutable()
2766
return M
2767
2768
def double_ramification_cycle(self, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False, chiodo_coeff=False, r_coeff=None):
2769
r"""
2770
Return the k-twisted double ramification cycle in genus g and codimension d
2771
for the partition Avector of k*(2g-2+n). For more information see the documentation
2772
of :func:`~admcycles.double_ramification_cycle.DR_cycle`.
2773
2774
EXAMPLES::
2775
2776
sage: from admcycles import TautologicalRing
2777
sage: R = TautologicalRing(2,2)
2778
sage: DR = R.double_ramification_cycle([1,3], k=1, d=1)
2779
sage: (DR * R.psi(1)^3).evaluate()
2780
-11/1920
2781
"""
2782
from .double_ramification_cycle import DR_cycle
2783
if len(Avector) != self._n:
2784
raise ValueError('length of argument Avector must be n')
2785
return DR_cycle(self._g, Avector, d=None, k=None, rpoly=False, tautout=True, basis=False, chiodo_coeff=False, r_coeff=None, moduli=self._moduli, base_ring=self.base_ring())
2786
2787
def theta_class(self):
2788
r"""
2789
Return the class Theta_{g,n} from [Norbury - A new cohomology class on the moduli space of curves].
2790
For more information see the documentation of :func:`~admcycles.double_ramification_cycle.ThetaClass`.
2791
2792
EXAMPLES::
2793
2794
sage: from admcycles import TautologicalRing
2795
sage: R = TautologicalRing(1, 1)
2796
sage: R.theta_class()
2797
Graph : [1] [[1]] []
2798
Polynomial : 11/6*(kappa_1)_0 + 1/6*psi_1
2799
<BLANKLINE>
2800
Graph : [0] [[3, 4, 1]] [(3, 4)]
2801
Polynomial : 1/24
2802
sage: R = TautologicalRing(2, 1, moduli='ct')
2803
sage: R.theta_class()
2804
0
2805
"""
2806
from .double_ramification_cycle import ThetaClass
2807
return self(ThetaClass(self._g, self._n, moduli=self._moduli))
2808
2809
def hyperelliptic_cycle(self, n=0, m=0):
2810
r"""
2811
Return the cycle class of the hyperelliptic locus of genus g curves with n marked
2812
fixed points and m pairs of conjugate points in `\bar M_{g,n+2m}`.
2813
2814
For more information see the documentation of :func:`~admcycles.admcycles.Hyperell`.
2815
2816
EXAMPLES::
2817
2818
sage: from admcycles import TautologicalRing
2819
sage: R = TautologicalRing(2,1)
2820
sage: H = R.hyperelliptic_cycle(1,0)
2821
sage: H.forgetful_pushforward([1]).fund_evaluate()
2822
6
2823
"""
2824
if n + 2 * m != self._n:
2825
raise ValueError('the number n+2m must equal the total number of marked points')
2826
from .admcycles import Hyperell
2827
return self(Hyperell(self._g, n, m))
2828
2829
def bielliptic_cycle(self, n=0, m=0):
2830
r"""
2831
Return the cycle class of the bielliptic locus of genus g curves with n marked
2832
fixed points and m pairs of conjugate points in `\bar M_{g,n+2m}`.
2833
2834
For more information see the documentation of :func:`~admcycles.admcycles.Biell`.
2835
2836
EXAMPLES::
2837
2838
sage: from admcycles import TautologicalRing
2839
sage: R = TautologicalRing(1,2)
2840
sage: B = R.bielliptic_cycle(0,1)
2841
sage: B.degree_list()
2842
[1]
2843
sage: B.forgetful_pushforward([2]).fund_evaluate()
2844
3
2845
"""
2846
if n + 2 * m != self._n:
2847
raise ValueError('the number n+2m must equal the total number of marked points')
2848
from .admcycles import Biell
2849
return self(Biell(self._g, n, m))
2850
2851
def generalized_lambda(self, deg, l, d, a):
2852
r"""
2853
Computes the Chern class c_deg of the derived pushforward of a line bundle
2854
\O(D) on the universal curve C_{g,n} over the space Mbar_{g,n} of stable curves, for
2855
2856
D = l \tilde{K} + sum_{i=1}^n d_i \sigma_i + \sum_{h,S} a_{h,S} C_{h,S}
2857
2858
where the numbers l, d_i and a_{h,S} are integers, \tilde{K} is the relative canonical
2859
class of the morphism C_{g,n} -> Mbar_{g,n}, \sigma_i is the image of the ith section
2860
and C_{h,S} is the boundary divisor of C_{g,n} where the moving point lies on a genus h
2861
component with markings given by the set S.
2862
2863
For more information see the documentation of :func:`~admcycles.GRRcomp.generalized_lambda`.
2864
2865
EXAMPLES::
2866
2867
sage: from admcycles import TautologicalRing
2868
sage: R = TautologicalRing(2,1)
2869
sage: l = 1; d = [0]; a = []
2870
sage: t = R.generalized_lambda(1,l,d,a)
2871
sage: t.basis_vector()
2872
(1/2, -1/2, -1/2)
2873
"""
2874
if len(d) != self._n:
2875
raise ValueError('the number of entries of d equal the total number of marked points')
2876
from .GRRcomp import generalized_lambda
2877
return self(generalized_lambda(deg, l, d, a, self._g, self._n))
2878
2879
def differential_stratum(self, k, mu, virt=False, res_cond=(), xi_power=0, method='pull'):
2880
r"""
2881
Return the fundamental class of the closure of the stratum of ``k``-differentials
2882
with vanishing and pole orders ``mu``.
2883
2884
For more information see the documentation of :func:`~admcycles.stratarecursion.Strataclass`.
2885
2886
EXAMPLES::
2887
2888
sage: from admcycles import TautologicalRing
2889
sage: R = TautologicalRing(1,2)
2890
sage: H1 = R.differential_stratum(1,(1, -1))
2891
sage: H1.is_zero()
2892
True
2893
sage: H5 = R.differential_stratum(1,(5, -5))
2894
sage: H5.forgetful_pushforward([2]).fund_evaluate()
2895
24
2896
"""
2897
if len(mu) != self._n:
2898
raise ValueError('the length of partition mu must equal the total number of marked points')
2899
from .stratarecursion import Strataclass
2900
return self(Strataclass(self._g, k, mu, virt=virt, res_cond=res_cond, xi_power=xi_power, method=method))
2901
2902
identify_class = identify_class
2903
2904
def presentation(self, generators=None, assume_FZ=True, eliminate_generators=None, output='pres'):
2905
r"""
2906
Computes a presentation of the tautological ring as a quotient of
2907
a polynomial ring by an ideal, where the generators of the polynomial
2908
ring are sent to the given generators.
2909
2910
The function returns the above ideal in a polynomial ring together with
2911
a ring homomorphism. Assumes FZ relations are sufficient unless otherwise
2912
instructed.
2913
2914
INPUT:
2915
2916
- generators (list) --- optional, if provided we will use the given
2917
elements as ring generators (it is the user's responsibility to check
2918
they generate as an algebra)
2919
- elimimate_generators (bool) --- optional, decides whether to keep around unnecessary ring generators.
2920
- output (str) --- optional. Can be::
2921
'pres', in which case returns a surjecion from a polynomial ring to self, and an ideal of that ring (the kernel)
2922
'lists', in which case it returns generators for the ideal, then lists of generators for R and a correspondng list of generators for TR.
2923
'fun', in which case it returns an ideal, a rung hom from free polynomial ring to self, and a function from self to free polynomial ring (a lift). This does not allow the user to choose their own basis.
2924
2925
EXAMPLES::
2926
2927
sage: from admcycles import TautologicalRing
2928
sage: TR = TautologicalRing(2,0)
2929
sage: I, f = TR.presentation()
2930
sage: R = I.ring()
2931
sage: I == R.ideal([2*R.0*R.1 + R.1^2, 40*R.0^3 - 43*R.1^3, R.1^4])
2932
True
2933
sage: R.ngens()
2934
2
2935
sage: I, f = TR.presentation(eliminate_generators = False)
2936
sage: R = I.ring()
2937
sage: R.ngens()
2938
5
2939
2940
By specifying generators lambda_1 and delta_1, we can check a result by
2941
Faber.::
2942
2943
sage: gens = (TR.lambdaclass(1), 1/2*TR.sepbdiv(1,()))
2944
sage: I, f = TR.presentation(gens)
2945
sage: R = I.ring()
2946
sage: x0, x1 = R.gens()
2947
sage: I == R.ideal([x1^2 + x0*x1, 5*x0^3 - x0^2 * x1])
2948
True
2949
sage: r = f(x1^2 + x0*x1)
2950
sage: r.is_zero()
2951
True
2952
2953
Going to the locus of smooth curves, we check Theorem 1.1 in [CanLars]_ by Canning-Larson (we correct the exponent of kap1 in the last term).::
2954
2955
sage: TR = TautologicalRing(7, 0, moduli = 'sm')
2956
sage: gens = (TR.kappa(1), TR.kappa(2))
2957
sage: I, f = TR.presentation(gens)
2958
sage: R = I.ring(); R
2959
Multivariate Polynomial Ring in x0, x1 over Rational Field
2960
sage: kap1, kap2 = R.gens()
2961
sage: idgens = [2423*kap1^2*kap2 - 52632*kap2^2, 1152000*kap2^2 - 2423*kap1^4, 16000*kap1^3*kap2 - 731*kap1^5]
2962
sage: I == R.ideal(idgens)
2963
True
2964
2965
Theorem 1.2 in [CanLars]_ (we correct the signs in the first term).::
2966
2967
sage: TR = TautologicalRing(8, 0, moduli = 'sm')
2968
sage: gens = (TR.kappa(1), TR.kappa(2))
2969
sage: I, f = TR.presentation(gens)
2970
sage: R = I.ring(); R
2971
Multivariate Polynomial Ring in x0, x1 over Rational Field
2972
sage: kap1, kap2 = R.gens()
2973
sage: idgens = [714894336*kap2^2 - 55211328*kap1^2*kap2 + 1058587*kap1^4, 62208000*kap1*kap2^2 - 95287*kap1^5, 144000*kap1^3*kap2 - 5617*kap1^5]
2974
sage: I == R.ideal(idgens)
2975
True
2976
2977
Theorem 1.3 in [CanLars]_ (corrected the last coefficient of the third generator).::
2978
2979
sage: TR = TautologicalRing(9, 0, moduli = 'sm')
2980
sage: gens = (TR.kappa(1), TR.kappa(2), TR.kappa(3))
2981
sage: I, f = TR.presentation(gens)
2982
sage: R = I.ring(); R
2983
Multivariate Polynomial Ring in x0, x1, x2 over Rational Field
2984
sage: kap1, kap2, kap3 = R.gens()
2985
sage: idgens = [5195*kap1^4 + 3644694*kap1*kap3 + 749412*kap2^2 - 265788*kap1^2*kap2, 33859814400*kap2*kap3 - 95311440*kap1^3*kap2 + 2288539*kap1^5, 19151377*kap1^5 + 16929907200*kap1*kap2^2 - 1142345520*kap1^3*kap2, 1422489600*kap3^2 - 983*kap1^6, 1185408000*kap2^3 - 47543*kap1^6]
2986
sage: I == R.ideal(idgens)
2987
True
2988
2989
"""
2990
if not assume_FZ:
2991
return NotImplementedError("Need to insert a step checking FZ conjecture here. ")
2992
if output == 'fun' and generators is not None:
2993
return NotImplementedError('if you want an inverse, please let me choose my own generators for now. Otherwise I would have to do a bit of linear algebra... ')
2994
if generators is None:
2995
generators = self.basis()
2996
if eliminate_generators is None:
2997
eliminate_generators = True
2998
if eliminate_generators is None:
2999
eliminate_generators = False
3000
if output in ['lists', 'fun']:
3001
eliminate_generators = False
3002
gens = []
3003
gen_degree_list = []
3004
for gen in generators:
3005
if not len(gen.degree_list()) == 1:
3006
return NotImplementedError("So far needs to assume the generators are homogeneous. ")
3007
m = max(gen.degree_list())
3008
if not m == 0:
3009
gens.append(gen)
3010
gen_degree_list.append(m)
3011
T = TermOrder("wdeglex", gen_degree_list)
3012
ngens = len(gens)
3013
R = PolynomialRing(QQ, 'x', ngens, order=T)
3014
x = R.gens()
3015
maxdeg = self.socle_degree()
3016
LL = [WeightedIntegerVectors(maxdeg + i + 1, gen_degree_list) for i in range(max(gen_degree_list))]
3017
LL2 = [a for b in LL for a in b]
3018
# NOTE: this is not maximally efficient, I guess
3019
deg_ideal = R.ideal([R({tuple(a): 1}) for a in LL2])
3020
ideal_gens = []
3021
for deg in range(1, maxdeg + 1):
3022
exponent_tuples_to_try = WeightedIntegerVectors(deg, gen_degree_list)
3023
taut_monomials_to_try = [prod([gens[i]**w[i] for i in range(len(gens))]) for w in exponent_tuples_to_try]
3024
poly_monomials_to_try = [R({tuple(w): 1}) for w in exponent_tuples_to_try]
3025
# NOTE: need to specify the degree, as some elements may be zero!
3026
rels = matrix([m.basis_vector(deg) for m in taut_monomials_to_try]).kernel().gens()
3027
for r in rels:
3028
P = sum([r[i] * poly_monomials_to_try[i] for i in range(len(r))])
3029
ideal_gens.append(P)
3030
genlist = (R.ideal(ideal_gens) + deg_ideal).groebner_basis()
3031
elimlist = []
3032
for i in range(ngens):
3033
i = ngens - i - 1
3034
elim = False
3035
for f in genlist:
3036
if not f.coefficient(x[i]) == 0 and f.coefficient(x[i]).is_constant():
3037
elim = True
3038
elimlist.append(i)
3039
break
3040
if elim:
3041
I = R.ideal(genlist).elimination_ideal(x[i])
3042
genlist = I.gens()
3043
if eliminate_generators:
3044
for i in elimlist:
3045
R = R.remove_var(x[i], order='degrevlex')
3046
gens.pop(i)
3047
II = R.ideal(list(genlist))
3048
if output == 'pres':
3049
Rhom = R.hom(gens, self)
3050
return II, Rhom
3051
if output == 'lists':
3052
return II, gens, R.gens()
3053
# return II, gens[i]:R.gens()[i] for i in range(ngens)}, {R.gens()[i]:gens[i] for i in range(ngens)}
3054
if output == 'fun':
3055
Rhom = R.hom(gens, self)
3056
3057
def f(tautelt):
3058
polyelt = 0
3059
gennum = 0
3060
for d in range(1, self.socle_degree()):
3061
vd = tautelt.basis_vector(d)
3062
polyelt += sum([vd[i] * R.gens()[i + gennum] for i in range(len(vd))])
3063
gennum += len(vd)
3064
return polyelt + tautelt.basis_vector(0)[0]
3065
return II, Rhom, f
3066
3067
3068
class TautologicalRingFunctor(ConstructionFunctor):
3069
r"""
3070
Construction functor for tautological ring.
3071
3072
This class is the way to implement the "promotion of base ring" (see below in the examples).
3073
3074
EXAMPLES::
3075
3076
sage: from admcycles.tautological_ring import TautologicalRing, TautologicalRingFunctor
3077
sage: F = TautologicalRingFunctor(1, (1,), 'st')
3078
sage: F(QQ)
3079
TautologicalRing(g=1, n=1, moduli='st') over Rational Field
3080
3081
sage: x = polygen(QQ, 'x')
3082
sage: (x**2 + 2) * TautologicalRing(1, 1).generators(1)[0]
3083
Graph : [1] [[1]] []
3084
Polynomial : (x^2 + 2)*(kappa_1)_0
3085
"""
3086
rank = 10
3087
3088
def __init__(self, g, markings, moduli):
3089
Functor.__init__(self, _CommutativeRings, _CommutativeRings)
3090
self.g = g
3091
self.markings = markings
3092
self.moduli = moduli
3093
3094
def _repr_(self):
3095
return 'TautologicalRingFunctor(g={}, n={}, moduli={!r})'.format(
3096
self.g, self.markings, _moduli_to_str[self.moduli])
3097
3098
def _apply_functor(self, R):
3099
return TautologicalRing(self.g, self.markings, self.moduli, base_ring=R)
3100
3101
def merge(self, other):
3102
r"""
3103
Return the merge of two tautological ring functors.
3104
"""
3105
if isinstance(other, TautologicalRingFunctor) and \
3106
self.g == other.g and \
3107
self.markings == other.markings:
3108
return TautologicalRingFunctor(self.g, self.markings, min(self.moduli, other.moduli))
3109
3110
def __eq__(self, other):
3111
return isinstance(other, TautologicalRingFunctor) and \
3112
self.g == other.g and \
3113
self.markings == other.markings and \
3114
self.moduli == other.moduli
3115
3116
def __ne__(self, other):
3117
return not (self == other)
3118
3119
__hash__ = ConstructionFunctor.__hash__
3120
3121