Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
181 views
unlisted
ubuntu2004
1
r"""
2
Generators of tautological ring
3
4
Each generator is called a stratum and is encoded in a class class:`Graph`. A
5
stratum is called *pure* if it has neither kappa nor psi marking (ie a stable
6
graph with no decoration).
7
"""
8
from copy import copy
9
import itertools
10
11
from sage.misc.cachefunc import cached_function
12
from sage.misc.lazy_attribute import lazy_attribute
13
14
from sage.rings.all import PolynomialRing, ZZ
15
from sage.combinat.all import IntegerVectors, Partitions
16
from sage.functions.other import floor
17
from sage.matrix.constructor import matrix
18
19
from .utils import aut
20
from .moduli import MODULI_SM, MODULI_RT, MODULI_CT, MODULI_ST, MODULI_SMALL, dim_form
21
22
23
R = PolynomialRing(ZZ, 1, order='lex', names=('X',))
24
X = R.gen()
25
26
27
class Graph:
28
r"""
29
A decorated graph representing a tautological class.
30
31
The graph is encoded as a matrix where
32
33
- each row after the first one corresponds to a vertex
34
- each column after the first one corresponds to an edge or leg
35
- the first column gives the genera of the vertices
36
- the first row gives the markings on the legs
37
- the other cells are
38
- 0 if the vertex and edge/leg are not incident
39
- 1 if the vertex and edge/leg are incident once
40
- 2 if the edge is a loop at the vertex
41
- entries with polynomials in X describe kappa/psi decorations:
42
- in the first column, each X^n term corresponds to a kappa_n at that
43
vertex
44
- in other locations, each X term corresponds to a psi at that half-edge
45
- at loops, 2 + aX + bX^2 corresponds to having psi^a on one side of the
46
loop and psi^b on the other
47
48
EXAMPLES::
49
50
sage: from admcycles.DR.graph import Graph, X
51
sage: gr = Graph(matrix(2, 3, [-1, 1, 2, 1, X + 1, 1]))
52
sage: gr
53
[ -1 1 2]
54
[ 1 X + 1 1]
55
sage: gr.num_vertices()
56
1
57
sage: gr.num_edges()
58
2
59
"""
60
def __init__(self, M=None, genus_list=None):
61
if M:
62
self.M = copy(M)
63
elif genus_list:
64
self.M = matrix(R, len(genus_list) + 1,
65
1, [-1] + genus_list)
66
else:
67
self.M = matrix(R, 1, 1, -1)
68
69
def __repr__(self):
70
return repr(self.M)
71
72
@lazy_attribute
73
def degree_vec(self):
74
r"""
75
Return the tuple of degrees of vertices.
76
77
EXAMPLES::
78
79
sage: from admcycles.DR.graph import Graph, X
80
sage: gr = Graph(matrix(2, 3, [-1, 1, 2, 1, X + 1, 1]))
81
sage: gr.degree_vec
82
(2,)
83
"""
84
res = [0 for i in range(1, self.M.nrows())]
85
for i in range(1, self.M.nrows()):
86
for j in range(1, self.M.ncols()):
87
res[i - 1] += self.M[i, j][0]
88
return tuple(res)
89
90
@lazy_attribute
91
def target_parity(self):
92
ans = 0
93
for i in range(1, self.M.nrows()):
94
local_parity = 1 + self.M[i, 0][0]
95
for j in range(1, self.M.ncols()):
96
local_parity += self.M[i, j][1] + self.M[i, j][2]
97
for j in range(1, self.M[i, 0].degree() + 1):
98
local_parity += j * self.M[i, 0][j]
99
local_parity %= 2
100
ans += (local_parity << (i - 1))
101
return ans
102
103
def degree(self, i):
104
r"""
105
Return the degree of vertex ``i``.
106
"""
107
return self.degree_vec[i - 1]
108
109
def num_vertices(self):
110
return self.M.nrows() - 1
111
112
def num_edges(self):
113
return self.M.ncols() - 1
114
115
def h1(self):
116
return self.M.ncols() - self.M.nrows() + 1
117
118
def add_vertex(self, g):
119
self.M = self.M.stack(matrix(1, self.M.ncols()))
120
self.M[-1, 0] = g
121
122
def add_edge(self, i1, i2, marking=0):
123
self.M = self.M.augment(matrix(self.M.nrows(), 1))
124
self.M[0, -1] = marking
125
if i1 > 0:
126
self.M[i1, -1] += 1
127
if i2 > 0:
128
self.M[i2, -1] += 1
129
130
def del_vertex(self, i):
131
self.M = self.M[:i].stack(self.M[(i + 1):])
132
133
def del_edge(self, i):
134
if i == self.num_edges():
135
self.M = self.M[0:, :i]
136
else:
137
self.M = self.M[0:, :i].augment(self.M[0:, (i + 1):])
138
139
def split_vertex(self, i, row1, row2):
140
self.M = self.M.stack(matrix(2, self.M.ncols(), row1 + row2))
141
self.add_edge(self.M.nrows() - 2,
142
self.M.nrows() - 1)
143
self.del_vertex(i)
144
145
def replace_vertex_with_graph(self, i, G):
146
nv = self.num_vertices()
147
ne = self.num_edges()
148
# i should have degree d, there should be no classes near i, and G should have genus equal to the genus of i
149
hedge_list_ones = []
150
hedge_list_others = []
151
unsym_cols = [-1]
152
mark_nr = -1
153
for k in range(1, G.M.ncols()):
154
if G.M[0, k] > 0:
155
if G.M[0, k] == 1:
156
mark_nr += 1
157
unsym_cols.append(G.M[0, k] + mark_nr)
158
else:
159
unsym_cols.append(0)
160
for k in range(1, self.M.ncols()):
161
for j in range(self.M[i, k][0]):
162
if self.M[0, k] == 1:
163
hedge_list_ones.append(k)
164
else:
165
hedge_list_others.append(k)
166
hedge_list = hedge_list_ones + hedge_list_others
167
self.del_vertex(i)
168
for j in range(G.num_edges() - len(hedge_list)):
169
self.add_edge(0, 0)
170
for j in range(G.num_vertices()):
171
self.add_vertex(G.M[j + 1, 0])
172
col = ne + 1
173
for k in range(1, G.M.ncols()):
174
if G.M[0, k] > 0:
175
mark = ZZ(unsym_cols[k])
176
for j in range(G.num_vertices()):
177
if self.M[nv + j, hedge_list[mark - 1]] == 0:
178
self.M[nv + j, hedge_list[mark - 1]] = G.M[j + 1, k]
179
elif G.M[j + 1, k] != 0:
180
a = self.M[nv + j, hedge_list[mark - 1]][1]
181
b = G.M[j + 1, k][1]
182
self.M[nv + j, hedge_list[mark - 1]] = 2 + \
183
max(a, b) * X + min(a, b) * X**2
184
else:
185
for j in range(G.num_vertices()):
186
self.M[nv + j, col] = G.M[j + 1, k]
187
col += 1
188
189
def compute_invariant(self):
190
nr, nc = self.M.nrows(), self.M.ncols()
191
self.invariant = [[self.M[i, 0], [], [], [
192
[] for j in range(1, nr)]] for i in range(1, nr)]
193
for k in range(1, nc):
194
L = [i for i in range(1, nr)
195
if self.M[i, k] != 0]
196
if len(L) == 1:
197
if self.M[0, k] != 0:
198
self.invariant[L[0] -
199
1][2].append((self.M[0, k], self.M[L[0], k]))
200
else:
201
self.invariant[L[0] - 1][1].append(self.M[L[0], k])
202
else:
203
self.invariant[L[0] - 1][3][L[1] - 1].append((self.M[L[0], k],
204
self.M[L[1], k]))
205
self.invariant[L[1] - 1][3][L[0] - 1].append((self.M[L[1], k],
206
self.M[L[0], k]))
207
for i in range(1, nr):
208
self.invariant[i - 1][3] = [term for term in self.invariant[i - 1][3]
209
if len(term)]
210
for k in range(len(self.invariant[i - 1][3])):
211
self.invariant[i - 1][3][k].sort()
212
self.invariant[i -
213
1][3][k] = tuple(self.invariant[i - 1][3][k])
214
self.invariant[i - 1][3].sort()
215
self.invariant[i - 1][3] = tuple(self.invariant[i - 1][3])
216
self.invariant[i - 1][2].sort()
217
self.invariant[i - 1][2] = tuple(self.invariant[i - 1][2])
218
self.invariant[i - 1][1].sort()
219
self.invariant[i - 1][1] = tuple(self.invariant[i - 1][1])
220
self.invariant[i - 1] = tuple(self.invariant[i - 1])
221
vertex_invariants = [[i, self.invariant[i - 1]]
222
for i in range(1, nr)]
223
self.invariant.sort()
224
self.invariant = tuple(self.invariant)
225
vertex_invariants.sort(key=lambda x: x[1])
226
self.vertex_groupings = []
227
for i in range(nr - 1):
228
if i == 0 or vertex_invariants[i][1] != vertex_invariants[i - 1][1]:
229
self.vertex_groupings.append([])
230
self.vertex_groupings[-1].append(
231
vertex_invariants[i][0])
232
233
def purify(self):
234
for i in range(self.M.nrows()):
235
for j in range(self.M.ncols()):
236
self.M[i, j] = R(self.M[i, j][0])
237
238
def contract(self, i, vlist, elist):
239
# assumes graph is undecorated
240
if self.M[0, i] != 0:
241
print("ERROR: cannot contract a marking")
242
return
243
S = [row for row in range(
244
1, self.M.nrows()) if self.M[row, i] != 0]
245
if len(S) == 1:
246
self.M[S[0], 0] += 1
247
self.del_edge(i)
248
elist = elist[:(i - 1)] + elist[i:]
249
else:
250
self.del_edge(i)
251
elist = elist[:(i - 1)] + elist[i:]
252
self.add_vertex(0)
253
self.M[-1] += self.M[S[0]]
254
self.M[-1] += self.M[S[1]]
255
self.del_vertex(S[1])
256
self.del_vertex(S[0])
257
vlist = vlist[:(S[0] - 1)] + vlist[S[0]:(S[1] - 1)] + \
258
vlist[S[1]:] + [vlist[S[0] - 1] + vlist[S[1] - 1]]
259
return vlist, elist
260
261
262
def graph_isomorphic(G1, G2):
263
r"""
264
Return whether ``G1`` and ``G2`` are isomorphic.
265
"""
266
if G1.invariant != G2.invariant:
267
return False
268
269
M1 = G1.M
270
M2 = G2.M
271
group1 = G1.vertex_groupings
272
group2 = G2.vertex_groupings
273
274
nr = M1.nrows()
275
nc = M1.ncols()
276
for sigma_data in itertools.product(*(itertools.permutations(range(len(group))) for group in group1)):
277
sigma = [0] * (nr - 1)
278
for i in range(len(group1)):
279
for j in range(len(group1[i])):
280
sigma[group1[i][j] - 1] = group2[i][sigma_data[i][j]]
281
good = True
282
for i in range(1, nr):
283
ii = sigma[i - 1]
284
for j in range(1, i):
285
jj = sigma[j - 1]
286
L1 = []
287
for k in range(1, nc):
288
if M1[i, k] != 0 and M1[j, k] != 0:
289
L1.append([M1[i, k], M1[j, k]])
290
L1.sort()
291
L2 = []
292
for k in range(1, nc):
293
if M2[ii, k] != 0 and M2[jj, k] != 0:
294
L2.append([M2[ii, k], M2[jj, k]])
295
L2.sort()
296
if L1 != L2:
297
good = False
298
break
299
if good is False:
300
break
301
if good:
302
return True
303
return False
304
305
306
def graph_count_automorphisms(G, vertex_orbits=False):
307
return count_automorphisms(G.M, G.vertex_groupings, vertex_orbits)
308
309
310
def count_automorphisms(M, grouping, vertex_orbits=False):
311
"""
312
INPUT:
313
314
M: adjacency matrix
315
grouping: list of lists
316
vertex_orbits: boolean
317
"""
318
nr, nc = M.nrows(), M.ncols()
319
count = ZZ.zero()
320
if vertex_orbits:
321
isom_list = []
322
for sigma_data in itertools.product(*(itertools.permutations(range(len(group))) for group in grouping)):
323
sigma = [0 for i in range(nr - 1)]
324
for i in range(len(grouping)):
325
for j in range(len(grouping[i])):
326
sigma[grouping[i][j] - 1] = grouping[i][sigma_data[i][j]]
327
good = True
328
for i in range(1, nr):
329
ii = sigma[i - 1]
330
for j in range(1, i):
331
jj = sigma[j - 1]
332
L1 = []
333
for k in range(1, nc):
334
if M[i, k] != 0 and M[j, k] != 0:
335
L1.append([M[i, k], M[j, k]])
336
L1.sort()
337
L2 = []
338
for k in range(1, nc):
339
if M[ii, k] != 0 and M[jj, k] != 0:
340
L2.append([M[ii, k], M[jj, k]])
341
L2.sort()
342
if L1 != L2:
343
good = False
344
break
345
if not good:
346
break
347
if good:
348
count += 1
349
if vertex_orbits:
350
isom_list.append(sigma)
351
352
if vertex_orbits:
353
orbit_list = []
354
vertices_used = []
355
while len(vertices_used) < nr - 1:
356
i = next(ii for ii in range(1, nr) if ii not in vertices_used)
357
orbit = []
358
for sigma in isom_list:
359
if sigma[i - 1] not in orbit:
360
orbit.append(sigma[i - 1])
361
vertices_used.append(sigma[i - 1])
362
orbit.sort()
363
orbit_list.append(orbit)
364
return orbit_list
365
366
for i in range(1, nr):
367
for k in range(1, nc):
368
if M[i, k][0] == 2 and M[i, k][1] == M[i, k][2]:
369
count *= 2
370
L = []
371
for k in range(1, nc):
372
if M[i, k] != 0:
373
if sum(1 for j in range(1, nr) if M[j, k] != 0) == 1:
374
L.append([M[0, k], M[i, k]])
375
count *= aut(L)
376
377
for j in range(1, i):
378
L = []
379
for k in range(1, nc):
380
if M[i, k] != 0 and M[j, k] != 0:
381
L.append([M[i, k], M[j, k]])
382
count *= aut(L)
383
return count
384
385
386
def graph_list_isomorphisms(G1, G2, only_one=False):
387
r"""
388
Return the list of isomorphisms from ``G1`` to ``G2``.
389
"""
390
# Warning: does not count loops!
391
# If this is too slow, we can probably improve by caching a list of automorphisms and applying those to the first isom found.
392
if G1.invariant != G2.invariant:
393
return []
394
395
M1 = G1.M
396
M2 = G2.M
397
group1 = G1.vertex_groupings
398
group2 = G2.vertex_groupings
399
400
nr = M1.nrows()
401
nc = M2.ncols()
402
isom_list = []
403
for sigma_data in itertools.product(*(itertools.permutations(range(len(group))) for group in group1)):
404
sigma = [0] * (nr - 1)
405
for i in range(len(group1)):
406
for j in range(len(group1[i])):
407
sigma[group1[i][j] - 1] = group2[i][sigma_data[i][j]]
408
good = True
409
for i in range(1, nr):
410
ii = sigma[i - 1]
411
for j in range(1, i):
412
jj = sigma[j - 1]
413
L1 = []
414
for k in range(1, nc):
415
if M1[i, k] != 0 and M1[j, k] != 0:
416
L1.append([M1[i, k], M1[j, k]])
417
L1.sort()
418
L2 = []
419
for k in range(1, nc):
420
if M2[ii, k] != 0 and M2[jj, k] != 0:
421
L2.append([M2[ii, k], M2[jj, k]])
422
L2.sort()
423
if L1 != L2:
424
good = False
425
break
426
if not good:
427
break
428
if good:
429
cols1 = [[M1[i, j] for i in range(nr)] for j in range(1, nc)]
430
cols2 = [[M2[0, j]] + [M2[sigma[i - 1], j] for i in range(1, nr)]
431
for j in range(1, nc)]
432
edge_group1 = []
433
edge_group2 = []
434
used1 = []
435
for j in range(1, nc):
436
if j not in used1:
437
edge_group1.append([])
438
edge_group2.append([])
439
for k in range(1, nc):
440
if cols1[k - 1] == cols1[j - 1]:
441
edge_group1[-1].append(k)
442
used1.append(k)
443
if cols2[k - 1] == cols1[j - 1]:
444
edge_group2[-1].append(k)
445
for edge_sigma_data in itertools.product(*(itertools.permutations(range(len(edge_group))) for edge_group in edge_group1)):
446
edge_sigma = [0 for i in range(nc - 1)]
447
for i in range(len(edge_group1)):
448
for j in range(len(edge_group1[i])):
449
edge_sigma[edge_group1[i][j] -
450
1] = edge_group2[i][edge_sigma_data[i][j]]
451
isom_list.append([sigma, edge_sigma])
452
if only_one:
453
return isom_list
454
return isom_list
455
456
457
def degenerate(G_list, moduli_type=MODULI_ST):
458
r"""
459
Return the list of degenerations of the graphs in ``G_list`` up to
460
isomorphisms.
461
"""
462
mod_size = moduli_type + 1
463
if moduli_type == MODULI_SMALL:
464
mod_size = MODULI_SM + 1
465
G_list_new = [[] for i in range(mod_size)]
466
for which_type in range(mod_size):
467
for G in G_list[which_type]:
468
for i in range(1, G.num_vertices() + 1):
469
row = list(G.M[i])
470
m = row[0] + sum(row)
471
if m < 4:
472
continue
473
row1 = [0 for j in range(len(row))]
474
while [2 * x for x in row1] <= row:
475
if row1[0] == 1 and moduli_type <= MODULI_RT:
476
break
477
if row1[0] + sum(row1) >= 2 and row1[0] + sum(row1) <= m - 2:
478
row2 = [row[j] - row1[j] for j in range(len(row))]
479
G_copy = Graph(G.M)
480
G_copy.split_vertex(i, row1, row2)
481
new_type = which_type
482
if new_type == MODULI_SM:
483
new_type = MODULI_RT
484
if new_type == MODULI_RT and row1[0] > 0:
485
new_type = MODULI_CT
486
G_list_new[new_type].append(G_copy)
487
row1[-1] += 1
488
for j in range(1, len(row)):
489
if row1[-j] <= row[-j]:
490
break
491
row1[-j] = 0
492
row1[-j - 1] += 1
493
for i in range(mod_size):
494
G_list_new[i] = remove_isomorphic(G_list_new[i])
495
return G_list_new
496
497
498
def decorate(G_list, r, moduli_type=MODULI_ST):
499
r"""
500
Add decorations to the graphs in ``G_list``.
501
"""
502
mod_size = moduli_type + 1
503
if moduli_type == MODULI_SMALL:
504
mod_size = MODULI_SM + 1
505
G_list_new = [[] for i in range(mod_size)]
506
for which_type in range(mod_size):
507
for G in G_list[which_type]:
508
G_deco = [[] for i in range(mod_size)]
509
nr, nc = G.M.nrows(), G.M.ncols()
510
two_list = []
511
one_list = []
512
for i in range(1, nr):
513
for j in range(1, nc):
514
if G.M[i, j] == 2:
515
two_list.append([i, j])
516
elif G.M[i, j] == 1:
517
one_list.append([i, j])
518
a = nr - 1
519
b = len(two_list)
520
c = len(one_list)
521
dims = [[dim_form(G.M[i + 1, 0][0], G.degree(i + 1), mod_type)
522
for i in range(a)] for mod_type in range(mod_size)]
523
for vec in IntegerVectors(r, a + b + c):
524
new_type = which_type
525
if moduli_type > MODULI_SMALL:
526
test_dims = vec[:a]
527
for i in range(b):
528
test_dims[two_list[i][0] - 1] += vec[a + i]
529
for i in range(c):
530
test_dims[one_list[i][0] - 1] += vec[a + b + i]
531
for mod_type in range(which_type, mod_size):
532
for i in range(a):
533
if test_dims[i] > dims[mod_type][i]:
534
new_type = mod_type + 1
535
break
536
if new_type > moduli_type:
537
continue
538
S_list = []
539
for i in range(a):
540
S_list.append(Partitions(vec[i]))
541
for i in range(a, a + b):
542
S_list.append(
543
[[vec[i] - j, j] for j in range(floor(vec[i] / ZZ(2) + 1))])
544
S = itertools.product(*S_list)
545
for vec2 in S:
546
G_copy = Graph(G.M)
547
for i in range(a):
548
for j in vec2[i]:
549
G_copy.M[i + 1, 0] += X**j
550
for i in range(a, a + b):
551
G_copy.M[two_list[i - a][0], two_list[i - a]
552
[1]] += vec2[i][0] * X + vec2[i][1] * X**2
553
for i in range(c):
554
G_copy.M[one_list[i][0],
555
one_list[i][1]] += vec[i + a + b] * X
556
G_deco[new_type].append(G_copy)
557
for mod_type in range(mod_size):
558
G_list_new[mod_type] += remove_isomorphic(G_deco[mod_type])
559
return G_list_new
560
561
562
def remove_isomorphic(G_list):
563
r"""
564
Return a list of isomorphism class representatives of the graphs in ``G_list``.
565
"""
566
G_list_new = []
567
inv_dict = {}
568
count = 0
569
for G1 in G_list:
570
G1.compute_invariant()
571
if G1.invariant not in inv_dict:
572
inv_dict[G1.invariant] = []
573
good = True
574
for i in inv_dict[G1.invariant]:
575
if graph_isomorphic(G1, G_list_new[i]):
576
good = False
577
break
578
if good:
579
G_list_new.append(G1)
580
inv_dict[G1.invariant].append(count)
581
count += 1
582
return G_list_new
583
584
585
def num_strata(g, r, markings=(), moduli_type=MODULI_ST):
586
r"""
587
Return the number of strata in given genus, rank, markings and moduli type.
588
589
EXAMPLES::
590
591
sage: from admcycles.DR.moduli import MODULI_SM, MODULI_CT, MODULI_RT, MODULI_ST
592
sage: from admcycles.DR.graph import num_strata
593
sage: for r in range(4):
594
....: print('r={}: {} {} {} {}'.format(r,
595
....: num_strata(2, r, (1, 1), MODULI_SM),
596
....: num_strata(2, r, (1, 1), MODULI_RT),
597
....: num_strata(2, r, (1, 1), MODULI_CT),
598
....: num_strata(2, r, (1, 1), MODULI_ST)))
599
r=0: 1 1 1 1
600
r=1: 2 3 5 6
601
r=2: 0 7 16 28
602
r=3: 0 0 38 113
603
"""
604
return len(all_strata(g, r, markings, moduli_type))
605
606
607
def num_pure_strata(g, r, markings=(), moduli_type=MODULI_ST):
608
r"""
609
Return the number of pure strata in given genus, rank, markings and moduli type.
610
611
EXAMPLES::
612
613
sage: from admcycles.DR.moduli import MODULI_SM, MODULI_CT, MODULI_RT, MODULI_ST
614
sage: from admcycles.DR.graph import num_pure_strata
615
sage: for r in range(4):
616
....: print('r={}: {} {} {} {}'.format(r,
617
....: num_pure_strata(2, r, (1, 1), MODULI_SM),
618
....: num_pure_strata(2, r, (1, 1), MODULI_RT),
619
....: num_pure_strata(2, r, (1, 1), MODULI_CT),
620
....: num_pure_strata(2, r, (1, 1), MODULI_ST)))
621
r=0: 1 1 1 1
622
r=1: 0 1 3 4
623
r=2: 0 0 3 10
624
r=3: 0 0 2 19
625
"""
626
return len(all_pure_strata(g, r, markings, moduli_type))
627
628
629
def single_stratum(num, g, r, markings=(), moduli_type=MODULI_ST):
630
r"""
631
Return the ``num``-th stratum.
632
"""
633
return all_strata(g, r, markings, moduli_type)[num]
634
635
636
def single_pure_stratum(num, g, r, markings=(), moduli_type=MODULI_ST):
637
r"""
638
Return the ``num``-th pure stratum.
639
"""
640
return all_pure_strata(g, r, markings, moduli_type)[num]
641
642
643
@cached_function
644
def autom_count(num, g, r, markings=(), moduli_type=MODULI_ST):
645
return graph_count_automorphisms(single_stratum(num, g, r, markings, moduli_type))
646
647
648
@cached_function
649
def pure_strata_autom_count(num, g, r, markings=(), moduli_type=MODULI_ST):
650
return graph_count_automorphisms(single_pure_stratum(num, g, r, markings, moduli_type))
651
652
653
@cached_function
654
def automorphism_cosets(num, g, r, markings=(), moduli_type=MODULI_ST):
655
G = single_stratum(num, g, r, markings, moduli_type)
656
pureG = Graph(G.M)
657
pureG.purify()
658
pureG.compute_invariant()
659
pure_auts = graph_list_isomorphisms(pureG, pureG)
660
num_pure = len(pure_auts)
661
impure_auts = graph_list_isomorphisms(G, G)
662
num_impure = len(impure_auts)
663
chosen_auts = []
664
used_auts = []
665
v = G.num_vertices()
666
e = G.num_edges()
667
for i in range(num_pure):
668
if i not in used_auts:
669
chosen_auts.append(pure_auts[i])
670
for g in impure_auts:
671
sigma = [[pure_auts[i][0][g[0][k] - 1]
672
for k in range(v)], [pure_auts[i][1][g[1][k] - 1] for k in range(e)]]
673
for ii in range(num_pure):
674
if pure_auts[ii] == sigma:
675
used_auts.append(ii)
676
break
677
return [num_impure, chosen_auts]
678
679
680
@cached_function
681
def unpurify_map(g, r, markings=(), moduli_type=MODULI_ST):
682
r"""
683
Return a dictionary which maps pure strata to list of strata.
684
685
EXAMPLES::
686
687
sage: from admcycles.DR.graph import unpurify_map
688
sage: unpurify_map(2, 2)
689
{(0, 0): [0, 1], (1, 0): [2, 3], (1, 1): [4, 5], (2, 0): [6], (2, 1): [7]}
690
"""
691
unpurify = {}
692
pure_strata = [all_pure_strata(g, r0, markings, moduli_type)
693
for r0 in range(r + 1)]
694
impure_strata = all_strata(g, r, markings, moduli_type)
695
for i, strati in enumerate(impure_strata):
696
G = Graph(strati.M)
697
G.purify()
698
r0 = G.num_edges() - len(markings)
699
found = False
700
for j in range(len(pure_strata[r0])):
701
if G.M == pure_strata[r0][j].M:
702
G_key = (r0, j)
703
found = True
704
break
705
assert found, "failed purification"
706
if G_key not in unpurify:
707
unpurify[G_key] = []
708
unpurify[G_key].append(i)
709
return unpurify
710
711
712
@cached_function
713
def all_strata(g, r, markings=(), moduli_type=MODULI_ST):
714
r"""
715
Return the lists of strata with given genus, degree, markings and moduli.
716
717
EXAMPLES::
718
719
sage: from admcycles.DR.graph import all_strata
720
sage: all_strata(2, 1, (1, 1))
721
[[ -1 1 1]
722
[X + 2 1 1],
723
[ -1 1 1]
724
[ 2 X + 1 1],
725
[-1 1 1 0]
726
[ 0 1 1 1]
727
[ 2 0 0 1],
728
[-1 1 1 0]
729
[ 1 0 0 1]
730
[ 1 1 1 1],
731
[-1 1 1 0]
732
[ 1 0 1 1]
733
[ 1 1 0 1],
734
[-1 0 1 1]
735
[ 1 2 1 1]]
736
"""
737
mod_size = moduli_type + 1
738
if moduli_type == MODULI_SMALL:
739
mod_size = MODULI_SM + 1
740
big_list = [[] for i in range(mod_size)]
741
for loops in range(g + 1):
742
if loops == 1 and moduli_type <= MODULI_CT:
743
break
744
if loops > r:
745
break
746
for edges in range(r - loops + 1):
747
if edges == 1 and moduli_type <= MODULI_SM:
748
break
749
G = Graph()
750
G.add_vertex(g - loops)
751
for k in range(loops):
752
G.add_edge(1, 1)
753
for k in markings:
754
G.add_edge(1, 0, k)
755
GGG = [[] for i in range(mod_size)]
756
if loops == 0:
757
if edges == 0:
758
GGG[MODULI_SM] = [G]
759
else:
760
GGG[MODULI_RT] = [G]
761
else:
762
GGG[MODULI_ST] = [G]
763
for k in range(edges):
764
GGG = degenerate(GGG, moduli_type)
765
GGG = decorate(GGG, r - loops - edges, moduli_type)
766
for i in range(mod_size):
767
big_list[i] += GGG[i]
768
combined_list = []
769
for i in range(mod_size):
770
combined_list += big_list[i]
771
return combined_list
772
773
774
@cached_function
775
def all_pure_strata(g, r, markings=(), moduli_type=MODULI_ST):
776
r"""
777
Return the lists of pure strata with given genus, degree, markings and moduli.
778
779
EXAMPLES::
780
781
sage: from admcycles.DR.graph import all_pure_strata
782
sage: from admcycles.DR.moduli import MODULI_CT
783
sage: all_pure_strata(2, 0, (1, 1))
784
[[-1 1 1]
785
[ 2 1 1]]
786
sage: all_pure_strata(2, 1, (1, 1), MODULI_CT)
787
[[-1 1 1 0]
788
[ 0 1 1 1]
789
[ 2 0 0 1],
790
[-1 1 1 0]
791
[ 1 0 0 1]
792
[ 1 1 1 1],
793
[-1 1 1 0]
794
[ 1 0 1 1]
795
[ 1 1 0 1]]
796
"""
797
big_list = [[] for i in range(moduli_type + 1)]
798
for loops in range(g + 1):
799
if loops == 1 and moduli_type <= MODULI_CT:
800
break
801
if loops > r:
802
break
803
for edges in range(r - loops, r - loops + 1):
804
if edges >= 1 and moduli_type <= MODULI_SM:
805
break
806
G = Graph()
807
G.add_vertex(g - loops)
808
for k in range(loops):
809
G.add_edge(1, 1)
810
for k in markings:
811
G.add_edge(1, 0, k)
812
G.compute_invariant()
813
GGG = [[] for i in range(moduli_type + 1)]
814
if loops == 0:
815
if edges == 0:
816
GGG[MODULI_SM] = [G]
817
else:
818
GGG[MODULI_RT] = [G]
819
else:
820
GGG[MODULI_ST] = [G]
821
for k in range(edges):
822
GGG = degenerate(GGG, moduli_type)
823
for i in range(moduli_type + 1):
824
big_list[i] += GGG[i]
825
combined_list = []
826
for i in range(moduli_type + 1):
827
combined_list += big_list[i]
828
return combined_list
829
830
831
@cached_function
832
def strata_invariant_lookup(g, r, markings=(), moduli_type=MODULI_ST):
833
inv_dict = {}
834
L = all_strata(g, r, markings, moduli_type)
835
for i in range(len(L)):
836
if L[i].invariant not in inv_dict:
837
inv_dict[L[i].invariant] = []
838
inv_dict[L[i].invariant].append(i)
839
return inv_dict
840
841
842
@cached_function
843
def num_of_stratum(G, g, r, markings=(), moduli_type=MODULI_ST):
844
r"""
845
Return the index of the graph ``G`` in the list of all strata with given
846
genus, rank, markings and moduli type.
847
848
This is the inverse of :func:`single_stratum`.
849
850
EXAMPLES::
851
852
sage: from admcycles.DR.graph import Graph, X, R, num_of_stratum
853
sage: G = Graph(matrix(R, 2, 3, [-1, 1, 2, X+1, 1, 1]))
854
sage: num_of_stratum(G, 1, 1, (1, 2))
855
0
856
"""
857
G.compute_invariant()
858
L = all_strata(g, r, markings, moduli_type)
859
LL = strata_invariant_lookup(g, r, markings, moduli_type)
860
x = LL[G.invariant]
861
862
if len(x) == 1:
863
return x[0]
864
for i in x:
865
if graph_isomorphic(G, L[i]):
866
return i
867
868
raise ValueError("wrong Graph with g={}, r={}, markings={}, moduli_type={}\n{}".format(
869
g, r, markings, moduli_type, G))
870
871
872
def list_strata(g, r, n=0, moduli_type=MODULI_ST):
873
"""
874
Displays the list of all strata.
875
876
EXAMPLES::
877
878
sage: from admcycles.DR.graph import list_strata
879
sage: list_strata(1,1,2)
880
generator 0
881
[ -1 1 2]
882
[X + 1 1 1]
883
------------------------------
884
generator 1
885
[ -1 1 2]
886
[ 1 X + 1 1]
887
------------------------------
888
generator 2
889
[ -1 1 2]
890
[ 1 1 X + 1]
891
------------------------------
892
generator 3
893
[-1 1 2 0]
894
[ 0 1 1 1]
895
[ 1 0 0 1]
896
------------------------------
897
generator 4
898
[-1 0 1 2]
899
[ 0 2 1 1]
900
------------------------------
901
"""
902
L = all_strata(g, r, tuple(range(1, n + 1)), moduli_type)
903
for i, Li in enumerate(L):
904
print("generator %s" % i)
905
print(Li.M)
906
print("-" * 30)
907
908
909
@cached_function
910
def contraction_table(g, r, markings=(), moduli_type=MODULI_ST):
911
contraction_dict = {}
912
pure_strata = [all_pure_strata(g, r0, markings, moduli_type)
913
for r0 in range(r + 1)]
914
for r0 in range(r + 1):
915
for ii in range(len(pure_strata[r0])):
916
G = pure_strata[r0][ii]
917
S = [j for j in range(1, G.M.ncols()) if G.M[0, j] == 0]
918
contractions = {}
919
for edge_subset in itertools.product(*[[0, 1] for i in range(r0)]):
920
key = tuple(i for i in range(
921
r0) if edge_subset[i] == 0)
922
A = [S[i] for i in key]
923
A.reverse()
924
vlist = [[i] for i in range(1, G.M.nrows())]
925
elist = list(range(1, G.M.ncols()))
926
Gcopy = Graph(G.M)
927
for i in A:
928
vlist, elist = Gcopy.contract(i, vlist, elist)
929
Gcopy.compute_invariant()
930
rnew = r0 - len(A)
931
contraction_result = []
932
for i in range(len(pure_strata[rnew])):
933
L = graph_list_isomorphisms(
934
pure_strata[rnew][i], Gcopy, True)
935
if L:
936
contraction_result.append((rnew, i))
937
contraction_result.append(L[0])
938
break
939
contraction_result.append((vlist, elist))
940
contractions[key] = contraction_result
941
942
for edge_assignment in itertools.product(*[[0, 1, 2] for i in range(r0)]):
943
if sum(1 for i in edge_assignment if i == 1) > r - r0:
944
continue
945
key1 = tuple(i for i in range(
946
r0) if edge_assignment[i] == 0)
947
B = [S[i]
948
for i in range(r0) if edge_assignment[i] == 1]
949
key2 = tuple(i for i in range(
950
r0) if edge_assignment[i] == 2)
951
if key1 > key2:
952
continue
953
contract1 = contractions[key1]
954
contract2 = contractions[key2]
955
dict_key = [contract1[0], contract2[0]]
956
dict_entry = [contract1[1], contract2[1]]
957
if dict_key[0] > dict_key[1]:
958
dict_key.reverse()
959
dict_entry.reverse()
960
dict_entry = [(r0, ii), B, contract2[2],
961
contract1[2]] + dict_entry
962
else:
963
dict_entry = [(r0, ii), B, contract1[2],
964
contract2[2]] + dict_entry
965
dict_key = tuple(dict_key)
966
if dict_key not in contraction_dict:
967
contraction_dict[dict_key] = []
968
contraction_dict[dict_key].append(dict_entry)
969
if dict_key[0] == dict_key[1]:
970
contraction_dict[dict_key].append(
971
dict_entry[:2] + [dict_entry[3], dict_entry[2], dict_entry[5], dict_entry[4]])
972
return contraction_dict
973
974