Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
180 views
unlisted
ubuntu2004
1
import itertools
2
from math import ceil
3
4
from sympy.utilities.iterables import partitions, permutations, multiset_partitions
5
6
# pylint does not know sage
7
from sage.graphs.graph import Graph # pylint: disable=import-error
8
# sage documentation says compositions are better than OrderedPartitions,
9
# no idea why....
10
from sage.combinat.composition import Compositions as sage_part # pylint: disable=import-error
11
from sage.combinat.partition import OrderedPartitions # pylint: disable=import-error
12
from sage.misc.cachefunc import cached_function # pylint: disable=import-error
13
14
import admcycles.diffstrata.levelgraph
15
from admcycles.diffstrata.sig import Signature
16
17
############################################################
18
############################################################
19
# Old BIC generation:
20
# This was the first attempt at generating BICs
21
# (generating all stable graphs with admcycles and then
22
# putting all possible bipartite structures etc on them).
23
# It's nice for historic reasons but painfully slow!
24
# Definitely use bic_alt below instead!!!
25
############################################################
26
############################################################
27
28
29
def SageGraph(gr):
30
# converts stgraph gr in Sage graph
31
legdic = {i: vnum for vnum in range(
32
len(gr.legs(copy=False))) for i in gr.legs(vnum, copy=False)}
33
return Graph([(legdic[a], legdic[b])
34
for (a, b) in gr.edges(copy=False)], loops=True, multiedges=True)
35
36
37
def bics(g, orders):
38
"""
39
Generate BICs for stratum with signature orders of genus g.
40
41
DO NOT USE!!! USE bic_alt instead!!!
42
43
Args:
44
g (int): genus
45
orders (tuple): signature tuple
46
47
Returns:
48
list: list of BICs
49
"""
50
print('WARNING! This is not the normal BIC generation algorithm!')
51
print("Only use if you know what you're doing!")
52
n = len(orders)
53
bound = g + n
54
result = [] # list of levelgraphs
55
orderdict = {i + 1: orders[i] for i in range(n)}
56
57
for e in range(1, bound + 1):
58
# look at which stgraphs in Mbar_g,n with e edges are bipartite
59
for gr in new_list_strata(g, n, e):
60
check = SageGraph(gr).is_bipartite(True)
61
if check[0]:
62
# check[1] contains dictionary {vertices: 0 or 1}
63
vert1 = [v for v in check[1] if check[1][v] == 0]
64
vert2 = [v for v in check[1] if check[1][v] == 1]
65
result += bics_on_bipgr(gr, vert1, vert2, orderdict)
66
result += bics_on_bipgr(gr, vert2, vert1, orderdict)
67
return result
68
69
70
def bics_on_bipgr(gr, vertup, vertdown, orderdict):
71
# takes stable graph and partition of range(len(gr.genera)) into vertup, vertdown
72
# creates list of possible pole orders at edges
73
result = []
74
75
# removed from admcycles so reinserted here for legacy purposes:
76
def dicunion(*dicts):
77
return dict(itertools.chain.from_iterable(dct.items()
78
for dct in dicts))
79
80
numvert = len(gr.genera(copy=False))
81
halfedges = gr.halfedges()
82
halfedgeinvers = dict(gr.edges(copy=False))
83
halfedgeinvers.update({e1: e0 for (e0, e1) in gr.edges(copy=False)})
84
85
levels = [0 for i in range(numvert)]
86
for i in vertdown:
87
levels[i] = -1
88
89
helist = [[l for l in ll if l in halfedges]
90
for ll in gr.legs(copy=False)] # list of half-edges of each vertex
91
# list (with len numvert) of orders that need to be provided by pole
92
# orders of half-edges (not legs)
93
degs = []
94
for v in range(numvert):
95
degs.append(2 * gr.genera(v) - 2 - sum([orderdict[l]
96
for l in gr.list_markings(v)]))
97
# if degs<0:
98
# return []
99
100
weightparts = []
101
for v in vertup:
102
vweightpart = []
103
for part in OrderedPartitions(degs[v] + len(helist[v]), len(helist[v])):
104
# dictionary of weights from edges connected to vertex v
105
vdic = {helist[v][i]: part[i] - 1 for i in range(len(helist[v]))}
106
vdic.update(
107
{halfedgeinvers[helist[v][i]]: -part[i] - 1 for i in range(len(helist[v]))})
108
# print vdic
109
vweightpart.append(vdic)
110
weightparts += [vweightpart]
111
# print weightparts
112
113
for parts in itertools.product(*weightparts):
114
# print parts
115
poleorders = dicunion(orderdict, *parts)
116
CandGraph = admcycles.diffstrata.levelgraph.LevelGraph(
117
gr.genera(
118
copy=False), gr.legs(
119
copy=False), gr.edges(
120
copy=False), poleorders, levels, quiet=True)
121
if CandGraph.is_legal(True) and CandGraph.checkadmissible(True):
122
result.append(CandGraph)
123
return result
124
125
126
def new_list_strata(g, n, r):
127
return [lis[0] for lis in admcycles.admcycles.degeneration_graph(int(g), n, r)[
128
0][r]]
129
130
############################################################
131
############################################################
132
133
############################################################
134
############################################################
135
# New BIC generation. This is currently used.
136
############################################################
137
############################################################
138
139
140
@cached_function
141
def bic_alt_noiso(sig):
142
"""
143
Construct all non-horizontal divisors in the stratum sig.
144
145
More precisely, each BIC is LevelGraph with two levels numbered 0, -1
146
and marked points 1,...,n where the i-th point corresponds to the element
147
i-1 of the signature.
148
149
Note that this is the method called by GeneralisedStratum.gen_bic.
150
151
Args:
152
sig (Signature): Signature of the stratum.
153
154
Returns:
155
list: list of 2-level non-horizontal LevelGraphs.
156
157
EXAMPLES::
158
159
sage: from admcycles.diffstrata import *
160
sage: assert comp_list(bic_alt(Signature((1,1))),\
161
[LevelGraph([1, 0],[[3, 4], [1, 2, 5, 6]],[(3, 5), (4, 6)],{1: 1, 2: 1, 3: 0, 4: 0, 5: -2, 6: -2},[0, -1],True),\
162
LevelGraph([1, 1, 0],[[4], [3], [1, 2, 5, 6]],[(3, 5), (4, 6)],{1: 1, 2: 1, 3: 0, 4: 0, 5: -2, 6: -2},[0, 0, -1],True),\
163
LevelGraph([1, 1],[[3], [1, 2, 4]],[(3, 4)],{1: 1, 2: 1, 3: 0, 4: -2},[0, -1],True),\
164
LevelGraph([2, 0],[[3], [1, 2, 4]],[(3, 4)],{1: 1, 2: 1, 3: 2, 4: -4},[0, -1],True)])
165
166
sage: assert comp_list(bic_alt(Signature((2,))),\
167
[LevelGraph([1, 1],[[2], [1, 3]],[(2, 3)],{1: 2, 2: 0, 3: -2},[0, -1],True),\
168
LevelGraph([1, 0],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 2, 2: 0, 3: 0, 4: -2, 5: -2},[0, -1],True)])
169
170
sage: assert comp_list(bic_alt(Signature((4,))),\
171
[LevelGraph([1, 1, 0],[[2, 4], [3], [1, 5, 6, 7]],[(2, 5), (3, 6), (4, 7)],{1: 4, 2: 0, 3: 0, 4: 0, 5: -2, 6: -2, 7: -2},[0, 0, -1],True),\
172
LevelGraph([1, 1, 1],[[3], [2], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 0, 3: 0, 4: -2, 5: -2},[0, 0, -1],True),\
173
LevelGraph([2, 0],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 2, 3: 0, 4: -4, 5: -2},[0, -1],True),\
174
LevelGraph([2, 0],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 1, 3: 1, 4: -3, 5: -3},[0, -1],True),\
175
LevelGraph([1, 0],[[2, 3, 4], [1, 5, 6, 7]],[(2, 5), (3, 6), (4, 7)],{1: 4, 2: 0, 3: 0, 4: 0, 5: -2, 6: -2, 7: -2},[0, -1],True),\
176
LevelGraph([1, 2],[[2], [1, 3]],[(2, 3)],{1: 4, 2: 0, 3: -2},[0, -1],True),\
177
LevelGraph([2, 1],[[2], [1, 3]],[(2, 3)],{1: 4, 2: 2, 3: -4},[0, -1],True),\
178
LevelGraph([1, 1],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 0, 3: 0, 4: -2, 5: -2},[0, -1],True)])
179
180
sage: len(bic_alt(Signature((1,1,1,1)))) # long time (2 seconds)
181
102
182
183
sage: len(bic_alt(Signature((2,2,0,-2))))
184
61
185
186
sage: len(bic_alt((2,2,0,-2)))
187
61
188
"""
189
if isinstance(sig, tuple):
190
sig = Signature(sig)
191
# for keeping track of the numbered half-edges, we remember the index
192
zeros = [i + 1 for i, a in enumerate(sig.sig) if a > 0]
193
z = sig.z
194
poles = [i + 1 for i, a in enumerate(sig.sig) if a < 0]
195
p = sig.p
196
marked_points = [i + 1 for i, a in enumerate(sig.sig) if a == 0]
197
mp = len(marked_points)
198
g = sig.g
199
orders = {i + 1: ord for i, ord in enumerate(sig.sig)}
200
201
n = sig.n
202
203
if sig.k == 1:
204
# As every g=0 top component needs at least one pole, the bottom max
205
# depends on this:
206
if p > 0:
207
g_bot_max = g
208
g_top_min = 0
209
pole_ind = [0, 1]
210
else:
211
g_bot_max = g - 1
212
g_top_min = 1
213
pole_ind = [0] # poles don't need to be distributed...
214
elif sig.k == 2:
215
# For quadratic differentials, g=0 top components without poles are
216
# possible.
217
g_bot_max = g
218
g_top_min = 0
219
pole_ind = [0, 1] if p > 0 else [0]
220
else:
221
raise ValueError("Not implemented for k>2")
222
223
only_even = sig.k == 2 and sig.difftype == "gs"
224
225
found = []
226
227
for bot_comp_len in range(1, z + mp + 1):
228
# every component on bottom level needs at least one zero or two marked points
229
# (in case it's genus 0 and has only one edge with a pole of order -2k going up...)
230
# Therefore, we split the zeros into top and bottom and distribute
231
# these:
232
for parts in itertools.chain(
233
multiset_partitions(zeros, 2), iter([[zeros, []]])):
234
for i in [0, 1]:
235
if mp > 0 or len(parts[i]) >= bot_comp_len:
236
bottom_zeros = parts[i]
237
top_zeros = parts[1 - i]
238
else:
239
continue
240
# if there are no marked points, every component needs a zero
241
# otherwise we are more flexible (this could be improved, but
242
# is good enoug for now)
243
if mp == 0:
244
bot_zero_gen = _distribute_fully(
245
bottom_zeros, bot_comp_len)
246
else:
247
bot_zero_gen = _distribute_points(
248
bottom_zeros, bot_comp_len)
249
for distr_bot_zeros in bot_zero_gen:
250
# partition genus into top, bottom and graph:
251
for total_g_bot in range(g_bot_max + 1):
252
for total_g_top in range(
253
g_top_min, g - total_g_bot + 1):
254
total_g_graph = g - total_g_bot - total_g_top
255
# partition bottom genus into components
256
for distr_bot_g in _distribute_part_ordered(
257
total_g_bot, bot_comp_len):
258
# first test:
259
# orders on each component must add up to k(2g-2), from now on only poles get
260
# added and we need at least one edge going up (adding at least a pole of order -(k+1))
261
# So if sum(zeros) < k(2g-2)+(k+1), we're
262
# screwed
263
if not all(sum(orders[bz] for bz in distr_bot_zeros[c]) >= sig.k * (
264
distr_bot_g[c] - 2) + sig.k + 1 for c in range(bot_comp_len)):
265
continue
266
# start distributing poles, first between top
267
# and bottom:
268
for pole_parts in itertools.chain(
269
multiset_partitions(poles, 2), iter([[poles, []]])):
270
for ip in pole_ind: # no poles => nothing to distribute
271
bottom_poles = pole_parts[ip]
272
top_poles = pole_parts[1 - ip]
273
# if k == 1, poles on top necessary for g=0 components
274
if sig.k == 1 and total_g_top == 0 and len(
275
top_poles) == 0:
276
continue
277
# poles are optional (i.e. not every vertex needs a pole)
278
for distr_bot_poles in _distribute_points(
279
bottom_poles, bot_comp_len):
280
# we save the "space" left to distribute among poles from
281
# half-edges:
282
spaces_bot = [(-sum(orders[tz] for tz in distr_bot_zeros[c])
283
- sum(orders[tp] for tp in distr_bot_poles[c])
284
+ sig.k * (2 * distr_bot_g[c] - 2)) for c in range(bot_comp_len)]
285
# retest: each space must be at least -(k+1)
286
if not all(s <= -(sig.k + 1)
287
for s in spaces_bot):
288
continue
289
# get the total orders of poles on the top components.
290
# Note that for k>1 there are poles coming from the edges.
291
if sig.k == 1:
292
# the total order of all poles on the top components
293
max_total_pole_order_top = sum(
294
orders[p] for p in top_poles)
295
# the maximal number of poles on the top components
296
max_poles_top = len(top_poles)
297
elif sig.k == 2:
298
# we get poles of order -1 from edges at the top iff we can
299
# add edges with poles of order -3 at the bottom end
300
max_poles_from_edges = sum(
301
[(-s / 3) if s % 3 == 0 else (int(-s / 3) - 1) for s in spaces_bot])
302
max_total_pole_order_top = sum(
303
orders[p] for p in top_poles) - max_poles_from_edges
304
max_poles_top = len(
305
top_poles) + max_poles_from_edges
306
else:
307
raise ValueError(
308
"Not implemented for k>2")
309
# each g=0 component on top level needs at least poles of total order -2*k
310
max_g0_top = min(
311
int(-max_total_pole_order_top / (2 * sig.k)), max_poles_top)
312
# iterate over top components
313
for top_comp_len in range(
314
1, total_g_top + max_g0_top + 1):
315
# now we know all vertices and the genus distribution, we know the number of edges:
316
num_of_edges = top_comp_len + bot_comp_len + total_g_graph - 1
317
# "global" condition on bottom: for each edge there will be
318
# at least another pole of order k+1
319
if (sum(sum(orders[bz] for bz in distr_bot_zeros[c])
320
+ sum(orders[bp] for bp in distr_bot_poles[c])
321
for c in range(bot_comp_len))
322
- (sig.k + 1) * num_of_edges < sig.k * (2 * total_g_bot - 2 * bot_comp_len)):
323
continue
324
# distribute genus and poles
325
for distr_top_g in _distribute_part_ordered(
326
total_g_top, top_comp_len):
327
for distr_top_poles in _distribute_points(
328
top_poles, top_comp_len):
329
# test: if orders add up to more than k(2g-2) this won't work
330
# (effectively this should only concern g=0 components here...)
331
if not all(sum(orders[tp] for tp in distr_top_poles[c])
332
# Edges may give additional poles if k > 1
333
+ (1 - sig.k) * \
334
num_of_edges
335
<= sig.k * (2 * distr_top_g[c] - 2)
336
for c in range(top_comp_len)):
337
continue
338
# distribute remaining zeros optionally
339
for distr_top_zeros in _distribute_points(
340
top_zeros, top_comp_len):
341
# again, we record the spaces:
342
spaces_top = [-sum(orders[tp] for tp in distr_top_poles[c])
343
- sum(orders[tz] for tz in distr_top_zeros[c])
344
+ sig.k *
345
(2 *
346
distr_top_g[c] - 2)
347
for c in range(top_comp_len)] # yapf: disable
348
# retest:
349
if not all(
350
s >= (1 - sig.k) * num_of_edges for s in spaces_top):
351
continue
352
# We distribute the edges together with their prongs/poleorders
353
# before we check for connectednes
354
# We start on bottom level, because there each half-edge
355
# comes with a mandatory pole:
356
for half_edges_bot, orders_he in _place_legs_on_bot(
357
spaces_bot, num_of_edges, n + num_of_edges, sig.k, only_even):
358
# note for the numbering of half-edges:
359
# * the HE on bot are numbered n+num_of_edges+1,...,n+2*num_of_edges
360
# * the HE on top are numbered the same for now, i.e. edges are (l,l) ,
361
# (and will be renumbered n+1,...,n+num_of_edges in a moment)
362
for half_edges_top in _place_legs_on_top(
363
spaces_top, orders_he, sig.k):
364
# check if graph is connected
365
if not is_connected(
366
half_edges_bot, half_edges_top):
367
continue
368
# add in the orders for top edges (note the offset)
369
for c in half_edges_bot:
370
for l in c:
371
orders_he[l - num_of_edges] = - \
372
2 * sig.k - orders_he[l]
373
genera = distr_top_g + distr_bot_g
374
# Distribute order 0 marked points
375
for distr_mp in _distribute_points(
376
marked_points, len(genera)):
377
# combine nested lists of legs:
378
legs = ([distr_top_zeros[c] + distr_top_poles[c]
379
+ distr_mp[c]
380
# renumber top HE
381
+ [l - num_of_edges for l in half_edges_top[c]]
382
for c in range(top_comp_len)] # top component
383
+ [distr_bot_zeros[c] + distr_bot_poles[c]
384
# offset
385
+ distr_mp[c + top_comp_len]
386
+ half_edges_bot[c]
387
for c in range(bot_comp_len)] # bot component
388
) # yapf: disable
389
# check stability:
390
if any(
391
len(ls) < 3 for c, ls in enumerate(legs) if genera[c] == 0):
392
continue
393
lg_data = (genera, legs,
394
[(l, l + num_of_edges) for l in range(
395
n + 1, n + num_of_edges + 1)],
396
# orders as dict
397
_merge_dicts(
398
orders, orders_he),
399
# levels
400
[0] * top_comp_len + \
401
[-1] * \
402
bot_comp_len,
403
# True
404
# #
405
# quiet
406
)
407
if sig.k == 1:
408
LG = admcycles.diffstrata.levelgraph.LevelGraph(
409
*lg_data)
410
elif sig.k == 2:
411
LG = admcycles.diffstrata.quadraticlevelgraph.QuadraticLevelGraph(
412
*lg_data)
413
else:
414
raise ValueError(
415
"Not implemented for k>2")
416
if LG.checkadmissible(
417
quiet=True):
418
if sig.k == 1:
419
if LG.is_legal(
420
quiet=True):
421
found.append(
422
LG)
423
elif sig.k == 2:
424
if (sig.difftype == "gs" and LG.is_legal_global_square(quiet=True)) \
425
or (sig.difftype == "p" and LG.is_legal_primitive(quiet=True)):
426
found.append(
427
LG)
428
else:
429
raise ValueError(
430
"Not implemented for k>2")
431
else:
432
# this should not happen!
433
print(
434
"Not admissible(!): ", LG)
435
found_new = []
436
found_set = set()
437
for x in found:
438
if x not in found_set:
439
found_new.append(x)
440
found_set.add(x)
441
# found = list(set(found)) # remove duplicates
442
return found_new
443
444
445
def bic_alt(sig):
446
"""
447
The BICs of the stratum sig up to isomorphism of LevelGraphs.
448
449
This should not be used directly, use a GeneralisedStratum or Stratum
450
instead to obtain EmbeddedLevelGraphs and the correct isomorphism classes.
451
452
Args:
453
sig (tuple): signature tuple
454
455
Returns:
456
list: list of LevelGraphs.
457
"""
458
return isom_rep(bic_alt_noiso(sig)) # remove duplicates up to iso
459
460
461
def _merge_dicts(x, y):
462
z = x.copy()
463
z.update(y)
464
return z
465
466
467
def _place_legs_on_bot(space, num_of_points, start, k, only_even):
468
# iterator distributing n legs with pole orders on (bottom) components according to space (each pole order is at least -(k+1))
469
# here space is a list of k(2g-2)-stuff we already placed on each bottom component
470
# return a pair (pointlist,orderdict) where
471
# * pointlist is a nested list of points (numbered start+1,...,n)
472
# * orderdict is a dictionary leg number : order
473
legal_splits = []
474
distr = [] # list to hold the current distribution
475
476
def split(n):
477
# distribute n points onto space and add to distr
478
# note that space and distr are accessed from back to front...
479
if n == 0:
480
# done
481
# correct signs and reverse order
482
legal_splits.append([[-a for a in c] for c in reversed(distr)])
483
return
484
else:
485
if not space: # check if empty
486
return
487
current = space.pop()
488
remaining_comp = len(space)
489
# there is a sign issue here: partitions are of positive integers, our pole orders are negative
490
# each leg has pole order at least k+1, so we only list these partitions
491
# moreover, we may place at most n-remaining_comp points on this
492
# component
493
if only_even:
494
possibilities = [[c * 2 for c in pos]
495
for pos in sage_part(-current / 2, min_part=ceil((k + 1) / 2.), max_length=n - remaining_comp)]
496
else:
497
possibilities = sage_part(-current, min_part=k + 1,
498
max_length=n - remaining_comp).list()
499
if possibilities: # check if non-empty
500
for possibility in possibilities:
501
distr.append(possibility)
502
# recursion
503
split(n - len(possibility))
504
# undo damage
505
distr.pop()
506
space.append(current)
507
# generate splits:
508
split(num_of_points)
509
# we now convert the generated lists (of pole orders) into the format: nested list of numbered legs
510
# Note:
511
# * the legs are numbered starting from start+1 to start+num_of_points+1
512
# * the orders are saved in the list of dictionaries edge_orders_bot
513
for dist in legal_splits:
514
order_dic = {}
515
p = start + 1
516
for c in range(len(dist)):
517
for a in range(len(dist[c])):
518
order_dic[p] = dist[c][a]
519
dist[c][a] = p
520
p += 1
521
yield (dist, order_dic)
522
523
524
def _place_legs_on_top(space, orders_bot, k):
525
# iterator distributing n legs with zero orders (determined by their friends on the bottom component)
526
# onto the top components according to space
527
# here space is a list of k(2g_comp-2)-stuff we already placed on each top component
528
# return a pointlist (numbered according to keys of orders_bot for now...)
529
legal_splits = []
530
distr = [[] for _ in space] # list to hold current distribution
531
# we sort the points by order, as the ones with the highest order are the hardest to place
532
# note that this is actually reverse sorted, as the top order is -2k-bottom order, but this
533
# is good, as we want to manipulate the list from the end
534
ordered_keys = [l for l, _ in reversed(
535
sorted(orders_bot.items(), key=lambda o: o[1]))]
536
537
def splits(keys):
538
# distribute the points (keys) onto spaces
539
if not keys:
540
# done if we hit all components and all spaces are 0
541
if all(distr) and all(s == 0 for s in space):
542
legal_splits.append([list(c) for c in distr])
543
return
544
else:
545
# check if there are enough points left
546
# components that don't have a point yet
547
remaining_comp = len([hit for hit in distr if not hit])
548
remaining_pole_order = 0 if k == 1 else sum(
549
o for o in [-2 * k - orders_bot[l] for l in keys] if o < 0)
550
if remaining_comp > len(keys):
551
return
552
current = keys.pop()
553
current_order = -2 * k - orders_bot[current]
554
# try to place current on all components
555
for i in range(len(space)):
556
if space[i] >= current_order + remaining_pole_order:
557
space[i] -= current_order
558
distr[i].append(current)
559
splits(keys) # recursion
560
# undo changes:
561
space[i] += current_order
562
distr[i].pop()
563
keys.append(current)
564
# generate splits:
565
splits(ordered_keys)
566
return legal_splits
567
568
569
def _distribute_fully(points, n):
570
# iterator distributing list of points onto n components, where each component gets at least one point
571
# return a nested list of points
572
# generate partitions into n subsets of permuted_points:
573
for part in multiset_partitions(points, n):
574
# we need to consider all permutations (multiset_partitions sorts!)
575
for permuted_points in permutations(part):
576
yield list(permuted_points) # permutations give tuples...
577
578
579
def _b_ary_gen(x, b, n):
580
# generator for reverse b-ary integers x of length n
581
for _ in itertools.repeat(None, n):
582
r = x % b
583
x = (x - r) // b
584
yield r
585
586
587
def _distribute_points(points, n):
588
# iterator distributing list of points onto n components (some might not receive any)
589
# return a nested list of points
590
l = len(points)
591
# n^l possibilities:
592
for i in range(n**l):
593
point_list = [[] for j in range(n)]
594
# n-ary representation of i tells us where the points should go
595
for pos, d in enumerate(_b_ary_gen(i, n, l)):
596
point_list[d].append(points[pos])
597
yield point_list
598
599
600
def _distribute_part_ordered(g, n):
601
# iterator of partitions (g_1,...,g_k) of g of length k <= n distributed onto n points
602
# return a list of length n (that sums to g)
603
if n < g:
604
maxi = n
605
else:
606
maxi = None
607
for part_dict in partitions(g, m=maxi):
608
# partitions are dictionaries {g_i : multiplicity}
609
part = []
610
for k in part_dict.keys():
611
part += [k] * part_dict[k]
612
# fill up with zeros:
613
part += (n - len(part)) * [0]
614
yield part
615
# we do not have to permute if everything else is permuted....
616
# for perm_part in set(permutations(part)):
617
# yield perm_part
618
619
620
def isom_rep(L):
621
"""
622
Return a list of representatives of isomorphism classes of L.
623
624
TODO: optimise!
625
"""
626
dist_list = []
627
for g in L:
628
if all(not g.is_isomorphic(h) for h in dist_list):
629
dist_list.append(g)
630
return dist_list
631
632
633
def comp_list(L, H):
634
r"""
635
Compare two lists of LevelGraphs (up to isomorphism).
636
637
Returns a tuple: (list L without H, list H without L)
638
"""
639
return ([g for g in L if not any(g.is_isomorphic(h) for h in H)],
640
[g for g in H if not any(g.is_isomorphic(h) for h in L)])
641
642
643
def is_connected(half_edges_top, half_edges_bottom):
644
"""
645
Check if graph given by the two sets of half edges is connected.
646
We do this by depth-first search.
647
Note that we assume that both ends of each edge have the same number,
648
i.e. each edge is of the form (j,j).
649
650
EXAMPLES::
651
652
sage: from admcycles.diffstrata.bic import is_connected
653
sage: is_connected([[1],[2]],[[1,2]])
654
True
655
sage: is_connected([[1],[2]],[[2],[1]])
656
False
657
"""
658
def _connected_comp(current_edges, other_edges,
659
seen_current, seen_other, current_vert=0):
660
seen_current.append(current_vert)
661
for e in current_edges[current_vert]:
662
for (i, edges) in enumerate(other_edges):
663
if e in edges:
664
if i not in seen_other:
665
_connected_comp(other_edges, current_edges,
666
seen_other, seen_current, i)
667
break
668
return (seen_current, seen_other)
669
seen_top = []
670
seen_bottom = []
671
_connected_comp(half_edges_top, half_edges_bottom, seen_top, seen_bottom)
672
return len(seen_top) == len(half_edges_top) and len(
673
seen_bottom) == len(half_edges_bottom)
674
675
676
def test_bic_algs(sig_list=None):
677
"""
678
Compare output of bics and bic_alt.
679
680
EXAMPLES::
681
682
sage: from admcycles.diffstrata import *
683
sage: test_bic_algs() # long time (45 seconds) # skip, not really needed + long # doctest: +SKIP
684
(1, 1): ([], [])
685
(1, 1, 0, 0, -2): ([], [])
686
(2, 0, -2): ([], [])
687
(1, 0, 0, 1): ([], [])
688
(1, -2, 2, 1): ([], [])
689
(2, 2): ([], [])
690
691
sage: test_bic_algs([(0,0),(2,1,1)]) # long time (50 seconds) # doctest: +SKIP
692
(0, 0): ([], [])
693
(2, 1, 1): ([], [])
694
695
sage: test_bic_algs([(1,0,-1),(2,),(4,),(1,-1)])
696
WARNING! This is not the normal BIC generation algorithm!
697
Only use if you know what you're doing!
698
(1, 0, -1): ([], [])
699
WARNING! This is not the normal BIC generation algorithm!
700
Only use if you know what you're doing!
701
(2,): ([], [])
702
WARNING! This is not the normal BIC generation algorithm!
703
Only use if you know what you're doing!
704
(4,): ([], [])
705
WARNING! This is not the normal BIC generation algorithm!
706
Only use if you know what you're doing!
707
(1, -1): ([], [])
708
"""
709
if sig_list is None:
710
sig_list = [(1, 1), (1, 1, 0, 0, -2), (2, 0, -2),
711
(1, 0, 0, 1), (1, -2, 2, 1), (2, 2)]
712
for sig in sig_list:
713
Sig = Signature(sig)
714
print(
715
"%r: %r" %
716
(Sig.sig,
717
comp_list(
718
bics(
719
Sig.g,
720
Sig.sig),
721
bic_alt(Sig))))
722
723