Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: admcycles
Views: 724
Visibility: Unlisted (only visible to those who know the link)
Image: ubuntu2004
1
from __future__ import absolute_import
2
from __future__ import print_function
3
4
from copy import deepcopy
5
6
# pylint does not know sage
7
from sage.structure.sage_object import SageObject # pylint: disable=import-error
8
from sage.modules.free_module import FreeModule # pylint: disable=import-error
9
from sage.rings.integer_ring import ZZ # pylint: disable=import-error
10
from sage.arith.functions import lcm # pylint: disable=import-error
11
from sage.misc.flatten import flatten # pylint: disable=import-error
12
from sage.functions.generalized import sign # pylint: disable=import-error
13
from sage.misc.cachefunc import cached_method # pylint: disable=import-error
14
from sage.graphs.graph import Graph # pylint: disable=import-error
15
16
from admcycles.admcycles import GraphIsom
17
from admcycles.stable_graph import StableGraph as stgraph
18
import admcycles.diffstrata.levelstratum
19
from admcycles.diffstrata.sig import Signature
20
21
def smooth_LG(sig):
22
"""
23
The smooth (i.e. one vertex) LevelGraph in the stratum (sig).
24
25
Args:
26
sig (Signature): signature of the stratum.
27
28
Returns:
29
LevelGraph: The smooth graph in the stratum.
30
"""
31
return LevelGraph.fromPOlist([sig.g],[list(range(1,sig.n+1))],[],sig.sig,[0])
32
33
class LevelGraph(SageObject):
34
r"""
35
Create a (stable) level graph.
36
37
NOTE: This is a low-level class and should NEVER be invoced directly!
38
Preferably, EmbeddedLevelGraphs should be used and these should be
39
generated automatically by Stratum (or GeneralisedStratum).
40
41
NOTE: We don't inherit from stgraph/StableGraph anymore, as LevelGraphs
42
should be static objects!
43
44
Extends admcycles stgraph to represent a level graph as a stgraph,
45
i.e. a list of genera, a list of legs and a list of edges, plus a list of
46
poleorders for each leg and a list of levels for each vertex.
47
48
Note that the class will warn if the data is not admissible, i.e. if the
49
graph is not stable or the pole orders at separating nodes across levels do
50
not add up to -2 or -1 on the same level (unless this is suppressed with
51
quiet=True).
52
53
INPUT:
54
55
genera : list
56
List of genera of the vertices of length m.
57
legs : list
58
List of length m, where ith entry is list of legs attached to vertex i.
59
By convention, legs are unique positive integers.
60
edges : list
61
List of edges of the graph. Each edge is a 2-tuple of legs.
62
poleorders : dictionary
63
Dictionary of the form leg number : poleorder
64
levels : list
65
List of length m, where the ith entry corresponds to the level of
66
vertex i.
67
By convention, top level is 0 and levels go down by 1, in particular
68
they are NEGATIVE numbers!
69
quiet : optional boolean (default = False)
70
Suppresses most error messages.
71
72
ALTERNATIVELY, the pole orders can be supplied as a list by calling
73
LevelGraph.fromPOlist:
74
poleorders : list
75
List of order of the zero (+) or pole (-) at each leg. The ith element
76
of the list corresponds to the order at the leg with the marking i+1
77
(because lists start at 0 and the legs are positive integers).
78
79
EXAMPLES ::
80
81
Creating a level graph with three components on different levels of genus 1,
82
3 and 0. The bottom level has a horizontal node.
83
84
sage: from admcycles.diffstrata import *
85
sage: G = LevelGraph.fromPOlist([1,3,0],[[1,2],[3,4,5],[6,7,8,9]],[(2,3),(5,6),(7,8)],[2,-2,0,6,-2,0,-1,-1,0],[-2,-1,0]); G # doctest: +SKIP
86
LevelGraph([1, 3, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9]],[(3, 2), (6, 5), (7, 8)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0},[-2, -1, 0],True)
87
88
or alternatively:
89
90
sage: LevelGraph([1, 3, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9]],[(3, 2), (6, 5), (7, 8)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0},[-2, -1, 0],True)
91
LevelGraph([1, 3, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9]],[(3, 2), (6, 5), (7, 8)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0},[-2, -1, 0],True)
92
93
We get a warning if the graph has non-stable components: (not any more ;-))
94
95
sage: G = LevelGraph.fromPOlist([1,3,0,0],[[1,2],[3,4,5],[6,7,8,9],[10]],[(2,3),(5,6),(7,8),(9,10)],[2,-2,0,6,-2,0,-1,-1,0,-2],[-3,-2,0,-1]); G # doctest: +SKIP
96
Warning: Component 3 is not stable: g = 0 but only 1 leg(s)!
97
Warning: Graph not stable!
98
LevelGraph([1, 3, 0, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9], [10]],[(3, 2), (6, 5), (7, 8), (9, 10)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0, 10: -2},[-3, -2, 0, -1],True)
99
"""
100
def __init__(self,genera,legs,edges,poleorders,levels,quiet=False):
101
checks = False
102
if len(genera) != len(legs):
103
raise ValueError('genera and legs must have the same length')
104
self.genera = genera
105
self.legs = legs
106
self.edges = edges
107
self.poleorders = poleorders
108
self.levels = levels
109
# the signature consists of the marked points that are not half-edges
110
sig_list = tuple(poleorders[l] for l in self.list_markings())
111
self.sig = Signature(sig_list)
112
# associated stgraph...
113
self._stgraph = None
114
self.k = 1 # in case we want to implement k-differentials
115
self._init_more() # initiate some things and make sure all is in order
116
if checks:
117
self.checkadmissible(quiet)
118
self._has_long_edge = None
119
self._is_long = {}
120
self._is_bic = None
121
122
@classmethod
123
def fromPOlist(cls,genera,legs,edges,poleordersaslist,levels,quiet=False):
124
"""
125
This gives a LevelGraph where the poleorders are given as a list, not
126
directly as a dictionary.
127
"""
128
# translate poleorder list to dictionary:
129
sortedlegs = sorted(flatten(legs))
130
if len(sortedlegs) != len(poleordersaslist):
131
raise ValueError("Numbers of legs and pole orders do not agree!"+
132
" Legs: " + repr(len(sortedlegs)) +
133
" Pole orders: " + repr(len(poleordersaslist)))
134
poleorderdict = { sortedlegs[i] : poleordersaslist[i] for i in range(len(sortedlegs)) }
135
return cls(genera,legs,edges,poleorderdict,levels,quiet)
136
137
def __repr__(self):
138
return "LevelGraph("+self.input_as_string()+")"
139
140
def __hash__(self):
141
return hash(repr(self))
142
143
def __str__(self):
144
return ("LevelGraph " + repr(self.genera)+ ' ' + repr(self.legs) + ' '
145
+ repr(self.edges) + ' ' + repr(self.poleorders) + ' '
146
+ repr(self.levels))
147
148
def input_as_string(self):
149
"""
150
return a string that can be given as argument to __init__ to give self
151
"""
152
return (repr(self.genera)+ ',' + repr(self.legs) + ','
153
+ repr(self.edges) + ',' + repr(self.poleorders) + ','
154
+ repr(self.levels)+',True')
155
156
def __eq__(self, other):
157
if not isinstance(other, LevelGraph):
158
return False
159
return (self.genera==other.genera) and (self.levels == other.levels) and (self.legs == other.legs) and (set(self.edges)==set(other.edges)) and (self.poleorders == other.poleorders)
160
161
## reimplementing the admcycles stuff we use:
162
@cached_method
163
def g(self):
164
genus = sum(self.genera) + len(self.edges) - len(self.genera) + ZZ.one()
165
assert genus == self.sig.g, "Signature %r does not match %r!" % (self.sig,self)
166
return genus
167
168
@property
169
def stgraph(self):
170
if self._stgraph is None:
171
self._stgraph = stgraph([int(g) for g in self.genera],
172
[[int(l) for l in leg_list] for leg_list in self.legs],
173
self.edges)
174
return self._stgraph
175
176
@cached_method
177
def list_markings(self,v=None):
178
r"""
179
Return the list of markings (non-edge legs) of self at vertex v.
180
"""
181
if v is None:
182
return tuple([j for v in range(len(self.genera))
183
for j in self.list_markings(v)])
184
s = set(self.legs[v])
185
for e in self.edges:
186
s -= set(e)
187
return tuple(s)
188
189
##########################################################
190
#### Interface:
191
#### --- provide a series of methods that translate various data
192
#### (maybe this can be optimised by smart caching later, so only
193
#### these should be used...)
194
#### up to now, we have:
195
#### * vertex : (overloaded from admcycles)
196
#### INPUT : leg
197
#### returns : vertex of leg (index of genera)
198
#### * vertex_list :
199
#### returns : list of vertices (index of genera)
200
#### * edges_list :
201
#### returns : list of edges
202
#### * prongs_list :
203
#### returns : list of tuples (edge,prong)
204
#### * levelofvertex :
205
#### INPUT : index of genera
206
#### returns : level number (negative)
207
#### * levelofleg :
208
#### INPUT : leg label (given as positive integer)
209
#### returns : level number (negative)
210
#### * orderatleg :
211
#### INPUT : leg label (given as positive integer)
212
#### returns : poleorder (integer)
213
#### * ordersonvertex :
214
#### INPUT : index of genera
215
#### returns : list of poleorders (integers)
216
#### * verticesonlevel :
217
#### INPUT : level number (negative)
218
#### returns : list of indices of genera
219
#### * edgesatvertex :
220
#### INPUT : index of genera
221
#### returns : list of edges
222
#### * legsatvertex :
223
#### INPUT : index of genera
224
#### returns : list of legs
225
#### * simplepolesatvertex :
226
#### INPUT : index of genera
227
#### returns : list of legs with pole order -1
228
#### * genus :
229
#### INPUT : index of genera
230
#### returns : genus
231
#### * codim :
232
#### returns : codimension
233
#### * is_bic :
234
#### returns : boolean
235
#### * edgesatlevel :
236
#### INPUT : level number (negative)
237
#### returns : list of edges with at least one node at that level (or horizontal)
238
#### * horizontaledgesatlevel :
239
#### INPUT : level number (negative)
240
#### returns : list of horizontal edges
241
#### * prong :
242
#### INPUT : edge
243
#### returns : prong order at edge
244
#### * nextlowerlevel :
245
#### INPUT : level number
246
#### returns : level number of next lower level
247
#### * lowest_level :
248
#### returns : level number of lowest level
249
#### * is_horizontal :
250
#### INPUT : edge (or none for whole graph)
251
#### returns : boolean success
252
#### * has_loop :
253
#### INPUT : vertex
254
#### returns : boolean success
255
#### * edgesgoingupfromlevel :
256
#### INPUT : level
257
#### returns : list of edges with e[1] on level
258
#### * verticesabove :
259
#### INPUT : level
260
#### returns : list of vertices with level > l
261
#### * edgesabove :
262
#### INPUT : level
263
#### returns : list of edges with level of each vertex > l
264
#### * crosseslevel :
265
#### INPUT : edge, level
266
#### returns : boolean
267
#### * edgesgoingpastlevel :
268
#### INPUT : level
269
#### returns : list of edges with start level > l and
270
#### end level < l
271
#### * _pointtype :
272
#### INPUT : order (integer)
273
#### returns : pole/marked point/zero (string)
274
#### * is_meromorphic :
275
#### returns : boolean (check for pole among marked points)
276
#### * highestorderpole :
277
#### INPUT : vertex
278
#### returns : leg name (at v) or -1 if none found
279
#########################################################
280
@cached_method
281
def vertex(self,leg):
282
"""
283
The vertex (as index of genera) where leg is located.
284
285
Args:
286
leg (int): leg
287
288
Returns:
289
int: index of genera
290
"""
291
return self.legdict[leg]
292
@cached_method
293
def vertex_list(self):
294
return list(range(len(self.genera)))
295
@cached_method
296
def edges_list(self):
297
return self.edges
298
@cached_method
299
def prongs_list(self):
300
return [(e,p) for e,p in self.prongs.items()]
301
@cached_method
302
def levelofvertex(self,v):
303
return self.levels[v]
304
@cached_method
305
def levelofleg(self,leg):
306
return self.levelofvertex(self.vertex(leg))
307
@cached_method
308
def orderatleg(self,leg):
309
return self.poleorders[leg]
310
@cached_method
311
def ordersonvertex(self,v):
312
return [self.orderatleg(leg) for leg in self.legsatvertex(v)]
313
@cached_method
314
def verticesonlevel(self,level):
315
return [v for v in range(len(self.genera)) if self.levelofvertex(v) == level]
316
@cached_method
317
def edgesatvertex(self,v):
318
return [e for e in self.edges if self.vertex(e[0]) == v or
319
self.vertex(e[1]) == v]
320
@cached_method
321
def legsatvertex(self,v):
322
return self.legs[v]
323
@cached_method
324
def is_halfedge(self,leg):
325
"""
326
Check if leg has an edge attached to it.
327
"""
328
for e in self.edges:
329
if e[0] == leg or e[1] == leg:
330
return True
331
return False
332
@cached_method
333
def simplepolesatvertex(self, v):
334
return [l for l in self.legsatvertex(v) if self.orderatleg(l) == -1]
335
336
@cached_method
337
def genus(self, v=None):
338
"""
339
Return the genus of vertex ``v``.
340
341
If ``v`` is ``None``, return genus of the complete ``LevelGraph``.
342
"""
343
if v is None:
344
return self.g() # from admcycles
345
else:
346
return self.genera[v]
347
348
@cached_method
349
def numberoflevels(self):
350
return len(set(self.levels))
351
def is_bic(self):
352
if self._is_bic is None:
353
self._is_bic = self.numberoflevels() == 2 and not self.is_horizontal()
354
return self._is_bic
355
@cached_method
356
def codim(self):
357
"""
358
Return the codim = No. of levels of self + horizontal edges.
359
"""
360
return (self.numberoflevels() - 1
361
+ len([e for e in self.edges if self.is_horizontal(e)]))
362
@cached_method
363
def edgesatlevel(self,level):
364
"""
365
Return list of edges with at least one node at level.
366
"""
367
return [e for e in self.edges if self.levelofleg(e[0]) == level or
368
self.levelofleg(e[1]) == level]
369
@cached_method
370
def horizontaledgesatlevel(self,level):
371
return [e for e in self.edgesatlevel(level) if self.is_horizontal(e)]
372
@cached_method
373
def prong(self, e):
374
"""
375
The prong order is the pole order of the higher-level pole +1
376
"""
377
if self.levelofleg(e[0]) > self.levelofleg(e[1]):
378
return self.orderatleg(e[0]) + 1
379
else:
380
return self.orderatleg(e[1]) + 1
381
@cached_method
382
def nextlowerlevel(self,l):
383
"""
384
Return the next lower level number or False if no legal level
385
(e.g. lowest level) is given as input.
386
387
Point of discussion: Is it better to tidy up the level numbers
388
to be directly ascending or not?
389
390
Pro tidy: * this becomes obsolete ;)
391
* easier to check isomorphisms?
392
Con tidy: * we "see" where we came from
393
* easier to undo/glue back in after cutting
394
"""
395
try:
396
llindex = self.sortedlevels.index(l) - 1
397
except ValueError:
398
return False
399
if llindex == -1:
400
return False
401
else:
402
return self.sortedlevels[llindex]
403
@cached_method
404
def internal_level_number(self,i):
405
"""
406
Return the internal i-th level, e.g.
407
408
self.levels = [0,-2,-5,-3]
409
410
then
411
412
internal_level_number(0) is 0
413
internal_level_number(1) is -2
414
internal_level_number(2) is -3
415
internal_level_number(3) is -4
416
417
Returns None if the level does not exist.
418
"""
419
reference_levels = list(reversed(sorted(list(set(self.levels)))))
420
i = abs(i)
421
if i >= len(reference_levels):
422
return None
423
else:
424
return reference_levels[i]
425
@cached_method
426
def level_number(self,l):
427
"""
428
Return the relative level number (positive!) of l, e.g.
429
430
self.levels = [0,-2,-5,-3]
431
432
then
433
434
level_number(0) is 0
435
level_number(-2) is 1
436
level_number(-3) is 2
437
level_number(-5) is 3
438
439
Returns None if the level does not exist.
440
"""
441
reference_levels = list(reversed(sorted(list(set(self.levels)))))
442
try:
443
return reference_levels.index(l)
444
except ValueError:
445
return None
446
@cached_method
447
def is_long(self,e):
448
"""
449
Check if edge e is long, i.e. passes through more than one level.
450
"""
451
try:
452
return self._is_long[e]
453
except KeyError:
454
il = abs(self.level_number(self.levelofleg(e[0])) - self.level_number(self.levelofleg(e[1]))) > 1
455
self._is_long[e] = il
456
return il
457
@cached_method
458
def has_long_edge(self):
459
if self._has_long_edge is None:
460
for e in self.edges:
461
if self.is_long(e):
462
self._has_long_edge = True
463
break
464
else:
465
self._has_long_edge = False
466
return self._has_long_edge
467
@cached_method
468
def lowest_level(self):
469
return self.sortedlevels[0]
470
@cached_method
471
def is_horizontal(self,e=None):
472
"""
473
Check if edge e is a horizontal edge or if self has a horizontal edge.
474
"""
475
if e is None:
476
for e in self.edges:
477
if self.is_horizontal(e):
478
return True
479
return False
480
if not (e in self.edges):
481
print("Warning: " + repr(e) + " is not an edge of this graph!")
482
return False
483
if self.levelofleg(e[0]) == self.levelofleg(e[1]):
484
return True
485
else:
486
return False
487
@cached_method
488
def has_loop(self,v):
489
"""
490
Check if vertex v has a loop.
491
"""
492
for e in self.edgesatvertex(v):
493
if self.vertex(e[0]) == self.vertex(e[1]):
494
return True
495
return False
496
@cached_method
497
def edgesgoingupfromlevel(self,l):
498
"""
499
Return the edges going up from level l.
500
501
This uses that e[0] is not on a lower level than e[1]!
502
"""
503
return [e for e in self.edges if self.levelofleg(e[1])==l and
504
not self.is_horizontal(e)]
505
@cached_method
506
def verticesabove(self,l):
507
"""
508
Return list of all vertices above level l.
509
510
If l is None, return all vertices.
511
"""
512
if l is None:
513
return list(range(len(self.genera)))
514
return [v for v in range(len(self.genera)) if self.levelofvertex(v) > l]
515
@cached_method
516
def edgesabove(self,l):
517
"""
518
Return a list of all edges above level l, i.e. start and end vertex
519
have level > l.
520
"""
521
return [e for e in self.edges if self.levelofleg(e[0]) > l
522
and self.levelofleg(e[1]) > l]
523
@cached_method
524
def crosseslevel(self,e,l):
525
"""
526
Check if e crosses level l (i.e. starts > l and ends <= l)
527
"""
528
return self.levelofleg(e[0]) > l and self.levelofleg(e[1]) <= l
529
@cached_method
530
def edgesgoingpastlevel(self,l):
531
"""
532
Return list of edges that go from above level l to below level l.
533
"""
534
return [e for e in self.edges if self.levelofleg(e[0]) > l
535
and self.levelofleg(e[1]) < l]
536
@cached_method
537
def _pointtype(self,order):
538
if order < 0:
539
return "pole"
540
elif order == 0:
541
return "marked point"
542
else:
543
return "zero"
544
@cached_method
545
def is_meromorphic(self):
546
"""
547
Returns True iff at least one of the MARKED POINTS is a pole.
548
"""
549
return any(self.orderatleg(l) < 0 for l in self.list_markings())
550
@cached_method
551
def highestorderpole(self,v):
552
"""
553
Returns the leg with the highest order free pole at v, -1 if no poles found.
554
"""
555
minorder = 0
556
leg = -1
557
for l in self.list_markings(v):
558
if self.orderatleg(l) < minorder:
559
minorder = self.orderatleg(l)
560
leg = l
561
return leg
562
#########################################################
563
#### end interface
564
#########################################################
565
566
#########################################################
567
#### Checks
568
#########################################################
569
@cached_method
570
def checkadmissible(self,quiet=False):
571
"""
572
Run all kinds of checks on the level graph. Currently:
573
* Check if orders on each komponent add up to k*(2g-2)
574
* Check if graph is stable
575
* Check if orders at edges sum to -2
576
* Check if orders respect level crossing
577
"""
578
admissible = True
579
if not self.checkorders(quiet):
580
if not quiet:
581
print("Warning: Illegal orders!")
582
admissible = False
583
if not self.is_stable(quiet):
584
if not quiet:
585
print("Warning: Graph not stable!")
586
admissible = False
587
if not self.checkedgeorders(quiet):
588
if not quiet:
589
print("Warning: Illegal orders at a node!")
590
admissible = False
591
return admissible
592
593
@cached_method
594
def checkorders(self,quiet=False):
595
"""
596
Check if the orders add up to k*(2g-2) on each component.
597
"""
598
for v, g in enumerate(self.genera):
599
if sum(self.ordersonvertex(v)) != self.k*(2*g-2):
600
if not quiet:
601
print("Warning: Component " + repr(v) + " orders add up to " +
602
repr(sum(self.ordersonvertex(v))) +
603
" but component is of genus " + repr(g))
604
return False
605
return True
606
607
@cached_method
608
def is_stable(self,quiet=False):
609
"""
610
Check if graph is stable.
611
"""
612
# total graph:
613
e = 2*self.genus() - 2 + len(self.leglist)
614
if e < 0:
615
if not quiet:
616
print("Warning: Total graph not stable! 2g-2+n = " + repr(e))
617
return False
618
619
# components:
620
for v in range(len(self.genera)):
621
if 3*self.genus(v) - 3 + len(self.legsatvertex(v)) < 0:
622
if not quiet:
623
print("Warning: Component " + repr(v) + " is not stable: g = "
624
+ repr(self.genus(v)) + " but only "
625
+ repr(len(self.legsatvertex(v))) + " leg(s)!")
626
return False
627
return True
628
629
@cached_method
630
def checkedgeorders(self,quiet=False):
631
"""
632
Check that the orders at nodes (i.e. edges) sum to -1 and that lower
633
level has lower order.
634
"""
635
for e in self.edges:
636
orders = self.orderatleg(e[0]) + self.orderatleg(e[1])
637
if orders != -2:
638
if not quiet:
639
print("Warning: Orders at edge " + repr(e) + " add up to " +
640
repr(orders) + " instead of -2!")
641
return False
642
# iff the pole order at e[0] is > the poleorder at e[1], e[0] should be on a higher level than e[1]
643
if (sign(self.orderatleg(e[0]) - self.orderatleg(e[1]))
644
!= sign(self.levelofleg(e[0]) - self.levelofleg(e[1]))):
645
if not quiet:
646
print("Warning: Orders at edge " + repr(e) +
647
" do not respect level crossing!")
648
print("Poleorders are",
649
(self.orderatleg(e[0]),self.orderatleg(e[1])),
650
"but levels are",
651
(self.levelofleg(e[0]),self.levelofleg(e[1]))
652
)
653
return False
654
return True
655
656
#################################################################
657
### End checks
658
#################################################################
659
660
#################################################################
661
### Cleanup functions
662
### USE ONLY IN __init__!!! LevelGraphs should be static!!!
663
#################################################################
664
def _gen_prongs(self):
665
"""
666
Generate the dictionary edge : prong order.
667
The prong order is the pole order of the higher-level pole +1
668
"""
669
return { e : self.prong(e) for e in self.edges }
670
671
def _init_more(self):
672
"""
673
This should be used _only_ for intialisation!
674
675
Make sure everything is in order, in particular:
676
* sortedlevels is fine
677
* leglist is fine
678
* legdict is fine
679
* maxleg is fine
680
* maxlevel is fine
681
* prongs are fine
682
* underlying graph is fine
683
"""
684
self.sortedlevels = sorted(self.levels)
685
# legs is a nested list:
686
self.leglist = flatten(self.legs)
687
# legs as dictionary
688
self.legdict = { l:v for v in range(len(self.genera))
689
for l in self.legs[v] }
690
self.maxleg = max([max(j+[0]) for j in self.legs])
691
# maxlevel is named a bit misleading, as it is the LOWEST level
692
# (the max of the absolute values...)
693
self.maxlevel = max([abs(l) for l in self.levels])
694
# the prong orders are stored in a dictionary edge : prong order
695
self.prongs = self._gen_prongs()
696
# construct the "underlying graph" (as a sage graph)
697
# the edges are labeled by their "real" name,
698
# the vertices are of the form (i,g_i,'LG')
699
# NOTE: In the case of an EmbeddedLevelGraph, vertices "at infinity" might
700
# be added to this!
701
self.underlying_graph = Graph([[self.UG_vertex(i) for i in range(len(self.genera))],
702
[(self.UG_vertex(self.vertex(e[0])), self.UG_vertex(self.vertex(e[1])), e)
703
for e in self.edges]],
704
format='vertices_and_edges', loops=True, multiedges=True)
705
706
def UG_vertex(self, v):
707
"""
708
Vertex of the underlying graph corresponding to v.
709
710
Args:
711
v (int): vertex number (index of self.genera)
712
713
Returns:
714
tuple: tuple encoding the vertex of the Underlying Graph.
715
"""
716
return (v, self.genera[v], 'LG')
717
718
#################################################################
719
#### Extract a subgraph
720
#################################################################
721
def extract(self,vertices,edges):
722
"""
723
Extract the subgraph of self (as a LevelGraph) consisting of vertices
724
(as list of indices) and edges (as list of edges).
725
726
Returns the levelgraph consisting of vertices, edges and all legs on
727
vertices (of self) with their original poleorders and the original
728
level structure.
729
"""
730
newvertices = deepcopy([self.genera[i] for i in vertices])
731
newlegs = deepcopy([self.legs[i] for i in vertices])
732
newedges = deepcopy(edges)
733
newpoleorders = deepcopy({l : self.orderatleg(l)
734
for l in flatten(newlegs)})
735
newlevels = deepcopy([self.levels[i] for i in vertices])
736
return LevelGraph(newvertices,newlegs,newedges,newpoleorders,newlevels)
737
738
def levelGraph_from_subgraph(self,G):
739
"""
740
Returns the LevelGraph associated to a subgraph of underlying_graph
741
(with the level structure induced by self)
742
"""
743
return self.extract(G.vertices(),G.edge_labels())
744
745
def stratum_from_level(self,l,excluded_poles=None):
746
"""
747
Return the LevelStratum at (relative) level l.
748
749
Args:
750
l (int): relative level number (0,...,codim)
751
excluded_poles (tuple, defaults to None): a list of poles (legs of the graph,
752
that are marked points of the stratum!) to be ignored for r-GRC.
753
754
Returns:
755
LevelStratum: the LevelStratum, i.e.
756
* a list of Signatures (one for each vertex on the level)
757
* a list of residue conditions, i.e. a list [res_1,...,res_r]
758
where each res_k is a list of tuples [(i_1,j_1),...,(i_n,j_n)]
759
where each tuple (i,j) refers to the point j (i.e. index) on the
760
component i and such that the residues at these points add up
761
to 0.
762
* a dictionary of legs, i.e. n -> (i,j) where n is the original
763
number of the point (on the LevelGraph self) and i is the
764
number of the component, j the index of the point in the signature tuple.
765
Note that LevelStratum is a GeneralisedStratum together with
766
a leg dictionary.
767
"""
768
if self.is_horizontal():
769
print("Error: Cannot extract levels of a graph with horizontal edges!")
770
return None
771
internal_l = self.internal_level_number(l)
772
# first, we extract the obvious information from self at this level:
773
vv = self.verticesonlevel(internal_l)
774
legs_on_level = []
775
sigs = []
776
res_cond = []
777
leg_dict = {}
778
for i,v in enumerate(vv):
779
legs_on_level += [deepcopy(self.legsatvertex(v))]
780
leg_dict.update([(n,(i,j)) for j,n in enumerate(legs_on_level[-1])])
781
sig_v = Signature(tuple(self.orderatleg(leg) for leg in legs_on_level[-1]))
782
sigs += [sig_v]
783
# Now we hunt for residue conditions.
784
# We work with the underlying graph to include the "level at infinity":
785
# We "cheat" here, to catch the edges going up from level l: These are the
786
# edges of the graph above level l-1 that have a vertex on level l:
787
# (see below for parsing of these edges)
788
ee = []
789
for e in self.UG_above_level(internal_l - 1).edges():
790
for UG_vertex in e[:2]:
791
if UG_vertex[2] == 'res':
792
continue
793
if self.levelofvertex(UG_vertex[0]) == internal_l:
794
ee.append(e)
795
break
796
G = self.UG_above_level(internal_l)
797
conn_comp = G.connected_components_subgraphs()
798
# Residue conditions are obtained by sorting the poles on this level
799
# by connected component of G they are attached to (note that this
800
# includes the "level at infinity"!)
801
for c in conn_comp:
802
# if there is a free pole in c, we ignore this component
803
if self._redeemed_by_merom_in_comp(c,excluded_poles=excluded_poles):
804
continue
805
res_cond_c = []
806
# Otherwise we record all poles to attached to this component in one
807
# residue condition:
808
for e in ee:
809
# We work with edges of the underlying graph here, so we have to
810
# be a bit more careful. Recall that edges of the underlying graph are of
811
# the form:
812
# * (UG_vertex, UG_vertex, edge name) if it's an edge of LG or
813
# * (UG_vertex, UG_vertex, resiedgej) if it's an edge to level infinity
814
# Here UG_vertex is either of the form (vertex number, genus, 'LG') or
815
# (i, 0, 'res') where
816
# i is the number of the vertex at infinity and resiedgej is the edge
817
# connecting the residue i to the leg j (of self).
818
if e[0] in c or e[1] in c:
819
# res_cond contains tuples (i,j) where i is the index of the
820
# component and j the index of the pole in sig_i.
821
#
822
# We need find the vertex on this level (i.e. not in c)
823
if e[0] in c:
824
UG_vertex = e[1]
825
else:
826
UG_vertex = e[0]
827
# to find the pole on v, we have to distinguish if this is an
828
# edge of self or if it connects to a residue at infinity:
829
edge = e[2]
830
if 'res' in edge:
831
# the leg is the number after 'edge' in the edge string:
832
_, leg = edge.split('edge')
833
leg = int(leg)
834
else:
835
# edge consists of (leg_top,leg_bot) and leg_bot is on level i:
836
leg = edge[1]
837
# We retrieve the stratum point from leg_dict:
838
res_cond_c += [leg_dict[leg]]
839
if res_cond_c:
840
res_cond += [res_cond_c]
841
return admcycles.diffstrata.levelstratum.LevelStratum(sigs,res_cond,leg_dict)
842
843
def UG_vertices_above_level(self,l):
844
"""
845
The vertices above (internal) level l (including possible vertices at infinity).
846
847
Used for checking residue conditions.
848
849
Args:
850
l (int): internal level number
851
852
Returns:
853
list: list of vertices of self.underlying_graph
854
"""
855
# note that verticesabove checks for >l
856
vertices_above = [self.UG_vertex(i) for i in self.verticesabove(l)]
857
vertices_above += [v for v in self.underlying_graph.vertices() if v[2] == 'res']
858
return vertices_above
859
860
def UG_above_level(self,l):
861
"""
862
Subgraph of Underlying Graph (including vertices at infinity) (strictly) above
863
(relative!) level l.
864
865
Args:
866
l (int): relative level number
867
868
Returns:
869
SAGE Graph: subgraph of self.underlying_graph with all vertices strictly
870
above level l.
871
"""
872
internal_l = self.internal_level_number(l)
873
vertices = self.UG_vertices_above_level(internal_l)
874
abovegraph = self.underlying_graph.subgraph(vertices)
875
return abovegraph
876
877
#################################################################
878
#### Check Residue condition (via inconvenient vertices/edges)
879
#################################################################
880
881
@cached_method
882
def is_inconvenient_vertex(self,v):
883
"""
884
Check if vertex is inconvenient, i.e.
885
* g = 0
886
* no simple poles
887
* there exists a pole order m_i such that m_i > sum(m_j)-p-1
888
Return boolean
889
"""
890
# inconvenient vertices are of genus 0 and have no simple poles.
891
if self.genus(v) > 0 or len(self.simplepolesatvertex(v)) > 0:
892
return False
893
# check inequality
894
ll = self.legsatvertex(v)
895
poles = [l for l in ll if self.orderatleg(l) < 0]
896
polesum = sum([-self.orderatleg(l) for l in poles])
897
p = len(poles)
898
for l in ll:
899
if self.orderatleg(l) > polesum - p - 1:
900
return True
901
return False
902
903
def _redeemed_by_merom_in_comp(self,G,excluded_poles=None):
904
"""
905
Check if there is a pole in the subgraph G (intended to be a connected
906
component above a vertex).
907
908
excluded_poles are ignored (for r-GRC).
909
910
Returns boolean (True = exists a pole)
911
"""
912
if excluded_poles is None:
913
excluded_poles = []
914
for w in G.vertices():
915
if w[2] == 'res': # vertex at infinity
916
continue
917
for l in self.list_markings(w[0]):
918
if (self.orderatleg(l) < 0) and (not l in excluded_poles):
919
return True
920
return False
921
922
@cached_method
923
def is_illegal_vertex(self,v,excluded_poles=None):
924
"""
925
Check if vertex is inconvenient and is not redeemed, i.e.
926
* v is inconvenient
927
* there are no two seperate edges to the same connected component above (i.e. loop above)
928
* if meromorphic: v is not connected to higher level marked poles
929
930
We can also pass a tuple (hashing!) of poles that are to be excluded because of
931
residue conditions.
932
933
Return boolean
934
"""
935
if not self.is_inconvenient_vertex(v):
936
return False
937
l = self.levelofvertex(v)
938
# in the underlying_graph, vertices are of the form (index in genera, genus, 'LG'):
939
v_graph = self.UG_vertex(v)
940
# edges not going down from v:
941
ee = [e for e in self.edgesatvertex(v) if self.levelofleg(e[0]) >= l
942
and self.levelofleg(e[1]) >= l]
943
# in the holomorphic case, legal inconvenient vertices need at least two edges not going down
944
if len(ee) < 1 and not self.is_meromorphic():
945
return True
946
# check if v has two edges into one connected component that don't lie below v,
947
# i.e. if v can be redeemed:
948
abovegraph = self.UG_above_level(l-1)
949
cc = self.underlying_graph.subgraph([w
950
for w in abovegraph.connected_component_containing_vertex(v_graph)
951
if w[2] == 'res' or self.levelofvertex(w[0]) >= l])
952
cc.delete_vertex(v_graph)
953
conn_comp = cc.connected_components_subgraphs()
954
if excluded_poles is None:
955
excluded_poles = []
956
freepoles = len([l for l in self.list_markings(v)
957
if (self.orderatleg(l) < 0) and (not l in excluded_poles)])
958
for G in conn_comp:
959
# edges from v to G:
960
# (Careful: cc does not have any edges with v anymore, so we must use abovegraph!)
961
eeG = [e for e in abovegraph.edges()
962
if (e[0] == v_graph and e[1] in G.vertices())
963
or (e[1] == v_graph and e[0] in G.vertices())]
964
if len(eeG) > 1:
965
# redeemed :-)
966
return False
967
# in the meromorphic case, we also check if a "free" pole exists in a connected component above v
968
if self.is_meromorphic() and self._redeemed_by_merom_in_comp(G,excluded_poles=excluded_poles):
969
freepoles += 1
970
if freepoles >= 2:
971
# redeemed :-)
972
return False
973
return True
974
975
@cached_method
976
def is_illegal_edge(self,e,excluded_poles=None):
977
"""
978
Check if edge is illegal, i.e. if
979
* e is horizontal (not loop) and
980
* there no simple loop over e
981
* there is no "free" pole over each end point
982
983
excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.
984
985
Return boolean
986
"""
987
if not self.is_horizontal(e) or (e[0] == e[1]):
988
return False
989
# check if there is a simple loop above e, i.e. if the subgraph
990
# above e is still connected after e is removed (e is not a bridge)
991
l = self.levelofleg(e[0])
992
# note that verticesabove checks for >l
993
abovegraph = self.UG_above_level(l-1)
994
# edges are encoded by (self.vertex(e[0]),self.vertex(e[1]),e) in
995
# underlying_graph!
996
cut = abovegraph.is_cut_edge((self.UG_vertex(self.vertex(e[0])), self.UG_vertex(self.vertex(e[1])), e))
997
# True = e is a cut-edge, i.e. connected components increase when removing e, i.e. no loop above,
998
# i.e. illegal unless also meromorphic
999
# False = graph has a loop, i.e. not illegal
1000
if not cut:
1001
return False
1002
else:
1003
if self.is_meromorphic():
1004
# if there are "free" poles above the endpoints, we are still fine
1005
abovegraph.delete_edge((self.UG_vertex(self.vertex(e[0])), self.UG_vertex(self.vertex(e[1])), e))
1006
polesfound = 0
1007
for l in e:
1008
v = self.vertex(l)
1009
G = abovegraph.connected_component_containing_vertex(self.UG_vertex(v))
1010
if self._redeemed_by_merom_in_comp(abovegraph.subgraph(G),excluded_poles=excluded_poles):
1011
polesfound += 1
1012
if polesfound == 2:
1013
# we are fine
1014
return False
1015
# no redemption :/
1016
return True
1017
1018
@cached_method
1019
def is_legal(self,quiet=False,excluded_poles=None):
1020
"""
1021
Check if self has illegal vertices or edges.
1022
1023
excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.
1024
1025
Return boolean
1026
"""
1027
for v in range(len(self.genera)):
1028
if self.is_illegal_vertex(v,excluded_poles=excluded_poles):
1029
if not quiet:
1030
print("Vertex", v, "is illegal!")
1031
return False
1032
for e in self.edges:
1033
if self.is_illegal_edge(e,excluded_poles=excluded_poles):
1034
if not quiet:
1035
print("Edge", e, "is illegal!")
1036
return False
1037
return True
1038
1039
#################################################################
1040
#### Squishing ---- i.e. contracting horizontal edges / levels
1041
#################################################################
1042
1043
def squish_horizontal(self,e,adddata=False):
1044
"""
1045
Squish the horizontal edge e and return the new graph.
1046
If adddata=True is specified, we additionally return the level graph
1047
that was removed together with the data needed to glue it back in,
1048
i.e. such that composition with split_horizontal is the identity.
1049
1050
More precisely: return a tuple (G,(v,dl,lg,loop)) consisting of
1051
* the contracted LevelGraph G
1052
* the "remaining" vertex v
1053
* a dictionary dl remembering how the legs were split between the
1054
original vertices
1055
* a list lg consisting of the genera of the original vertices
1056
* a boolean loop
1057
"""
1058
# make sure v <= w
1059
[v,w] = sorted([self.vertex(e[0]),self.vertex(e[1])])
1060
1061
# generate extra data
1062
if adddata:
1063
if v == w:
1064
loop = True
1065
lg = [self.genus(v)]
1066
else:
1067
loop = False
1068
lg = [self.genus(v),self.genus(w)]
1069
dl = dict([(l,0) for l in self.legsatvertex(v)] +
1070
[(l,1) for l in self.legsatvertex(w)])
1071
del dl[e[0]]
1072
del dl[e[1]]
1073
extradata = (v,dl,lg,loop)
1074
1075
if not self.is_horizontal(e):
1076
print("Warning: " + repr(e) + " is not a horizontal edge -- Not contracting.")
1077
if adddata:
1078
return (self,extradata)
1079
else:
1080
return self
1081
1082
newgenera = deepcopy(self.genera)
1083
newlegs = deepcopy(self.legs)
1084
newedges = deepcopy(self.edges)
1085
newpoleorders = deepcopy(self.poleorders)
1086
newlevels = deepcopy(self.levels)
1087
if v == w:
1088
# --horizontal loop getting contracted---
1089
# add genus to node:
1090
newgenera[v] += 1
1091
else:
1092
# --horizontal edge getting contracted---
1093
# transfer genus and legs from w to v:
1094
newgenera[v] += newgenera[w]
1095
newlegs[v] += newlegs[w]
1096
# remove all traces of w
1097
newgenera.pop(w)
1098
newlegs.pop(w)
1099
newlevels.pop(w)
1100
# remove edge:
1101
newedges.remove(e)
1102
# remove legs
1103
newlegs[v].remove(e[0])
1104
newlegs[v].remove(e[1])
1105
# remove poleorders
1106
del newpoleorders[e[1]]
1107
del newpoleorders[e[0]]
1108
1109
returngraph = LevelGraph(newgenera,newlegs,newedges,newpoleorders,newlevels,True)
1110
1111
if adddata:
1112
return (returngraph,extradata)
1113
else:
1114
return returngraph
1115
1116
def squish_vertical_slow(self,l,adddata=False,quiet=False):
1117
"""
1118
Squish the level l (and the next lower level!) and return the new graph.
1119
If addata=True is specified, we additionally return a boolean that tells
1120
us if a level was squished and the legs that no longer exist.
1121
1122
More precisely, adddata=True returns a tuple (G,boolean,legs) where
1123
* G is the (possibly non-contracted) graph and
1124
* boolean tells us if a level was squished
1125
* legs is the (possibly empty) list of legs that don't exist anymore
1126
1127
Implementation:
1128
At the moment, we remember all the edges (ee) that connect level l to
1129
level l-1. We then create a new LevelGraph with the same data as self,
1130
the only difference being that all vertices of level l-1 have now been
1131
assigned level l. We then remmove all (now horizontal!) edges in ee.
1132
1133
In particular, all points and edges retain their numbering (only the level might have changed).
1134
1135
WARNING: Level l-1 does not exist anymore afterwards!!!
1136
1137
Downside: At the moment we get a warning for each edge in ee, as these
1138
don't have legal pole orders for being horizontal (so checkedgeorders
1139
complains each time the constructor is called :/).
1140
"""
1141
if l is None:
1142
return self
1143
ll = self.nextlowerlevel(l)
1144
if ll is False:
1145
if not quiet:
1146
print("Warning: Illegal level to contract: " + repr(l))
1147
if adddata:
1148
return (self,False,None)
1149
else:
1150
return self
1151
1152
if not quiet:
1153
print("Squishing levels", l, "and", ll)
1154
1155
vv = self.verticesonlevel(ll)
1156
# edges that go to next lower level, i.e. will be contracted
1157
ee = [e for e in self.edgesatlevel(l) if self.levelofleg(e[1]) == ll
1158
and not self.is_horizontal(e)]
1159
1160
if not quiet:
1161
print("Contracting edges", ee)
1162
1163
newgenera = deepcopy(self.genera)
1164
newlegs = deepcopy(self.legs)
1165
newedges = deepcopy(self.edges)
1166
newpoleorders = deepcopy(self.poleorders)
1167
newlevels = deepcopy(self.levels)
1168
1169
# raise level
1170
for v in vv:
1171
newlevels[v] = l
1172
returngraph = LevelGraph(newgenera,newlegs,newedges,newpoleorders,newlevels,True)
1173
# contract edges that are now horizontal:
1174
for e in ee:
1175
returngraph = returngraph.squish_horizontal(e)
1176
1177
if adddata:
1178
return (returngraph,True,flatten(ee))
1179
else:
1180
return returngraph
1181
1182
def squish_vertical(self,l,adddata=False,quiet=True):
1183
"""
1184
Squish the level l (and the next lower level!) and return the new graph.
1185
1186
Implementation:
1187
At the moment, we remember all the edges (ee) that connect level l to
1188
level l-1. We then create a new LevelGraph with the same data as self,
1189
the only difference being that all vertices of level l-1 have now been
1190
assigned level l. We then remmove all edges in ee.
1191
1192
(In contrast to the old squish_vertical_slow, this is now done in
1193
one step and not recursively so we avoid a lot of the copying of graphs.)
1194
1195
In particular, all points and edges retain their numbering (only the level might have changed).
1196
1197
WARNING: Level l-1 does not exist anymore afterwards!!!
1198
1199
Args:
1200
l (int): (internal) level number
1201
adddata (bool, optional): For legacy reasons, do not use. Defaults to False.
1202
quiet (bool, optional): No output. Defaults to True.
1203
1204
Returns:
1205
LevelGraph: new levelgraph with one level less.
1206
"""
1207
if l is None:
1208
return self
1209
ll = self.nextlowerlevel(l)
1210
levelset = set([l, ll])
1211
if ll is False:
1212
if not quiet:
1213
print("Warning: Illegal level to contract: " + repr(l))
1214
else:
1215
return self
1216
vertices = self.verticesonlevel(l) + self.verticesonlevel(ll)
1217
# edges that go to next lower level, i.e. will be contracted
1218
edges = [e for e in self.edges
1219
if set([self.levelofleg(e[0]), self.levelofleg(e[1])]) == levelset]
1220
# we use two dictionaries for quick lookup while we reorder the info:
1221
genus_of_vertex = {v : self.genus(v) for v in vertices}
1222
vertex_of_leg = {leg : v for v in vertices for leg in self.legsatvertex(v)}
1223
legs_of_vertex = {v : self.legsatvertex(v)[:] for v in vertices}
1224
deleted_legs = []
1225
while edges:
1226
start, end = edges.pop()
1227
v = vertex_of_leg[start]
1228
w = vertex_of_leg[end]
1229
del vertex_of_leg[start]
1230
del vertex_of_leg[end]
1231
legs_of_vertex[v].remove(start)
1232
legs_of_vertex[w].remove(end)
1233
deleted_legs.extend([start,end])
1234
if v == w:
1235
# if e (now) connects a vertex to itself, increase its genus
1236
genus_of_vertex[v] += 1
1237
else:
1238
# if e connects two different vertices, combine their data
1239
# (moving everything from w to v)
1240
genus_of_vertex[v] += genus_of_vertex[w]
1241
del genus_of_vertex[w]
1242
for leg in legs_of_vertex[w]:
1243
vertex_of_leg[leg] = v
1244
legs_of_vertex[v].append(leg)
1245
del legs_of_vertex[w]
1246
# Build new graph with this data:
1247
# We use sets for more efficient lookup:
1248
vertices = set(vertices)
1249
deleted_legs = set(deleted_legs)
1250
1251
newgenera = []
1252
newlegs = []
1253
newlevels = []
1254
# we copy the untouched vertices and insert the new ones:
1255
for v in range(len(self.genera)):
1256
if v in vertices:
1257
if v in genus_of_vertex:
1258
newgenera.append(genus_of_vertex[v])
1259
newlegs.append(legs_of_vertex[v][:])
1260
newlevels.append(l)
1261
# otherwise it has been deleted
1262
else:
1263
newgenera.append(self.genus(v))
1264
newlegs.append(self.legsatvertex(v)[:])
1265
newlevels.append(self.levelofvertex(v))
1266
1267
# for the edges, we remove those with a delted edge:
1268
newedges = []
1269
for start, end in self.edges:
1270
if start in deleted_legs:
1271
assert end in deleted_legs
1272
continue
1273
assert not end in deleted_legs
1274
newedges.append((start, end))
1275
# for the new poleorders, we only have to remove the deleted legs:
1276
newpoleorders = {leg : p for leg, p in self.poleorders.items() if not leg in deleted_legs}
1277
1278
return LevelGraph(newgenera,newlegs,newedges,newpoleorders,newlevels,True)
1279
1280
def delta(self,k,quiet=False):
1281
"""
1282
Squish all levels except for the k-th.
1283
1284
Note that delta(1) contracts everything except top-level and that
1285
the argument is interpreted via internal_level_number
1286
1287
WARNING: Currently giving an out of range level (e.g.
1288
0 or >= maxlevel) squishes the entire graph!!!
1289
1290
Return the corresponding divisor.
1291
"""
1292
G = self
1293
if self.is_horizontal():
1294
print("Error: Cannot delta a graph with horizontal edges!")
1295
return deepcopy(G)
1296
# recursive squishing forces us to go backwards, as the lower squished
1297
# level is always removed.
1298
for i in reversed(range(self.numberoflevels()-1)):
1299
# unfortunately delta and squish use slightly incompatible level
1300
# numbering, as the "illegal" squish here is k-1
1301
if abs(k)-1 == i:
1302
continue
1303
if not quiet:
1304
print("Squishing level", i)
1305
# note that it is important that internal_level_number of
1306
# self and not G is called in the argument, as the relative
1307
# numbering of levels in G changes, but the internal names don't
1308
G = G.squish_vertical(self.internal_level_number(i),quiet=quiet)
1309
return G
1310
1311
#################################################################
1312
#### end Squishing
1313
#################################################################
1314
1315
#################################################################
1316
#### Isomorphisms
1317
#### DEPRECATED!!!! USE EmbeddedLevelGraphs instead!
1318
####
1319
#### This only remains for the BIC generation comparison tests.
1320
#################################################################
1321
def _init_invariant(self):
1322
"""
1323
DEPRECATED!! DO NOT USE!!
1324
1325
Compute a bunch of invariants (as a quick check for not isommorphic).
1326
Up to now we have:
1327
* sorted list of genera (by level)
1328
* number of edges
1329
* number of levels
1330
* names and pole orders of marked points
1331
* sorted list of pole orders
1332
"""
1333
self._invariant = (
1334
len(self.edges),
1335
self.codim(),
1336
[(l,self.orderatleg(l)) for l in sorted(self.list_markings())],
1337
sorted([self.orderatleg(l) for l in self.leglist]))
1338
def invariant(self):
1339
"""
1340
DEPRECATED!!! DO NOT USE!!!
1341
"""
1342
self._init_invariant()
1343
return self._invariant
1344
1345
def _genisom_preserves_levels(self,other,dV):
1346
"""
1347
Check if a "vertex isomorphism" preserves the (relative) level structure
1348
"""
1349
return all(self.sortedlevels.index(self.levelofvertex(sv))
1350
== other.sortedlevels.index(other.levelofvertex(ov))
1351
for sv, ov in dV.items())
1352
1353
def _legisom_preserves_poleorders(self,other,dL):
1354
"""
1355
Check if a "leg isomorphism" preserves pole orders.
1356
"""
1357
return all(self.orderatleg(sl) == other.orderatleg(ol)
1358
for sl, ol in dL.items())
1359
1360
def _leveliso(self,other,dV):
1361
"""
1362
Give a dictionary translating the levels of self to other in accordance
1363
with the dictionary of vertices dV.
1364
"""
1365
return {l : other.levelofvertex(dV[self.verticesonlevel(l)[0]])
1366
for l in self.levels}
1367
1368
def is_isomorphic(self,other,adddata=False):
1369
"""
1370
DEPRECATED!!! Instead, use EmbeddedLevelGraph!!!
1371
1372
Check if self is isomorphic to other.
1373
Return boolean + list of isomorphisms if adddata=True.
1374
1375
Note that our isomorphisms are stgraph isomorphisms plus a dictionary
1376
translating the level structure of self into that of other.
1377
1378
At the moment we use admcycles.GraphIsom and check which of these
1379
preserve the LevelGraph structure (using the above invariants, though!)
1380
Probably it would be much faster to reimplement this for LevelGraphs
1381
as it seems quite redundant as is...
1382
"""
1383
isoms = GraphIsom(self.stgraph, other.stgraph)
1384
# check if the stable graph isomorphisms preserve level structure
1385
# GraphIsoms consist of a tuple (dV,dL), where dV is a dictionary
1386
# translating the vertices and dL is one for the legs.
1387
levelisoms = [[dV,dL,self._leveliso(other,dV)] for [dV,dL] in isoms
1388
if self._genisom_preserves_levels(other,dV)
1389
and self._legisom_preserves_poleorders(other,dL)]
1390
if adddata:
1391
return (levelisoms != [], levelisoms)
1392
else:
1393
return (levelisoms != [])
1394
1395
#################################################################
1396
#### Twist groups
1397
#################################################################
1398
def twist_group(self,quiet=True,with_degree=False):
1399
"""
1400
This should not be needed! The stack factor should be computed
1401
using AdditiveGenerator.stack_factor!!!
1402
1403
Calculate the index of the simple twist group inside the twist group.
1404
1405
with_degree=True: return a tuple (e,g) where
1406
* e = index of simple twist in twist
1407
* g = index of twist group in level rotation group
1408
1409
"""
1410
# horizontal edges => trivial twist group :-)
1411
if self.is_horizontal():
1412
return 1
1413
N = len(self.edges)
1414
M = FreeModule(ZZ,N)
1415
# kernel of projections to Z/prong Z
1416
K = M.submodule([M.basis()[i]*self.prong(e)
1417
for i,e in enumerate(self.edges)])
1418
if not quiet:
1419
print("kernel:", K)
1420
# submodule generated by edges crossing level i
1421
# i.e. image of R^Gamma (level rotation group) in ZZ^edges
1422
t_basis = [sum([M.basis()[i] for i,e in enumerate(self.edges)
1423
if self.crosseslevel(e,self.internal_level_number(l))])
1424
for l in range(1,self.numberoflevels())]
1425
E = M.submodule(t_basis)
1426
if not quiet:
1427
print("t-module:", E)
1428
# simple twist group: lcm of delta(l) edge prongs*t_i
1429
deltas = [self.delta(l,True) for l in range(1,self.numberoflevels())]
1430
if not quiet:
1431
print("deltas:", deltas)
1432
ll = [lcm([d.prong(e) for e in d.edges]) for d in deltas]
1433
if not quiet:
1434
print("lcms:", ll)
1435
st_basis = [sum([ll[l-1] * M.basis()[i] for i,e in enumerate(self.edges)
1436
if self.crosseslevel(e,self.internal_level_number(l))])
1437
for l in range(1,self.numberoflevels())]
1438
S = M.submodule(st_basis)
1439
if not quiet:
1440
print("simple twist group:")
1441
print(S)
1442
print("Intersection:")
1443
print(K.intersection(E))
1444
# K.intersection(E) = Twist group (in ZZ^edges)
1445
tw_gr = K.intersection(E)
1446
if with_degree:
1447
return (S.index_in(tw_gr),tw_gr.index_in(E))
1448
else:
1449
return S.index_in(tw_gr)
1450
1451
#################################################################
1452
#### Plotting
1453
#################################################################
1454
def plot_obj(self):
1455
"""
1456
Return a sage graphics object generated from a sage graph.
1457
1458
TODO: Some things that are still ugly:
1459
* No legs!!
1460
* currently the vertices have labels (index,genus)
1461
* loops are vertical not horizontal
1462
"""
1463
# Create the underlying graph with edge label prong order.
1464
G = Graph([self.genera,
1465
[(self.vertex(e[0]),self.vertex(e[1]),self.prong(e))
1466
for e in self.edges]],
1467
format='vertices_and_edges', loops=True, multiedges=True)
1468
# unfortunately, sage insists of displaying the vertex name (index)
1469
# and not only the label (genus), so we display both...
1470
G.relabel(list(enumerate(self.genera)))
1471
h={l:[(v,self.genus(v)) for v in self.verticesonlevel(l)]
1472
for l in self.levels}
1473
# at the moment we just display a list of legs and orders at the bottom
1474
legtext = "Marked points: "
1475
for l in sorted(set(self.levels)):
1476
points_at_level = [leg for leg in self.list_markings()
1477
if self.levelofleg(leg) == l]
1478
if not points_at_level:
1479
continue
1480
legtext += "\nAt level %s: "%l
1481
for leg in points_at_level:
1482
legtext += "\n" + self._pointtype(leg)
1483
legtext += " of order " + repr(self.orderatleg(leg))
1484
print(legtext)
1485
return G.plot(edge_labels=True, vertex_size=1000,
1486
heights=h, layout='ranked') + text(legtext,(0,-0.1),
1487
vertical_alignment='top',axis_coords=True,axes=False)
1488
1489