Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
180 views
unlisted
ubuntu2004
1
# pylint does not know sage
2
from sage.structure.sage_object import SageObject # pylint: disable=import-error
3
from sage.misc.flatten import flatten # pylint: disable=import-error
4
5
import admcycles.diffstrata.generalisedstratum
6
import admcycles.diffstrata.levelgraph
7
import admcycles.diffstrata.embeddedlevelgraph
8
9
#######################################################################
10
#######################################################################
11
# Point Dictionaries
12
#######################################################################
13
# * Points on generalised strata are of the form (i,j) where i is the
14
# index of the component and j is the index in the signature of
15
# the component.
16
# * An EmbeddedLevelGraph is a LevelGraph together with a dictionary
17
# mapping all non-half-edges (i.e. marked points) to the the points
18
# of the associated stratum.
19
# Note that the numbering of points starts with 1 while the indices
20
# of the signatures start with 0. I.e. in the case of a gen. stratum
21
# with only one component, the dictionary is i -> (0,i-1).
22
# * For clutching, we need dictionaries that map points of strata to
23
# points of other strata. This allows us to embed clutched LevelGraphs
24
# by composition.
25
# * When splitting a BIC into top and bottom, we consequently need to
26
# remember the dictionary that maps marked points of the top and
27
# bottom gen. stratum into the points of stratum the BIC was
28
# embedded into.
29
##
30
# Thus, a splitting of a BIC inside stratum X must result in two generalised
31
# strata, top and bottom, as well as three dictionaries:
32
# * emb_top: points of top -> points of X
33
# * emb_bot: points of bottom -> points of X
34
# * clutching: points of top -> points of bottom
35
# All of these dictionaries are injective, the keys of emp_top and clutching
36
# give a partition of the points of top, the keys of emb_bot and the values
37
# of clutching give a partition of the points of bottom and the values of
38
# emb_top and emb_bot give a partition of the points of X.
39
#######################################################################
40
#######################################################################
41
42
43
def clutch(
44
X,
45
top,
46
bottom,
47
clutch_dict,
48
emb_dict_top,
49
emb_dict_bot,
50
middle=None,
51
clutch_dict_lower={},
52
clutch_dict_long={},
53
emb_dict_mid={}):
54
"""
55
Clutch EmbeddedLevelGraphs top and bottom to get a new EmbeddedLevelGraph.
56
57
If middle is specified, we do a "double clutching" top - middle - bottom to
58
give a graph in X.
59
60
.. NOTE::
61
62
* this cannot be split into two steps, as the intermediate graph is
63
not a graph in X!
64
* We include the possibility to clutch points in the top component *past*
65
the middle to the bottom component (long edges!)
66
67
This is the inverse procedure to EmbeddedLevelGraph.split() and
68
GeneralisedStratum.doublesplit().
69
70
Note that if top or bottom is None, we return None.
71
72
INPUT:
73
74
X (GeneralisedStratum): Stratum that the clutched graph will live in
75
76
top (EmbeddedLevelGraph): Top component to be clutched
77
78
bottom (EmbeddedLevelGraph): Bottom component to be clutched
79
80
middle (EmbeddedLevelGraph, optional, defaults to None): Middle component to
81
be clutched.
82
83
clutch_dict (dict): The dictionary giving the clutching information,
84
(i.e. the edges that will be inserted).
85
In the case of a "simple clutch", i.e. top to bottom with no middle,
86
a dictionary:
87
88
* point on top stratum -> point on bottom stratum
89
In the case of a middle graph, i.e. a "double clutching":
90
* point on top stratum -> point on middle stratum.
91
In this case, we also need clutch_dict_lower for the other clutching
92
and clutch_dict_long for the long edges.
93
94
clutch_dict_lower (dict, optional): In the case of a "double clutching", the
95
info for the "lower" clutching, i.e. a dictionary
96
point on middle stratum -> point on bottom stratum
97
98
clutch_dict_long (dict, optional): In the case of a "double clutching", the
99
info for the "long edges", i.e. a dictionary
100
point on top stratum -> point on bottom stratum
101
102
emb_dict_top (dict): dictionary giving the embedding of the resulting
103
EmbeddedLevelGraph, i.e. points on top stratum -> point on
104
enveloping stratum.
105
106
emb_dict_bot (dict): dictionary giving the embedding of the resulting
107
EmbeddedLevelGraph, i.e. points on bottom stratum -> point on
108
enveloping stratum.
109
110
emb_dict_mid (dict, optional): dictionary giving the embedding of the
111
resulting EmbeddedLevelGraph, i.e. points on middle stratum -> point on
112
enveloping stratum.
113
114
OUTPUT:
115
116
EmbeddedLevelGraph: Clutched LevelGraph together with embedding.
117
118
Returns:
119
tuple: EmbeddedLevelGraph: as above
120
121
EXAMPLES::
122
123
sage: from admcycles.diffstrata import *
124
sage: X=GeneralisedStratum([Signature((1,1))])
125
sage: assert clutch(**X.bics[1].split()).is_isomorphic(X.bics[1])
126
sage: assert all(clutch(**B.split()).is_isomorphic(B) for B in X.bics)
127
128
The same works for 3-level graphs and doublesplit:
129
130
sage: X=GeneralisedStratum([Signature((1,1))])
131
sage: assert all(X.lookup_graph(*ep).is_isomorphic(clutch(**X.doublesplit(ep))) for ep in X.enhanced_profiles_of_length(2))
132
sage: X=GeneralisedStratum([Signature((2,2,-2))])
133
sage: assert all(X.lookup_graph(*ep).is_isomorphic(clutch(**X.doublesplit(ep))) for ep in X.enhanced_profiles_of_length(2)) # long time (4 seconds)
134
"""
135
if top is None or bottom is None:
136
return None
137
# Build new EmbeddedLevelGraph:
138
# This is somewhat similar to levelstratum's unite_embedded_graphs
139
# but here the result is embedded into a _different_ stratum!
140
# In case top, middle or bottom are LevelStrata, we replace them by their smooth
141
# component (this is mainly to allow clutch to eat the output of split...)
142
if isinstance(top, admcycles.diffstrata.generalisedstratum.LevelStratum):
143
top = top.smooth_LG
144
if isinstance(
145
bottom, admcycles.diffstrata.generalisedstratum.LevelStratum):
146
bottom = bottom.smooth_LG
147
if isinstance(
148
middle, admcycles.diffstrata.generalisedstratum.LevelStratum):
149
# in particular, middle is not None!
150
middle = middle.smooth_LG
151
# genera simply get combined
152
if middle is None:
153
newgenera = top.LG.genera + bottom.LG.genera
154
else:
155
newgenera = top.LG.genera + middle.LG.genera + bottom.LG.genera
156
# We renumber the levels consecutively starting at 0.
157
top_levels = top.LG.numberoflevels()
158
top_level_dict = {top.LG.internal_level_number(
159
l): -l for l in range(top_levels)}
160
newlevels = [top_level_dict[l] for l in top.LG.levels]
161
if middle is None:
162
mid_levels = 0
163
mid_level_dict = {}
164
else:
165
# the levels of the middle component have to be shifted:
166
mid_levels = middle.LG.numberoflevels()
167
mid_level_dict = {middle.LG.internal_level_number(l): -l - top_levels
168
for l in range(mid_levels)}
169
newlevels += [mid_level_dict[l] for l in middle.LG.levels]
170
bot_levels = bottom.LG.numberoflevels()
171
# The levels of the bottom component have to be shifted by both top and
172
# middle:
173
bot_level_dict = {
174
bottom.LG.internal_level_number(l): -
175
l -
176
top_levels -
177
mid_levels for l in range(bot_levels)}
178
newlevels += [bot_level_dict[l] for l in bottom.LG.levels]
179
# TODO: What is a sensible level dictionary?
180
# At the moment, we choose the identity.
181
newdlevels = {-l: -l for l in range(top_levels + mid_levels + bot_levels)}
182
# The legs have to be renumbered in analogy to unite_embedded_graphs:
183
newlegs = []
184
newedges = []
185
newpoleorders = {}
186
newdmp = {} # the new embedding is given by the emb_dicts
187
# For adding the new edges, we have to record the new leg numbers and this will
188
# happen at different stages (on each component).
189
# The idea is, for each set of edges, to create a list of the form
190
# [(starting leg 1, starting leg 2,....), (ending leg 1, ending leg 2,...)]
191
# and then zip these lists to create the edges.
192
# The points are currently stored (as points on the respective stratum) in the
193
# various clutching dictionaries.
194
# We want to move them to lists of the form described above.
195
if middle is None:
196
# If this is a "simple clutch", we only have to add edges between top
197
# and bottom.
198
clutch_edges = [] # the new edges to be added
199
else:
200
# Otherwise, we have to distinguish between:
201
# * edges from top to middle:
202
clutch_edges_tm = []
203
# * edges from middle to bottom:
204
clutch_edges_mb = []
205
# * and edges from top to bottom (going past middle):
206
clutch_edges_tb = []
207
leg_number_shift = 0 # the shift for renumbering legs
208
legs = 0 # leg counter
209
# We now go through the components, starting with the top:
210
#
211
# Note that we also have to choose the appropriate embedding dictionary.
212
# We will also have to record the clutching information, as the points are all
213
# going to be renamed, so we have to remember who will be clutched.
214
#
215
if middle is None:
216
# in the "simple clutch" case, it makes sense to also pass
217
# the clutch points to each component; these are stored in clutch_dict
218
comp_data = ((top, emb_dict_top, clutch_dict.keys()),
219
(bottom, emb_dict_bot, clutch_dict.values()))
220
else:
221
# We have to sort out the clutching in the loop anyway, so we just pass
222
# a dummy.
223
comp_data = ((top, emb_dict_top, {}),
224
(middle, emb_dict_mid, {}),
225
(bottom, emb_dict_bot, {}))
226
# emb_g: graph to be clutched
227
# emb_dict: dictionary
228
# marked points of the enveloping stratum of emb_g -> points of X
229
for emb_g, emb_dict, clutch_points in comp_data:
230
leg_dict = {} # old number -> new number
231
for i, l in enumerate(flatten(emb_g.LG.legs)):
232
# the point numbers on bottom component are shifted by the top
233
# number
234
newlegnumber = leg_number_shift + i + 1
235
leg_dict[l] = newlegnumber
236
# while we're at it, we add the pole orders:
237
newpoleorders[newlegnumber] = emb_g.LG.poleorders[l]
238
legs += 1
239
# For the dictionary of marked points (for the embedding), we
240
# must distinguish if this is a marked point or a half-edge.
241
# Marked points are simply the ones for which we have a key
242
# in emb_dict :-)
243
# The newdmp must then map this point to the image of its dmp in
244
# the new stratum, i.e. the image under emb_dict:
245
# Note that emb_dict eats stratum points, i.e. the image of the
246
# leg under dmp.
247
try:
248
newdmp[newlegnumber] = emb_dict[emb_g.dmp[l]]
249
except KeyError:
250
pass
251
leg_number_shift += legs
252
# build (nested) list of legs:
253
newlegs += [[leg_dict[l] for l in comp] for comp in emb_g.LG.legs]
254
# finally, the edges are renumbered accordingly:
255
newedges += [(leg_dict[e[0]], leg_dict[e[1]]) for e in emb_g.LG.edges]
256
# We now record the clutching information:
257
# Note that the points to be clutched are points on the Stratum!
258
#
259
# Because everything was renamed with leg_dict, we need to *now* remember
260
# the edges to clutch (see discussion above).
261
#
262
# For this, we now have to distinguish if we're working with
263
# two or three components:
264
# * In the two component case, the points to be clutched are
265
# the keys (top) and values (bottom) of clutch_dict.
266
# Said differently: clutch_dict encodes the edges that will be added.
267
if middle is None:
268
# We save "half" of the clutch points in each step, afterwards we just
269
# have to combine these lists to obtain all new edges.
270
clutch_edges.append(
271
tuple(leg_dict[emb_g.dmp_inv[p]] for p in clutch_points))
272
# * In the three component case,
273
# * the *keys* of clutch_dict are points of the *top* component
274
# * the *values* clutch_dict are those points on the *middle* component that
275
# are clutched to the *top* component
276
# * the *keys* of clutch_dict_lower are points of the *middle* component
277
# * the *values* of clutch_dict_lower are points of the *bottom* component that
278
# are clutched to the *middle* component
279
# * the *keys* of clutch_dict_long are points of the *top* component
280
# * the *values* of clutch_dict_long are points of the *bottom* component that
281
# are clutched to the *top* component (long edges going past the middle)
282
# Said differently:
283
# * clutch_dict encodes edges top - middle
284
# * clutch_dict_lower encodes edges middle - bottom
285
# * clutch_dict_long encodes edges top - bottom (long edges going past middle)
286
else:
287
# We save the tuple of legs on the current component:
288
if emb_g is top:
289
clutch_edges_tm.append(tuple(
290
leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict.keys()
291
))
292
clutch_edges_tb.append(tuple(
293
leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_long.keys()
294
))
295
elif emb_g is middle:
296
clutch_edges_tm.append(tuple(
297
leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict.values()
298
))
299
clutch_edges_mb.append(tuple(
300
leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_lower.keys()
301
))
302
else:
303
assert emb_g is bottom
304
clutch_edges_tb.append(tuple(
305
leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_long.values()
306
))
307
clutch_edges_mb.append(tuple(
308
leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_lower.values()
309
))
310
# Now the actual clutching happens, i.e. adding in the extra edges between
311
# the points of clutch_dict:
312
if middle is None:
313
assert len(clutch_edges) == 2
314
newedges += list(zip(*clutch_edges))
315
else:
316
assert len(clutch_edges_tm) == 2
317
assert len(clutch_edges_tb) == 2
318
assert len(clutch_edges_mb) == 2
319
newedges += list(zip(*clutch_edges_tm)) + \
320
list(zip(*clutch_edges_tb)) + \
321
list(zip(*clutch_edges_mb))
322
323
LG = admcycles.diffstrata.levelgraph.LevelGraph(
324
newgenera, newlegs, newedges, newpoleorders, newlevels
325
)
326
327
ELG = admcycles.diffstrata.embeddedlevelgraph.EmbeddedLevelGraph(
328
X, LG, newdmp, newdlevels)
329
330
if not ELG.is_legal():
331
# This should not happen if R-GRC is checked!
332
# print("Illegal clutch attempted: %r, %r" % (top,bottom))
333
raise RuntimeError("Illegal clutch, this should not happen anymore!")
334
335
return ELG
336
337
338
class GenDegenerationGraph(SageObject):
339
"""
340
The degeneration graph of the Generalised Stratum X.
341
342
A degeneration graph of a (generalised) stratum is built recursively.
343
For that we need to know:
344
345
* The BICs inside X
346
* For each BIC its top and bottom component (generalised strata)
347
* For each BIC the clutching map of these components
348
* For each top and bottom component of each BIC an embedding of their
349
respective BICs into BIC(X)
350
351
This allows us to translate products of BICs on X into clutchings of
352
products of BICs on top and bottom components and gives a recursive
353
procedure to compute the LevelGraph(s) for any product of BICs in X.
354
355
INPUT:
356
357
X (GeneralisedStratum): The GeneralisedStratum we give the degeneration
358
graph of.
359
"""
360
def __init__(self, X):
361
self.X = X
362
self.n = len(self.X.bics) # possibly generates all bics of X (!)
363
self._top_to_bic = [None for _ in range(
364
self.n)] # initialise on demand
365
self._bot_to_bic = [None for _ in range(
366
self.n)] # initialise on demand
367
self._middle_to_bic = {} # initialise on demand
368
self._top_to_bic_inv = [None for _ in range(
369
self.n)] # initialise on demand
370
self._bot_to_bic_inv = [None for _ in range(
371
self.n)] # initialise on demand
372
self._middle_to_bic_inv = {} # initialise on demand
373
# Note that while top_to_bic and bot_to_bic aren't injective, their
374
# images are disjoint, i.e. we can remove the guys we found for top
375
# from the ones we're searching through for bot (and vice versa).
376
# This happens for every bic, though.
377
self._unused_bics_top = [
378
list(range(self.n)) for _ in range(self.n)]
379
self._unused_bics_bot = [
380
list(range(self.n)) for _ in range(self.n)]
381
# To build all LevelGraphs inside a (generalised) stratum, we store all
382
# tuples of indices of bics that give a (non-empty!) 3-level graph:
383
# Note that the multiplicities tell us something about automorphisms
384
# so we keep track of them and the set in parallel (use
385
# _append_triple_prod() !!)
386
self._triple_prods = []
387
self._triple_prod_set = set()
388
self._triple_prod_set_list = None
389
390
def __repr__(self):
391
return "GenDegenerationGraph(X=%r)" % self.X
392
393
def _append_triple_prod(self, t):
394
# append the tuple t to _triple_prods (both the list and the set...)
395
self._triple_prods.append(t)
396
self._triple_prod_set.add(t)
397
398
@property
399
def triple_prods(self):
400
"""
401
A list of profiles of the 3-level graphs in X.
402
403
Note that this is generated on first call.
404
405
Returns:
406
list: list of tuples of length two of indices of X.bics.
407
"""
408
if self._triple_prod_set_list is None:
409
# the easiest way to initialise is to initialise all bic maps:
410
for i in range(self.n):
411
self.top_to_bic(i)
412
self.bot_to_bic(i)
413
self._triple_prod_set_list = list(self._triple_prod_set)
414
return self._triple_prod_set_list
415
416
def top_to_bic(self, i):
417
"""
418
A dictionary indices of BICs of X.bic[i].top -> indices of X.bics.
419
420
Note that this is created once and then reused.
421
422
Args:
423
i (int): Index of X.bics
424
425
Raises:
426
RuntimeError: Raised if the squish of the clutching of a degeneration of
427
the top component with X.bic[i].bot is not found in X.bics.
428
429
Returns:
430
dict: Dictionary mapping BICs of X.bic[i].top to indices of X.bics.
431
"""
432
if self._top_to_bic[i] is None:
433
B = self.X.bics[i]
434
top_to_bic = {} # index of B.top.bics -> index of X.bics
435
for j, Bt in enumerate(B.top.bics):
436
# Note that Bt is embedded (as a Graph) into B.top
437
# B.top is embedded (as a stratum) into B (via B.emb_top)
438
G = clutch(self.X, Bt, B.bot, B.clutch_dict,
439
B.emb_top, B.emb_bot)
440
if G is None:
441
# illegal clutch:
442
raise RuntimeError(
443
"Illegal clutch, this should not happen anymore!")
444
# Because the degeneration is clutched on top, squishing the
445
# bottom level gives a new divisor (i.e. delta(1))
446
squished_bic = G.delta(1)
447
for im_j in self._unused_bics_top[i]:
448
if im_j == i:
449
continue # B^2 is empty
450
if squished_bic.is_isomorphic(self.X.bics[im_j]):
451
top_to_bic[j] = im_j
452
# Record profile of the 3-level graph:
453
self._append_triple_prod((im_j, i))
454
if self._bot_to_bic[i] is None:
455
# otherwise bot_to_bic has already been constructed and
456
# this is futile...
457
try:
458
self._unused_bics_bot[i].remove(im_j)
459
except ValueError:
460
# this is a case where the map is non-injective
461
pass
462
break
463
else: # no break
464
raise RuntimeError(
465
"delta(1) of %s, %s, not found in list of BICs of %s" %
466
(G, squished_bic, self.X))
467
self._top_to_bic[i] = top_to_bic.copy()
468
return self._top_to_bic[i]
469
470
def bot_to_bic(self, i):
471
"""
472
A dictionary indices of BICs of X.bic[i].bot -> indices of X.bics.
473
474
Note that this is created once and then reused.
475
476
Args:
477
i (int): Index of X.bics
478
479
Raises:
480
RuntimeError: Raised if the squish of the clutching of a degeneration of
481
the bottom component with X.bic[i].top is not found in X.bics.
482
483
Returns:
484
dict: Dictionary mapping BICs of X.bic[i].bot to indices of X.bics.
485
"""
486
if self._bot_to_bic[i] is None:
487
B = self.X.bics[i]
488
bot_to_bic = {} # index of B.top.bics -> index of X.bics
489
for j, Bb in enumerate(B.bot.bics):
490
# Note that Bb is embedded (as a Graph) into B.bot
491
# B.bot is embedded (as a stratum) into B (via B.emb_bot)
492
G = clutch(self.X, B.top, Bb, B.clutch_dict,
493
B.emb_top, B.emb_bot)
494
if G is None:
495
# illegal clutch:
496
raise RuntimeError(
497
"Illegal clutch, this should not happen anymore!")
498
# Because the degeneration is clutched on bottom, squishing the
499
# top level gives a new divisor (i.e. delta(2))
500
squished_bic = G.delta(2)
501
for im_j in self._unused_bics_bot[i]:
502
if im_j == i:
503
continue # B^2 is empty
504
if squished_bic.is_isomorphic(self.X.bics[im_j]):
505
bot_to_bic[j] = im_j
506
# Record profile of the 3-level graph:
507
self._append_triple_prod((i, im_j))
508
if self._top_to_bic[i] is None:
509
# otherwise top_to_bic has already been constructed and
510
# this is futile...
511
try:
512
self._unused_bics_top[i].remove(im_j)
513
except ValueError:
514
# this is a case where the map is non-injective
515
pass
516
break
517
else: # no break
518
raise RuntimeError(
519
"delta(2) of %s, %s, not found in list of BICs of %s" %
520
(G, squished_bic, self.X))
521
self._bot_to_bic[i] = bot_to_bic.copy()
522
return self._bot_to_bic[i]
523
524
def middle_to_bic(self, enh_profile):
525
"""
526
A dictionary for each 3-level graph mapping (indices of) BICs of the middle level
527
to (indices of) BICs of the enveloping stratum.
528
529
Raises a RuntimeError if
530
531
* either enh_profile is not a 3-level graph
532
* or if a degeneration of the middle level is isomorphic
533
(after squishing) to more than one BIC of X.
534
* or if no isomorphic BIC of X is found.
535
536
INPUT:
537
538
enh_profile (tuple): Enhanced profile of a 3-level graph.
539
540
OUTPUT:
541
542
dictionary: indices of bics of the middle level -> indices of X.bics
543
"""
544
if enh_profile not in self._middle_to_bic:
545
p, i = enh_profile
546
if len(p) != 2:
547
raise RuntimeError(
548
"Only 3-level graphs have a well-defined middle! %r" %
549
(enh_profile,))
550
# The dictionary we want to create:
551
mid_to_bic = {}
552
# The easiest thing is to split the graph and reuse all the clutching
553
# info this yields, replacing the middle level by one of its BICs:
554
clutching_info = self.X.doublesplit(enh_profile)
555
L = clutching_info['middle']
556
# candidates are those BICs that appear as bottom degenerations
557
# of the top level *and* as top degenerations of the bottom level:
558
candidates = set()
559
top_deg = set(self.top_to_bic(p[1]).values())
560
for b in self.bot_to_bic(p[0]).values():
561
if b in top_deg:
562
candidates.add(b)
563
# We now clutch in degenerations of the middle level:
564
for i, B in enumerate(L.bics):
565
clutching_info['middle'] = B
566
H = clutch(**clutching_info)
567
# We now apply to delta to find the new BIC.
568
# Note that delta is shifted with regard to the profile numbering.
569
# It also allows us to recover p:
570
assert H.delta(1).is_isomorphic(self.X.bics[p[0]])
571
assert H.delta(3).is_isomorphic(self.X.bics[p[1]])
572
# delta(2) is the one we are interested in:
573
XB = H.delta(2)
574
for b in candidates:
575
if XB.is_isomorphic(self.X.bics[b]):
576
# check if more than one is found
577
if i in mid_to_bic:
578
raise RuntimeError(
579
"BIC %r of %r seems to be isomorphic to several BICs of %r!" %
580
(i, L, self.X))
581
mid_to_bic[i] = b
582
if i not in mid_to_bic:
583
raise RuntimeError(
584
"BIC %r of %r not found in BICs of %r!" %
585
(i, L, self.X))
586
self._middle_to_bic[enh_profile] = mid_to_bic.copy()
587
return self._middle_to_bic[enh_profile]
588
589
def top_to_bic_inv(self, i):
590
"""
591
Inverse of top_to_bic.
592
593
More Precisely: A dictionary assigning indices of X.bics a list of indices of
594
X.top.bics. Note that this can be more than one (if the intersection is not
595
irreducible).
596
597
Note also that not all indices of X.bics are keys (but if not, they should be
598
keys of bot_to_bic_inv)!
599
600
Args:
601
i (int): index of X.bics
602
603
Returns:
604
dict: index of X.bics -> list of indices of X.top.bics.
605
"""
606
if self._top_to_bic_inv[i] is None:
607
self._top_to_bic_inv[i] = {}
608
d = self.top_to_bic(i) # possibly built here (!)
609
for j, im_j in d.items():
610
try:
611
self._top_to_bic_inv[i][im_j].append(j)
612
except KeyError:
613
self._top_to_bic_inv[i][im_j] = [j]
614
return self._top_to_bic_inv[i]
615
616
def bot_to_bic_inv(self, i):
617
"""
618
Inverse of :meth:`bot_to_bic`.
619
620
More Precisely: A dictionary assigning indices of X.bics a list of indices of
621
X.bot.bics. Note that this can be more than one (if the intersection is not
622
irreducible).
623
624
Note also that not all indices of X.bics are keys (but if not, they should be
625
keys of top_to_bic_inv)!
626
627
Args:
628
i (int): index of X.bics
629
630
Returns:
631
dict: index of X.bics -> list of indices of X.bot.bics.
632
"""
633
if self._bot_to_bic_inv[i] is None:
634
self._bot_to_bic_inv[i] = {}
635
d = self.bot_to_bic(i) # possibly built here (!)
636
for j, im_j in d.items():
637
try:
638
self._bot_to_bic_inv[i][im_j].append(j)
639
except KeyError:
640
self._bot_to_bic_inv[i][im_j] = [j]
641
return self._bot_to_bic_inv[i]
642
643
def middle_to_bic_inv(self, enh_profile):
644
"""
645
Inverse of middle_to_bic.
646
647
More Precisely: A dictionary assigning indices of X.bics a list of indices of
648
{middle level of enh_profile}.bics.
649
Note that this can be more than one (if the intersection is not
650
irreducible).
651
652
INPUT:
653
654
enh_profile (tuple): enhanced profile of a 3-level graph.
655
656
OUTPUT:
657
658
index of X.bics -> list of indices of {middle level of enh_profile}.bics.
659
"""
660
if enh_profile not in self._middle_to_bic_inv:
661
self._middle_to_bic_inv[enh_profile] = {}
662
d = self.middle_to_bic(enh_profile) # possibly built here (!)
663
for j, im_j in d.items():
664
try:
665
self._middle_to_bic_inv[enh_profile][im_j].append(j)
666
except KeyError:
667
self._middle_to_bic_inv[enh_profile][im_j] = [j]
668
return self._middle_to_bic_inv[enh_profile]
669
670