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