Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
181 views
unlisted
ubuntu2004
1
from copy import deepcopy
2
3
# pylint does not know sage
4
from sage.modules.free_module import FreeModule # pylint: disable=import-error
5
from sage.rings.integer_ring import ZZ # pylint: disable=import-error
6
from sage.arith.functions import lcm # pylint: disable=import-error
7
from sage.misc.flatten import flatten # pylint: disable=import-error
8
from sage.misc.cachefunc import cached_method # pylint: disable=import-error
9
10
import admcycles.diffstrata.generalisedstratum
11
from admcycles.diffstrata.sig import Signature
12
from admcycles.diffstrata.klevelgraph import KLevelGraph
13
14
15
def smooth_LG(sig):
16
"""
17
The smooth (i.e. one vertex) LevelGraph in the stratum (sig).
18
19
INPUT:
20
21
sig (Signature): signature of the stratum.
22
23
OUTPUT:
24
25
LevelGraph: The smooth graph in the stratum.
26
"""
27
if sig.k != 1:
28
raise ValueError("The signature is not for an abelian differential")
29
return LevelGraph.fromPOlist(
30
[sig.g], [list(range(1, sig.n + 1))], [], sig.sig, [0])
31
32
33
class LevelGraph(KLevelGraph):
34
r"""
35
Create a (stable) level graph.
36
37
..NOTE::
38
39
This is a low-level class and should NEVER be invoced directly!
40
Preferably, EmbeddedLevelGraphs should be used and these should be
41
generated automatically by Stratum (or GeneralisedStratum).
42
43
.. NOTE::
44
45
We don't inherit from stgraph/StableGraph anymore, as LevelGraphs
46
should be static objects!
47
48
Extends admcycles stgraph to represent a level graph as a stgraph,
49
i.e. a list of genera, a list of legs and a list of edges, plus a list of
50
poleorders for each leg and a list of levels for each vertex.
51
52
Note that the class will warn if the data is not admissible, i.e. if the
53
graph is not stable or the pole orders at separating nodes across levels do
54
not add up to -2 or -1 on the same level (unless this is suppressed with
55
quiet=True).
56
57
INPUT:
58
59
genera : list
60
List of genera of the vertices of length m.
61
62
legs : list
63
List of length m, where ith entry is list of legs attached to vertex i.
64
By convention, legs are unique positive integers.
65
66
edges : list
67
List of edges of the graph. Each edge is a 2-tuple of legs.
68
69
poleorders : dictionary
70
Dictionary of the form leg number : poleorder
71
72
levels : list
73
List of length m, where the ith entry corresponds to the level of
74
vertex i.
75
By convention, top level is 0 and levels go down by 1, in particular
76
they are NEGATIVE numbers!
77
78
quiet : optional boolean (default = False)
79
Suppresses most error messages.
80
81
ALTERNATIVELY, the pole orders can be supplied as a list by calling
82
LevelGraph.fromPOlist:
83
84
poleorders : list
85
List of order of the zero (+) or pole (-) at each leg. The ith element
86
of the list corresponds to the order at the leg with the marking i+1
87
(because lists start at 0 and the legs are positive integers).
88
89
EXAMPLES:
90
91
Creating a level graph with three components on different levels of genus 1,
92
3 and 0. The bottom level has a horizontal node.::
93
94
sage: from admcycles.diffstrata import *
95
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
96
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)
97
98
or alternatively::
99
100
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],quiet=True)
101
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)
102
103
We get a warning if the graph has non-stable components: (not any more ;-))::
104
105
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
106
Warning: Component 3 is not stable: g = 0 but only 1 leg(s)!
107
Warning: Graph not stable!
108
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)
109
"""
110
def __init__(self, genera, legs, edges, poleorders, levels, quiet=False):
111
super().__init__(
112
genera, legs, edges, poleorders, levels, 1, quiet)
113
# the prong orders are stored in a dictionary edge : prong order
114
self.prongs = self._gen_prongs()
115
116
@classmethod
117
def fromPOlist(cls, genera, legs, edges,
118
poleordersaslist, levels, quiet=False):
119
"""
120
This gives a LevelGraph where the poleorders are given as a list, not
121
directly as a dictionary.
122
"""
123
# translate poleorder list to dictionary:
124
sortedlegs = sorted(flatten(legs))
125
if len(sortedlegs) != len(poleordersaslist):
126
raise ValueError("Numbers of legs and pole orders do not agree!" +
127
" Legs: " + repr(len(sortedlegs)) +
128
" Pole orders: " + repr(len(poleordersaslist)))
129
poleorderdict = {sortedlegs[i]: poleordersaslist[i]
130
for i in range(len(sortedlegs))}
131
return cls(genera, legs, edges, poleorderdict, levels, quiet)
132
133
@classmethod
134
def fromKLevelGraph(cls, kLG):
135
assert kLG.sig.k == 1
136
return cls(kLG.genera, kLG.legs, kLG.edges,
137
kLG.poleorders, kLG.levels, quiet=True)
138
139
def __repr__(self):
140
return "LevelGraph(" + self.input_as_string() + ")"
141
142
def __hash__(self):
143
return hash(repr(self))
144
145
def __str__(self):
146
return ("LevelGraph " + repr(self.genera) + ' ' + repr(self.legs) + ' '
147
+ repr(self.edges) + ' ' + repr(self.poleorders) + ' '
148
+ repr(self.levels))
149
150
def input_as_string(self):
151
"""
152
return a string that can be given as argument to __init__ to give self
153
"""
154
return (repr(self.genera) + ',' + repr(self.legs) + ','
155
+ repr(self.edges) + ',' + repr(self.poleorders) + ','
156
+ repr(self.levels) + ',True')
157
158
##########################################################
159
# Interface:
160
# --- provide a series of methods that translate various data
161
# (maybe this can be optimised by smart caching later, so only
162
# these should be used...)
163
# up to now, we have:
164
# * prongs_list :
165
# returns : list of tuples (edge,prong)
166
# * prong :
167
# INPUT : edge
168
# returns : prong order at edge
169
# * _pointtype :
170
# INPUT : order (integer)
171
# returns : pole/marked point/zero (string)
172
# * is_meromorphic :
173
# returns : boolean (check for pole among marked points)
174
# * highestorderpole :
175
# INPUT : vertex
176
# returns : leg name (at v) or -1 if none found
177
#########################################################
178
@cached_method
179
def prongs_list(self):
180
return list(self.prongs.items())
181
182
@cached_method
183
def prong(self, e):
184
"""
185
The prong order is the pole order of the higher-level pole +1
186
"""
187
if self.levelofleg(e[0]) > self.levelofleg(e[1]):
188
return self.orderatleg(e[0]) + 1
189
else:
190
return self.orderatleg(e[1]) + 1
191
192
@cached_method
193
def _pointtype(self, order):
194
if order < 0:
195
return "pole"
196
elif order == 0:
197
return "marked point"
198
else:
199
return "zero"
200
201
@cached_method
202
def is_meromorphic(self):
203
"""
204
Returns True iff at least one of the MARKED POINTS is a pole.
205
"""
206
return any(self.orderatleg(l) < 0 for l in self.list_markings())
207
208
@cached_method
209
def highestorderpole(self, v):
210
"""
211
Returns the leg with the highest order free pole at v, -1 if no poles found.
212
"""
213
minorder = 0
214
leg = -1
215
for l in self.list_markings(v):
216
if self.orderatleg(l) < minorder:
217
minorder = self.orderatleg(l)
218
leg = l
219
return leg
220
#########################################################
221
# end interface
222
#########################################################
223
224
#################################################################
225
# Cleanup functions
226
# USE ONLY IN __init__!!! KLevelGraphs should be static!!!
227
#################################################################
228
def _gen_prongs(self):
229
"""
230
Generate the dictionary edge : prong order.
231
The prong order is the pole order of the higher-level pole +1
232
"""
233
return {e: self.prong(e) for e in self.edges}
234
235
#################################################################
236
# Extract a subgraph
237
#################################################################
238
239
def extract(self, vertices, edges):
240
"""
241
Extract the subgraph of self (as a LevelGraph) consisting of vertices
242
(as list of indices) and edges (as list of edges).
243
244
Returns the levelgraph consisting of vertices, edges and all legs on
245
vertices (of self) with their original poleorders and the original
246
level structure.
247
"""
248
return self.fromKLevelGraph(
249
super().extract(vertices, edges))
250
251
def levelGraph_from_subgraph(self, G):
252
"""
253
Returns the LevelGraph associated to a subgraph of underlying_graph
254
(with the level structure induced by self)
255
"""
256
return self.fromKLevelGraph(
257
super().levelGraph_from_subgraph(G))
258
259
def stratum_from_level(self, l, excluded_poles=None):
260
"""
261
Return the LevelStratum at (relative) level l.
262
263
INPUT:
264
265
l (int): relative level number (0,...,codim)
266
267
excluded_poles (tuple, defaults to None): a list of poles (legs of the graph,
268
that are marked points of the stratum!) to be ignored for r-GRC.
269
270
OUTPUT:
271
272
LevelStratum: the LevelStratum, i.e.
273
274
* a list of Signatures (one for each vertex on the level)
275
* a list of residue conditions, i.e. a list [res_1,...,res_r]
276
where each res_k is a list of tuples [(i_1,j_1),...,(i_n,j_n)]
277
where each tuple (i,j) refers to the point j (i.e. index) on the
278
component i and such that the residues at these points add up
279
to 0.
280
* a dictionary of legs, i.e. n -> (i,j) where n is the original
281
number of the point (on the LevelGraph self) and i is the
282
number of the component, j the index of the point in the signature tuple.
283
284
Note that LevelStratum is a GeneralisedStratum together with
285
a leg dictionary.
286
"""
287
if self.is_horizontal():
288
print("Error: Cannot extract levels of a graph with horizontal edges!")
289
return None
290
internal_l = self.internal_level_number(l)
291
# first, we extract the obvious information from self at this level:
292
vv = self.verticesonlevel(internal_l)
293
legs_on_level = []
294
sigs = []
295
res_cond = []
296
leg_dict = {}
297
for i, v in enumerate(vv):
298
legs_on_level += [deepcopy(self.legsatvertex(v))]
299
leg_dict.update([(n, (i, j))
300
for j, n in enumerate(legs_on_level[-1])])
301
sig_v = Signature(tuple(self.orderatleg(leg)
302
for leg in legs_on_level[-1]))
303
sigs += [sig_v]
304
# Now we hunt for residue conditions.
305
# We work with the underlying graph to include the "level at infinity":
306
# We "cheat" here, to catch the edges going up from level l: These are the
307
# edges of the graph above level l-1 that have a vertex on level l:
308
# (see below for parsing of these edges)
309
ee = []
310
for e in self.UG_above_level(internal_l - 1).edges(sort=True):
311
for UG_vertex in e[:2]:
312
if UG_vertex[2] == 'res':
313
continue
314
if self.levelofvertex(UG_vertex[0]) == internal_l:
315
ee.append(e)
316
break
317
G = self.UG_above_level(internal_l)
318
conn_comp = G.connected_components_subgraphs()
319
# Residue conditions are obtained by sorting the poles on this level
320
# by connected component of G they are attached to (note that this
321
# includes the "level at infinity"!)
322
for c in conn_comp:
323
# if there is a free pole in c, we ignore this component
324
if self._redeemed_by_merom_in_comp(
325
c, excluded_poles=excluded_poles):
326
continue
327
res_cond_c = []
328
# Otherwise we record all poles to attached to this component in one
329
# residue condition:
330
for e in ee:
331
# We work with edges of the underlying graph here, so we have to
332
# be a bit more careful. Recall that edges of the underlying graph are of
333
# the form:
334
# * (UG_vertex, UG_vertex, edge name) if it's an edge of LG or
335
# * (UG_vertex, UG_vertex, resiedgej) if it's an edge to level infinity
336
# Here UG_vertex is either of the form (vertex number, genus, 'LG') or
337
# (i, 0, 'res') where
338
# i is the number of the vertex at infinity and resiedgej is the edge
339
# connecting the residue i to the leg j (of self).
340
if e[0] in c or e[1] in c:
341
# res_cond contains tuples (i,j) where i is the index of the
342
# component and j the index of the pole in sig_i.
343
#
344
# We need find the vertex on this level (i.e. not in c)
345
if e[0] in c:
346
UG_vertex = e[1]
347
else:
348
UG_vertex = e[0]
349
# to find the pole on v, we have to distinguish if this is an
350
# edge of self or if it connects to a residue at infinity:
351
edge = e[2]
352
if 'res' in edge:
353
# the leg is the number after 'edge' in the edge
354
# string:
355
_, leg = edge.split('edge')
356
leg = int(leg)
357
else:
358
# edge consists of (leg_top,leg_bot) and leg_bot is on
359
# level i:
360
leg = edge[1]
361
# We retrieve the stratum point from leg_dict:
362
res_cond_c += [leg_dict[leg]]
363
if res_cond_c:
364
res_cond += [res_cond_c]
365
return admcycles.diffstrata.generalisedstratum.LevelStratum(
366
sigs, res_cond, leg_dict)
367
368
#################################################################
369
# Check Residue condition (via inconvenient vertices/edges)
370
#################################################################
371
372
@cached_method
373
def is_inconvenient_vertex(self, v):
374
"""
375
Check if vertex is inconvenient, i.e.
376
* g = 0
377
* no simple poles
378
* there exists a pole order m_i such that m_i > sum(m_j)-p-1
379
Return boolean
380
"""
381
# inconvenient vertices are of genus 0 and have no simple poles.
382
if self.genus(v) > 0 or len(self.simplepolesatvertex(v)) > 0:
383
return False
384
# check inequality
385
ll = self.legsatvertex(v)
386
poles = [l for l in ll if self.orderatleg(l) < 0]
387
polesum = sum([-self.orderatleg(l) for l in poles])
388
p = len(poles)
389
for l in ll:
390
if self.orderatleg(l) > polesum - p - 1:
391
return True
392
return False
393
394
def _redeemed_by_merom_in_comp(self, G, excluded_poles=None):
395
"""
396
Check if there is a pole in the subgraph G (intended to be a connected
397
component above a vertex).
398
399
excluded_poles are ignored (for r-GRC).
400
401
Returns boolean (True = exists a pole)
402
"""
403
if excluded_poles is None:
404
excluded_poles = []
405
for w in G.vertices(sort=False):
406
if w[2] == 'res': # vertex at infinity
407
continue
408
for l in self.list_markings(w[0]):
409
if (self.orderatleg(l) < 0) and (l not in excluded_poles):
410
return True
411
return False
412
413
@cached_method
414
def is_illegal_vertex(self, v, excluded_poles=None):
415
"""
416
Check if vertex is inconvenient and is not redeemed, i.e.
417
* v is inconvenient
418
* there are no two separate edges to the same connected component above (i.e. loop above)
419
* if meromorphic: v is not connected to higher level marked poles
420
421
We can also pass a tuple (hashing!) of poles that are to be excluded because of
422
residue conditions.
423
424
Return boolean
425
"""
426
if not self.is_inconvenient_vertex(v):
427
return False
428
l = self.levelofvertex(v)
429
# in the underlying_graph, vertices are of the form (index in genera,
430
# genus, 'LG'):
431
v_graph = self.UG_vertex(v)
432
# edges not going down from v:
433
ee = [e for e in self.edgesatvertex(v) if self.levelofleg(e[0]) >= l
434
and self.levelofleg(e[1]) >= l]
435
# in the holomorphic case, legal inconvenient vertices need at least
436
# two edges not going down
437
if len(ee) < 1 and not self.is_meromorphic():
438
return True
439
# check if v has two edges into one connected component that don't lie below v,
440
# i.e. if v can be redeemed:
441
abovegraph = self.UG_above_level(l - 1)
442
cc = self.underlying_graph.subgraph([w for w in abovegraph.connected_component_containing_vertex(
443
v_graph) if w[2] == 'res' or self.levelofvertex(w[0]) >= l])
444
cc.delete_vertex(v_graph)
445
conn_comp = cc.connected_components_subgraphs()
446
if excluded_poles is None:
447
excluded_poles = []
448
freepoles = len([l for l in self.list_markings(v) if (
449
self.orderatleg(l) < 0) and (l not in excluded_poles)])
450
for G in conn_comp:
451
# edges from v to G:
452
# (Careful: cc does not have any edges with v anymore, so we must use abovegraph!)
453
eeG = [e for e in abovegraph.edges(sort=True)
454
if (e[0] == v_graph and e[1] in G.vertices(sort=False))
455
or (e[1] == v_graph and e[0] in G.vertices(sort=False))]
456
if len(eeG) > 1:
457
# redeemed :-)
458
return False
459
# in the meromorphic case, we also check if a "free" pole exists in
460
# a connected component above v
461
if self.is_meromorphic() and self._redeemed_by_merom_in_comp(G,
462
excluded_poles=excluded_poles):
463
freepoles += 1
464
if freepoles >= 2:
465
# redeemed :-)
466
return False
467
return True
468
469
@cached_method
470
def is_illegal_edge(self, e, excluded_poles=None):
471
"""
472
Check if edge is illegal, i.e. if
473
* e is horizontal (not loop) and
474
* there no simple loop over e
475
* there is no "free" pole over each end point
476
477
excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.
478
479
Return boolean
480
"""
481
if not self.is_horizontal(e) or (e[0] == e[1]):
482
return False
483
# check if there is a simple loop above e, i.e. if the subgraph
484
# above e is still connected after e is removed (e is not a bridge)
485
l = self.levelofleg(e[0])
486
# note that verticesabove checks for >l
487
abovegraph = self.UG_above_level(l - 1)
488
# edges are encoded by (self.vertex(e[0]),self.vertex(e[1]),e) in
489
# underlying_graph!
490
cut = abovegraph.is_cut_edge(
491
(self.UG_vertex(
492
self.vertex(
493
e[0])), self.UG_vertex(
494
self.vertex(
495
e[1])), e))
496
# True = e is a cut-edge, i.e. connected components increase when removing e, i.e. no loop above,
497
# i.e. illegal unless also meromorphic
498
# False = graph has a loop, i.e. not illegal
499
if not cut:
500
return False
501
else:
502
if self.is_meromorphic():
503
# if there are "free" poles above the endpoints, we are still
504
# fine
505
abovegraph.delete_edge(
506
(self.UG_vertex(
507
self.vertex(
508
e[0])), self.UG_vertex(
509
self.vertex(
510
e[1])), e))
511
polesfound = 0
512
for l in e:
513
v = self.vertex(l)
514
G = abovegraph.connected_component_containing_vertex(
515
self.UG_vertex(v))
516
if self._redeemed_by_merom_in_comp(
517
abovegraph.subgraph(G), excluded_poles=excluded_poles):
518
polesfound += 1
519
if polesfound == 2:
520
# we are fine
521
return False
522
# no redemption :/
523
return True
524
525
@cached_method
526
def is_legal(self, quiet=False, excluded_poles=None):
527
"""
528
Check if self has illegal vertices or edges.
529
530
excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.
531
532
Return boolean
533
"""
534
for v in range(len(self.genera)):
535
if self.is_illegal_vertex(v, excluded_poles=excluded_poles):
536
if not quiet:
537
print("Vertex", v, "is illegal!")
538
return False
539
for e in self.edges:
540
if self.is_illegal_edge(e, excluded_poles=excluded_poles):
541
if not quiet:
542
print("Edge", e, "is illegal!")
543
return False
544
return True
545
546
#################################################################
547
# Squishing ---- i.e. contracting horizontal edges / levels
548
#################################################################
549
550
def squish_horizontal(self, e):
551
"""
552
Squish the horizontal edge e and return the new graph.
553
554
EXAMPLES::
555
556
sage: from admcycles.diffstrata import *
557
sage: G = LevelGraph.fromPOlist([1,1], [[1,2],[3,4]], [(2,3)], [1,-1,-1,1],[0,0])
558
sage: G.squish_horizontal((2,3))
559
LevelGraph([2],[[1, 4]],[],{1: 1, 4: 1},[0],True)
560
"""
561
return LevelGraph(*self._squish_horizontal(e), quiet=True)
562
563
def squish_vertical_slow(self, l, adddata, quiet=False):
564
"""
565
Squish the level l (and the next lower level!) and return the new graph.
566
If addata=True is specified, we additionally return a boolean that tells
567
us if a level was squished and the legs that no longer exist.
568
569
More precisely, adddata=True returns a tuple (G,boolean,legs) where
570
* G is the (possibly non-contracted) graph and
571
* boolean tells us if a level was squished
572
* legs is the (possibly empty) list of legs that don't exist anymore
573
574
Implementation:
575
At the moment, we remember all the edges (ee) that connect level l to
576
level l-1. We then create a new LevelGraph with the same data as self,
577
the only difference being that all vertices of level l-1 have now been
578
assigned level l. We then remove all (now horizontal!) edges in ee.
579
580
In particular, all points and edges retain their numbering (only the level might have changed).
581
582
WARNING: Level l-1 does not exist anymore afterwards!!!
583
584
Downside: At the moment we get a warning for each edge in ee, as these
585
don't have legal pole orders for being horizontal (so checkedgeorders
586
complains each time the constructor is called :/).
587
"""
588
return self.fromKLevelGraph(
589
super().squish_vertical_slow(l, adddata, quiet))
590
591
def squish_vertical(self, l, quiet=True):
592
"""
593
Squish the level l (and the next lower level!) and return the new graph.
594
595
WARNING: Level l-1 does not exist anymore afterwards!!!
596
597
Args:
598
l (int): (internal) level number
599
quiet (bool, optional): No output. Defaults to True.
600
601
Returns:
602
LevelGraph: new levelgraph with one level less.
603
604
EXAMPLES::
605
606
sage: from admcycles.diffstrata import *
607
sage: G = LevelGraph.fromPOlist([1,2], [[1],[2,3,4]], [(1,2)], [0,-2,1,3],[0,-1])
608
sage: G.squish_vertical(0)
609
LevelGraph([3],[[3, 4]],[],{3: 1, 4: 3},[0],True)
610
"""
611
return LevelGraph(*self._squish_vertical(l, quiet=quiet), quiet=True)
612
613
def delta(self, k, quiet=False):
614
"""
615
Squish all levels except for the k-th.
616
617
Note that delta(1) contracts everything except top-level and that
618
the argument is interpreted via internal_level_number
619
620
WARNING: Currently giving an out of range level (e.g.
621
0 or >= maxlevel) squishes the entire graph!!!
622
623
Return the corresponding divisor.
624
"""
625
return self.fromKLevelGraph(super().delta(k, quiet))
626
627
#################################################################
628
# end Squishing
629
#################################################################
630
631
def relabel(self, legdict, tidyup=True):
632
new_legs = []
633
for vlegs in self.legs:
634
list1 = [legdict[i] for i in vlegs]
635
if tidyup:
636
list1.sort()
637
new_legs.append(list1)
638
new_edges = [(legdict[e[0]], legdict[e[1]]) for e in self.edges]
639
new_poleorders = {legdict[i]: j for i, j in self.poleorders.items()}
640
if tidyup:
641
new_edges.sort()
642
new_poleorders = {a: b for a, b in sorted(new_poleorders.items())}
643
newLG = LevelGraph(self.genera, new_legs, new_edges,
644
new_poleorders, self.levels)
645
return newLG
646
647
#################################################################
648
# Twist groups
649
#################################################################
650
651
def twist_group(self, quiet=True, with_degree=False):
652
"""
653
This should not be needed! The stack factor should be computed
654
using AdditiveGenerator.stack_factor!!!
655
656
Calculate the index of the simple twist group inside the twist group.
657
658
with_degree=True: return a tuple (e,g) where
659
* e = index of simple twist in twist
660
* g = index of twist group in level rotation group
661
662
"""
663
# horizontal edges => trivial twist group :-)
664
if self.is_horizontal():
665
return 1
666
N = len(self.edges)
667
M = FreeModule(ZZ, N)
668
# kernel of projections to Z/prong Z
669
K = M.submodule([M.basis()[i] * self.prong(e)
670
for i, e in enumerate(self.edges)])
671
if not quiet:
672
print("kernel:", K)
673
# submodule generated by edges crossing level i
674
# i.e. image of R^Gamma (level rotation group) in ZZ^edges
675
t_basis = [sum([M.basis()[i] for i, e in enumerate(self.edges)
676
if self.crosseslevel(e, self.internal_level_number(l))])
677
for l in range(1, self.numberoflevels())]
678
E = M.submodule(t_basis)
679
if not quiet:
680
print("t-module:", E)
681
# simple twist group: lcm of delta(l) edge prongs*t_i
682
deltas = [self.delta(l, True) for l in range(1, self.numberoflevels())]
683
if not quiet:
684
print("deltas:", deltas)
685
ll = [lcm([d.prong(e) for e in d.edges]) for d in deltas]
686
if not quiet:
687
print("lcms:", ll)
688
st_basis = [sum([ll[l - 1] * M.basis()[i] for i, e in enumerate(self.edges)
689
if self.crosseslevel(e, self.internal_level_number(l))])
690
for l in range(1, self.numberoflevels())]
691
S = M.submodule(st_basis)
692
if not quiet:
693
print("simple twist group:")
694
print(S)
695
print("Intersection:")
696
print(K.intersection(E))
697
# K.intersection(E) = Twist group (in ZZ^edges)
698
tw_gr = K.intersection(E)
699
if with_degree:
700
return (S.index_in(tw_gr), tw_gr.index_in(E))
701
else:
702
return S.index_in(tw_gr)
703
704