Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
180 views
unlisted
ubuntu2004
1
2
# pylint does not know sage
3
from sage.structure.sage_object import SageObject # pylint: disable=import-error
4
from sage.misc.cachefunc import cached_method # pylint: disable=import-error
5
from sage.rings.rational_field import QQ # pylint: disable=import-error
6
7
import admcycles.admcycles
8
import admcycles.stratarecursion
9
10
import admcycles.diffstrata.elgtautclass
11
12
from admcycles.diffstrata.auxiliary import hash_AG
13
14
15
class AdditiveGenerator (SageObject):
16
"""
17
Product of Psi classes on an EmbeddedLevelGraph (of a stratum X).
18
19
The information of a product of psi-class on an EmbeddedLevelGraph, i.e. a
20
leg_dict and an enhanced_profile, where leg_dict is a dictionary on the legs
21
leg -> exponent of the LevelGraph associated to the enhanced profile, i.e.
22
(profile,index) or None if we refer to the class of the graph.
23
24
We (implicitly) work inside some stratum X, where the enhanced profile
25
makes sense.
26
27
This class should be considered constant (hashable)!
28
"""
29
30
def __init__(self, X, enh_profile, leg_dict=None):
31
"""
32
AdditiveGenerator for psi polynomial given by leg_dict on graph
33
corresponding to enh_profile in X.
34
35
Args:
36
X (GeneralisedStratum): enveloping stratum
37
38
enh_profile (tuple): enhanced profile (in X)
39
40
leg_dict (dict, optional): dictionary leg of enh_profile -> exponent
41
encoding a psi monomial. Defaults to None.
42
"""
43
self._X = X
44
self._hash = hash_AG(leg_dict, enh_profile)
45
self._enh_profile = (tuple(enh_profile[0]), enh_profile[1])
46
self._leg_dict = leg_dict
47
self._G = self._X.lookup_graph(*enh_profile)
48
# dictionary leg -> level
49
# Careful! These are leg numbers on the whole graph, not on
50
# the graphs inside the LevelStrata!!
51
self._level_dict = {}
52
if leg_dict is not None:
53
for l in leg_dict:
54
self._level_dict[l] = self._G.LG.level_number(
55
self._G.LG.levelofleg(l))
56
self._inv_level_dict = {}
57
for leg in self._level_dict:
58
try:
59
self._inv_level_dict[self._level_dict[leg]].append(leg)
60
except KeyError:
61
self._inv_level_dict[self._level_dict[leg]] = [leg]
62
63
@classmethod
64
def from_hash(cls, X, hash):
65
"""
66
AdditiveGenerator from a hash generated with hash_AG.
67
68
Args:
69
X (GeneralisedStratum): Enveloping stratum.
70
hash (tuple): hash from hash_AG
71
72
Returns:
73
AdditiveGenerator: AG from hash.
74
"""
75
if hash[0] is None:
76
leg_dict = None
77
else:
78
leg_dict = dict(hash[0])
79
return cls(X, (hash[1], hash[2]), leg_dict)
80
81
def __hash__(self):
82
return hash(self._hash)
83
84
def __eq__(self, other):
85
try:
86
return self._hash == other._hash
87
except AttributeError:
88
return NotImplemented
89
90
def __repr__(self):
91
return "AdditiveGenerator(X=%r,enh_profile=%r,leg_dict=%r)"\
92
% (self._X, self._enh_profile, self._leg_dict)
93
# Better, but destroys tests:
94
# return "AdditiveGenerator(enh_profile=%r,leg_dict=%r)"\
95
# % (self._enh_profile, self._leg_dict)
96
97
def __str__(self):
98
str = ""
99
if self._leg_dict is not None:
100
for l in self._leg_dict:
101
str += "Psi class %r with exponent %r on level %r * "\
102
% (l, self._leg_dict[l], self._level_dict[l])
103
str += "Graph %r" % (self._enh_profile,)
104
return str
105
106
def __mul__(self, other):
107
"""
108
Multiply to psi products on the same graph (add dictionaries).
109
110
Args:
111
other (AdditiveGenerator): Product of psi classes on same graph.
112
113
Returns:
114
AdditiveGenerator: Product of psi classes on same graph.
115
116
EXAMPLES::
117
118
119
Also works without legs.
120
121
"""
122
# Check that other is an AdditiveGenerator for the same graph:
123
try:
124
if self._X != other._X or self._enh_profile != other._enh_profile:
125
return NotImplemented
126
other_leg_dict = other._leg_dict
127
except AttributeError:
128
return NotImplemented
129
# "unite" the leg_dicts:
130
if self._leg_dict is None:
131
self_leg_dict = {}
132
else:
133
self_leg_dict = self._leg_dict
134
if other_leg_dict is None:
135
other_leg_dict = {}
136
new_leg_dict = {l: self_leg_dict.get(l, 0) + other_leg_dict.get(l, 0)
137
for l in set(self_leg_dict) | set(other_leg_dict)}
138
return self._X.additive_generator(self._enh_profile, new_leg_dict)
139
140
def __rmul__(self, other):
141
self.__mul__(other)
142
143
def __pow__(self, n):
144
return self.pow(n)
145
146
@property
147
def enh_profile(self):
148
return self._enh_profile
149
150
@property
151
def psi_degree(self):
152
"""
153
Sum of powers of psi classes of self.
154
"""
155
if self._leg_dict is None:
156
return 0
157
else:
158
return sum(self._leg_dict.values())
159
160
@cached_method
161
def dim_check(self):
162
"""
163
Check if, on any level, the psi degree is higher than the dimension.
164
165
Returns:
166
bool: False if the class is 0 for dim reasons, True otherwise.
167
"""
168
# remove if degree > dim(X)
169
if self.degree > self._X.dim():
170
return False
171
if self.codim == 0:
172
# Avoid crazy infinite recursion for smooth graph :-)
173
return True
174
# for each level, check if psi product on level exceeds level dimension
175
for level_number in range(self.codim + 1):
176
assert self.level_dim(level_number) >= 0
177
if self.degree_on_level(
178
level_number) > self.level_dim(level_number):
179
return False
180
return True
181
182
@property
183
def codim(self):
184
"""
185
The codimension of the graph (number of levels)
186
187
Returns:
188
int: length of the profile
189
"""
190
return len(self._enh_profile[0])
191
192
@property
193
def degree(self):
194
"""
195
Degree of class, i.e. codimension of graph + psi-degree
196
197
Returns:
198
int: codim + psi_degree
199
"""
200
# degree = codim of graph + powers of psi classes
201
return self.codim + self.psi_degree
202
203
@property
204
def leg_dict(self):
205
return self._leg_dict
206
207
@property
208
def level_dict(self):
209
"""
210
The dictionary mapping leg -> level
211
"""
212
return self._level_dict
213
214
@property
215
def inv_level_dict(self):
216
"""
217
The dictionary mapping level -> list of legs on level.
218
219
Returns:
220
dict: level -> list of legs.
221
"""
222
return self._inv_level_dict
223
224
@cached_method
225
def degree_on_level(self, level):
226
"""
227
Total degree of psi classes on level.
228
229
Args:
230
level (int): (relative) level number (i.e. 0...codim)
231
232
Raises:
233
RuntimeError: Raised for level number out of range.
234
235
Returns:
236
int: sum of exponents of psis appearing on this level.
237
"""
238
if level not in range(self.codim + 1):
239
raise RuntimeError(
240
"Illegal level number: %r on %r" % (level, self))
241
try:
242
return sum(self._leg_dict[leg]
243
for leg in self._inv_level_dict[level])
244
except KeyError:
245
# no psis on this level
246
return 0
247
248
def level(self, level_number):
249
"""
250
Level of underlying graph.
251
252
Args:
253
level_number (int): (relative) level number (0...codim)
254
255
Returns:
256
LevelStratum: Stratum at level level_number of self._G.
257
"""
258
return self._G.level(level_number)
259
260
@cached_method
261
def level_dim(self, level_number):
262
"""
263
Dimension of level level_number.
264
265
Args:
266
level_number (int): (relative) level number (i.e. 0...codim)
267
268
Returns:
269
int: dimension of GeneralisedLevelStratum
270
"""
271
level = self._G.level(level_number)
272
return level.dim()
273
274
@property
275
def stack_factor(self):
276
"""
277
The stack factor, that is the product of the prongs of the underlying graph
278
divided by the product of the ells of the BICs and the automorphisms.
279
280
Returns:
281
QQ: stack factor
282
"""
283
try:
284
return self._stack_factor
285
except AttributeError:
286
# to get g_Gamma, we have to take the product of prongs/lcm for
287
# each bic:
288
prod = 1
289
for k in self._G.LG.prongs.values():
290
prod *= k
291
292
p, _ = self.enh_profile
293
294
bic_contr = 1
295
for i in p:
296
bic_contr *= self._X.bics[i].ell
297
298
stack_factor = QQ(prod) / QQ(bic_contr *
299
len(self._G.automorphisms))
300
301
self._stack_factor = stack_factor
302
return self._stack_factor
303
304
@cached_method
305
def as_taut(self):
306
"""
307
Helper method, returns [(1,self)] as default input to ELGTautClass.
308
"""
309
return admcycles.diffstrata.elgtautclass.ELGTautClass(self._X, [
310
(1, self)])
311
312
@cached_method
313
def is_in_ambient(self, ambient_enh_profile):
314
"""
315
Check if ambient_enh_profile is an ambient graph, i.e. self is a degeneration
316
of ambient_enh_profile.
317
318
INPUT:
319
320
ambient_enh_profile: tuple
321
An enhanced profile.
322
323
OUTPUT:
324
325
True if there exists a leg map, False otherwise.
326
"""
327
return self._X.is_degeneration(self._enh_profile, ambient_enh_profile)
328
329
@cached_method
330
def pow(self, n, amb=None):
331
"""
332
Recursively calculate the n-th power of self (in amb), caching all results.
333
334
Args:
335
n (int): exponent
336
amb (tuple, optional): enhanced profile. Defaults to None.
337
338
Returns:
339
ELGTautClass: self^n in CH(amb)
340
"""
341
if amb is None:
342
ONE = self._X.ONE
343
amb = ((), 0)
344
else:
345
ONE = self._X.taut_from_graph(*amb)
346
if n == 0:
347
return ONE
348
return self._X.intersection(self.as_taut(), self.pow(n - 1, amb), amb)
349
350
@cached_method
351
def exp(self, c, amb=None, stop=None):
352
"""
353
exp(c * self) in CH(amb), calculated via exp_list.
354
355
Args:
356
c (QQ): coefficient
357
amb (tuple, optional): enhanced profile. Defaults to None.
358
stop (int, optional): cut-off. Defaults to None.
359
360
Returns:
361
ELGTautClass: the tautological class associated to the
362
graded list exp_list.
363
"""
364
# graded pieces are already reduced:
365
new_taut_list = []
366
for T in self.exp_list(c, amb, stop):
367
new_taut_list.extend(T.psi_list)
368
return admcycles.diffstrata.elgtautclass.ELGTautClass(
369
self._X, new_taut_list, reduce=False)
370
371
@cached_method
372
def exp_list(self, c, amb=None, stop=None):
373
"""
374
Calculate exp(c * self) in CH(amb).
375
376
We calculate exp as a sum of powers (using self.pow, i.e. cached)
377
and check at each step if the power vanishes (if yes, we obviously stop).
378
379
The result is returned as a list consisting of the graded pieces.
380
381
Optionally, one may specify the cut-off degree using stop (by
382
default this is dim + 1).
383
384
Args:
385
c (QQ): coefficient
386
amb (tuple, optional): enhanced profile. Defaults to None.
387
stop (int, optional): cut-off. Defaults to None.
388
389
Returns:
390
list: list of ELGTautClasses
391
"""
392
c = QQ(c)
393
if amb is None:
394
ONE = self._X.ONE
395
amb = ((), 0)
396
else:
397
ONE = self._X.taut_from_graph(*amb)
398
e = [ONE]
399
f = ONE
400
coeff = QQ(1)
401
k = QQ(0)
402
if stop is None:
403
stop = self._X.dim() + 1
404
while k < stop and f != self._X.ZERO:
405
k += 1
406
coeff *= c / k
407
f = self.pow(k, amb)
408
e.append(coeff * f)
409
return e
410
411
def pull_back(self, deg_enh_profile):
412
"""
413
Pull back self to the graph associated to deg_enh_profile.
414
415
Note that this returns an ELGTautClass as there could be several maps.
416
417
More precisely, we return the sum over the pulled back classes divided
418
by the number of undegeneration maps.
419
420
Args:
421
deg_enh_profile (tuple): enhanced profile of graph to pull back to.
422
423
Raises:
424
RuntimeError: raised if deg_enh_profile is not a degeneration of the
425
underlying graph of self.
426
427
Returns:
428
ELGTautClass: sum of pullbacks of self to deg_enh_profile for each
429
undegeneration map divided by the number of such maps.
430
431
"""
432
if self._leg_dict is None:
433
# trivial pullback
434
return admcycles.diffstrata.elgtautclass.ELGTautClass(
435
self._X, [(1, self._X.additive_generator(deg_enh_profile))])
436
else:
437
leg_maps = self._X.explicit_leg_maps(
438
self._enh_profile, deg_enh_profile)
439
if leg_maps is None:
440
raise RuntimeError("Pullback failed: %r is not a degeneration of %r")\
441
% (deg_enh_profile, self._enh_profile)
442
psi_list = []
443
aut_factor = QQ(1) / QQ(len(leg_maps))
444
for leg_map in leg_maps:
445
new_leg_dict = {leg_map[l]: e for l,
446
e in self._leg_dict.items()}
447
psi_list.append(
448
(aut_factor, self._X.additive_generator(
449
deg_enh_profile, new_leg_dict)))
450
return admcycles.diffstrata.elgtautclass.ELGTautClass(
451
self._X, psi_list)
452
453
def psis_on_level(self, l):
454
"""
455
The psi classes on level l of self.
456
457
Args:
458
l (int): level, i.e. 0,...,codim
459
460
Returns:
461
dict: psi dictionary on self.level(l).smooth_LG
462
"""
463
L = self.level(l)
464
# The psi classes on this level should be expressed in terms of the legs
465
# of the smooth_LG of L:
466
EG = L.smooth_LG
467
try:
468
# Careful: the legs of the smooth_LG are numbered 1,...,n
469
# The psi classes are still numbered inside the whole graph
470
# The conversion runs through the embedding of the LevelStratum
471
# and back through the embedding of smooth_LG (dmp_inv)
472
psis = {EG.dmp_inv[L.leg_dict[leg]]: self.leg_dict[leg]
473
for leg in self.inv_level_dict[l]}
474
except KeyError:
475
# no psis on this level
476
psis = {}
477
return psis
478
479
def evaluate(self, quiet=False, warnings_only=False,
480
admcycles_output=False):
481
"""
482
Evaluate self (cap with the fundamental class of self._X).
483
484
Note that this gives 0 if self is not a top-degree class.
485
486
Evaluation works by taking the product of the evaluation of each level
487
(i.e. evaluating, for each level, the psi monomial on this level) and
488
multiplying this with the stack factor.
489
490
The psi monomials on the levels are evaluated using admcycles (after
491
removing residue conditions).
492
493
Raises a RuntimeError if there are inconsistencies with the psi degrees
494
on the levels.
495
496
INPUT:
497
498
quiet: boolean (optional)
499
If set to true, then get no output. Defaults to False.
500
501
warnings_only: boolean (optional)
502
If set to true, then output warnings. Defaults to False.
503
504
admcycles_output: boolean (optional)
505
If set to true, prints debugging info (used when evaluating levels). Defaults to False.
506
507
OUTPUT:
508
509
the integral of self on X as a rational number.
510
"""
511
if self.degree < self._X.dim():
512
if not quiet or warnings_only:
513
print("Warning: %r is not of top degree: %r (instead of %r)" %
514
(self, self.degree, self._X.dim()))
515
return 0
516
level_list = []
517
for l in range(self.codim + 1):
518
if self.degree_on_level(l) < self.level_dim(l):
519
raise RuntimeError(
520
"%r is of top degree, but not on level %r" % (self, l))
521
L = self.level(l)
522
value = L.evaluate(
523
psis=self.psis_on_level(l),
524
quiet=quiet,
525
warnings_only=warnings_only,
526
admcycles_output=admcycles_output)
527
if value == 0:
528
return 0
529
level_list.append(value)
530
# product over levels:
531
prod = 1
532
for p in level_list:
533
prod *= p
534
if not quiet:
535
print("----------------------------------------------------")
536
print("Contribution of Additive generator:")
537
print(self)
538
print("Product of level-wise integrals: %r" % prod)
539
print("Stack factor: %r" % self.stack_factor)
540
print("Total: %r" % (prod * self.stack_factor))
541
print("----------------------------------------------------")
542
return self.stack_factor * prod
543
544
def to_prodtautclass(self, relabel=False):
545
"""
546
Transform self into an admcycles prodtautclass on the underlying stgraph of self.
547
548
Note that this gives the pushforward to M_g,n in the sense that we multiply with
549
Strataclass and remove all residue conditions.
550
551
Returns:
552
prodtautclass: the prodtautclass of self, multiplied with the Strataclasses of
553
the levels and all residue conditions removed.
554
555
EXAMPLES::
556
557
sage: from admcycles.diffstrata import *
558
sage: X=Stratum((2,))
559
sage: X.additive_generator(((),0)).to_prodtautclass()
560
Outer graph : [2] [[1]] []
561
Vertex 0 :
562
Graph : [2] [[1]] []
563
Polynomial : -7/24*(kappa_1)_0 + 79/24*psi_1
564
<BLANKLINE>
565
<BLANKLINE>
566
Vertex 0 :
567
Graph : [1] [[1, 3, 4]] [(3, 4)]
568
Polynomial : -1/48
569
<BLANKLINE>
570
<BLANKLINE>
571
Vertex 0 :
572
Graph : [1, 1] [[3], [1, 4]] [(3, 4)]
573
Polynomial : -19/24
574
sage: from admcycles.stratarecursion import Strataclass
575
sage: X=GeneralisedStratum([Signature((4,-2,-2))], res_cond=[[(0,1)], [(0,2)]])
576
sage: (X.additive_generator(((),0)).to_prodtautclass().pushforward() - Strataclass(1, 1, [4,-2,-2], res_cond=[2])).is_zero()
577
True
578
579
580
TESTS::
581
582
sage: from admcycles import diffstrata, psiclass
583
sage: X = diffstrata.generalisedstratum.GeneralisedStratum(sig_list = [diffstrata.sig.Signature(tuple([8,-3,-2,-3]))], res_cond = [[(0,1)],[(0,2)]])
584
sage: X.psi(1).evaluate()
585
9
586
sage: v = X.ONE.to_prodtautclass().pushforward()
587
sage: (v*psiclass(1,1,4)).evaluate()
588
9
589
590
We noticed the problem that the markings of the prodtautclass of an AdditiveGenerator are usually not the standard one,
591
and hence its pushforward to M_g,n is not comparable to that of other AdditiveGenerator. Thus we solve this
592
by adding the keyword "relabel"::
593
594
sage: X=diffstrata.generalisedstratum.Stratum((4,-2))
595
sage: X.additive_generator(((1,),0)).to_prodtautclass(relabel=True)
596
Outer graph : [1, 0] [[3, 4], [1, 2, 5, 6]] [(3, 5), (4, 6)]
597
Vertex 0 :
598
Graph : [1] [[1, 2]] []
599
Polynomial : 1/2
600
Vertex 1 :
601
Graph : [0] [[1, 2, 3, 4]] []
602
Polynomial : psi_4
603
<BLANKLINE>
604
<BLANKLINE>
605
Vertex 0 :
606
Graph : [1] [[1, 2]] []
607
Polynomial : 1/2
608
Vertex 1 :
609
Graph : [0, 0] [[2, 3, 9], [1, 4, 12]] [(9, 12)]
610
Polynomial : 3
611
<BLANKLINE>
612
<BLANKLINE>
613
Vertex 0 :
614
Graph : [1] [[1, 2]] []
615
Polynomial : 1/2
616
Vertex 1 :
617
Graph : [0, 0] [[3, 4, 9], [1, 2, 12]] [(9, 12)]
618
Polynomial : -3
619
620
621
We fixed the problem that when the ELGTautClass arised from removing residue conditions of a level stratum has only one term, the
622
original algorithm would break down. We can test it::
623
624
sage: X=diffstrata.generalisedstratum.GeneralisedStratum([diffstrata.sig.Signature((0,-1,-1)),diffstrata.sig.Signature((0,-1,-1))], res_cond=[[(0,1),(1,1)]])
625
sage: X.additive_generator(((),0)).to_prodtautclass()
626
Outer graph : [0, 0] [[1, 2, 3], [4, 5, 6]] []
627
Vertex 0 :
628
Graph : [0] [[1, 2, 3]] []
629
Polynomial : 1
630
Vertex 1 :
631
Graph : [0] [[1, 2, 3]] []
632
Polynomial : 1
633
634
"""
635
LG = self._G.LG
636
stgraph = LG.stgraph
637
if any(self.level(l).zeroStratumClass()
638
for l in range(self.codim + 1)):
639
return admcycles.admcycles.prodtautclass(stgraph, terms=[]) # ZERO
640
641
if len(LG.genera) == 1 and self._X.res_cond == []: # just some stratum class without res_cond
642
adm_psis = admcycles.admcycles.decstratum(stgraph, psi=self.leg_dict)
643
adm_psis_taut = admcycles.admcycles.tautclass([adm_psis])
644
sig = self._X._sig_list[0].sig
645
g = self._X._sig_list[0].g
646
stratum_class = admcycles.stratarecursion.Strataclass(g, 1, sig)
647
result = admcycles.admcycles.prodtautclass(stgraph, protaut=[adm_psis_taut * stratum_class])
648
return result
649
650
# Now, we are dealing with nontrivial graphs
651
alpha = [] # prodtautclasses on levels
652
vertices = [] # embedding of level into stgraph
653
for l in range(self.codim + 1):
654
psis = self.psis_on_level(l) # the psi classes on level l
655
T = self.level(l).remove_res_cond(psis)
656
657
# turn to the to_prodtautclass of ELGTautClass which will recursively be solved
658
alpha.append(T.to_prodtautclass())
659
vertices.append(LG.verticesonlevel(LG.internal_level_number(l)))
660
prod = self.stack_factor * admcycles.admcycles.prodtautclass(stgraph)
661
662
for l, ptc in enumerate(alpha):
663
prod = prod.factor_pullback(vertices[l], ptc) # returns product (!)
664
665
if relabel:
666
# we standardise the marking of the EmbeddedLevelGraph and leg_dict for psi
667
standard_legdict = self._G.standard_markings()
668
newEmbLG = self._G.relabel(standard_legdict, tidyup=False)
669
stgraph = newEmbLG.LG.stgraph
670
671
# get the prodtaut under the standardised stable graph
672
prod = admcycles.admcycles.prodtautclass(stgraph, prod.terms)
673
674
return prod
675
676