unlisted
ubuntu2004# pylint does not know sage1from sage.structure.sage_object import SageObject # pylint: disable=import-error2from sage.misc.flatten import flatten # pylint: disable=import-error34import admcycles.diffstrata.generalisedstratum5import admcycles.diffstrata.levelgraph6import admcycles.diffstrata.embeddedlevelgraph78#######################################################################9#######################################################################10# Point Dictionaries11#######################################################################12# * Points on generalised strata are of the form (i,j) where i is the13# index of the component and j is the index in the signature of14# the component.15# * An EmbeddedLevelGraph is a LevelGraph together with a dictionary16# mapping all non-half-edges (i.e. marked points) to the the points17# of the associated stratum.18# Note that the numbering of points starts with 1 while the indices19# of the signatures start with 0. I.e. in the case of a gen. stratum20# with only one component, the dictionary is i -> (0,i-1).21# * For clutching, we need dictionaries that map points of strata to22# points of other strata. This allows us to embed clutched LevelGraphs23# by composition.24# * When splitting a BIC into top and bottom, we consequently need to25# remember the dictionary that maps marked points of the top and26# bottom gen. stratum into the points of stratum the BIC was27# embedded into.28##29# Thus, a splitting of a BIC inside stratum X must result in two generalised30# strata, top and bottom, as well as three dictionaries:31# * emb_top: points of top -> points of X32# * emb_bot: points of bottom -> points of X33# * clutching: points of top -> points of bottom34# All of these dictionaries are injective, the keys of emp_top and clutching35# give a partition of the points of top, the keys of emb_bot and the values36# of clutching give a partition of the points of bottom and the values of37# emb_top and emb_bot give a partition of the points of X.38#######################################################################39#######################################################################404142def clutch(43X,44top,45bottom,46clutch_dict,47emb_dict_top,48emb_dict_bot,49middle=None,50clutch_dict_lower={},51clutch_dict_long={},52emb_dict_mid={}):53"""54Clutch EmbeddedLevelGraphs top and bottom to get a new EmbeddedLevelGraph.5556If middle is specified, we do a "double clutching" top - middle - bottom to57give a graph in X.5859.. NOTE::6061* this cannot be split into two steps, as the intermediate graph is62not a graph in X!63* We include the possibility to clutch points in the top component *past*64the middle to the bottom component (long edges!)6566This is the inverse procedure to EmbeddedLevelGraph.split() and67GeneralisedStratum.doublesplit().6869Note that if top or bottom is None, we return None.7071INPUT:7273X (GeneralisedStratum): Stratum that the clutched graph will live in7475top (EmbeddedLevelGraph): Top component to be clutched7677bottom (EmbeddedLevelGraph): Bottom component to be clutched7879middle (EmbeddedLevelGraph, optional, defaults to None): Middle component to80be clutched.8182clutch_dict (dict): The dictionary giving the clutching information,83(i.e. the edges that will be inserted).84In the case of a "simple clutch", i.e. top to bottom with no middle,85a dictionary:8687* point on top stratum -> point on bottom stratum88In the case of a middle graph, i.e. a "double clutching":89* point on top stratum -> point on middle stratum.90In this case, we also need clutch_dict_lower for the other clutching91and clutch_dict_long for the long edges.9293clutch_dict_lower (dict, optional): In the case of a "double clutching", the94info for the "lower" clutching, i.e. a dictionary95point on middle stratum -> point on bottom stratum9697clutch_dict_long (dict, optional): In the case of a "double clutching", the98info for the "long edges", i.e. a dictionary99point on top stratum -> point on bottom stratum100101emb_dict_top (dict): dictionary giving the embedding of the resulting102EmbeddedLevelGraph, i.e. points on top stratum -> point on103enveloping stratum.104105emb_dict_bot (dict): dictionary giving the embedding of the resulting106EmbeddedLevelGraph, i.e. points on bottom stratum -> point on107enveloping stratum.108109emb_dict_mid (dict, optional): dictionary giving the embedding of the110resulting EmbeddedLevelGraph, i.e. points on middle stratum -> point on111enveloping stratum.112113OUTPUT:114115EmbeddedLevelGraph: Clutched LevelGraph together with embedding.116117Returns:118tuple: EmbeddedLevelGraph: as above119120EXAMPLES::121122sage: from admcycles.diffstrata import *123sage: X=GeneralisedStratum([Signature((1,1))])124sage: assert clutch(**X.bics[1].split()).is_isomorphic(X.bics[1])125sage: assert all(clutch(**B.split()).is_isomorphic(B) for B in X.bics)126127The same works for 3-level graphs and doublesplit:128129sage: X=GeneralisedStratum([Signature((1,1))])130sage: assert all(X.lookup_graph(*ep).is_isomorphic(clutch(**X.doublesplit(ep))) for ep in X.enhanced_profiles_of_length(2))131sage: X=GeneralisedStratum([Signature((2,2,-2))])132sage: 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)133"""134if top is None or bottom is None:135return None136# Build new EmbeddedLevelGraph:137# This is somewhat similar to levelstratum's unite_embedded_graphs138# but here the result is embedded into a _different_ stratum!139# In case top, middle or bottom are LevelStrata, we replace them by their smooth140# component (this is mainly to allow clutch to eat the output of split...)141if isinstance(top, admcycles.diffstrata.generalisedstratum.LevelStratum):142top = top.smooth_LG143if isinstance(144bottom, admcycles.diffstrata.generalisedstratum.LevelStratum):145bottom = bottom.smooth_LG146if isinstance(147middle, admcycles.diffstrata.generalisedstratum.LevelStratum):148# in particular, middle is not None!149middle = middle.smooth_LG150# genera simply get combined151if middle is None:152newgenera = top.LG.genera + bottom.LG.genera153else:154newgenera = top.LG.genera + middle.LG.genera + bottom.LG.genera155# We renumber the levels consecutively starting at 0.156top_levels = top.LG.numberoflevels()157top_level_dict = {top.LG.internal_level_number(158l): -l for l in range(top_levels)}159newlevels = [top_level_dict[l] for l in top.LG.levels]160if middle is None:161mid_levels = 0162mid_level_dict = {}163else:164# the levels of the middle component have to be shifted:165mid_levels = middle.LG.numberoflevels()166mid_level_dict = {middle.LG.internal_level_number(l): -l - top_levels167for l in range(mid_levels)}168newlevels += [mid_level_dict[l] for l in middle.LG.levels]169bot_levels = bottom.LG.numberoflevels()170# The levels of the bottom component have to be shifted by both top and171# middle:172bot_level_dict = {173bottom.LG.internal_level_number(l): -174l -175top_levels -176mid_levels for l in range(bot_levels)}177newlevels += [bot_level_dict[l] for l in bottom.LG.levels]178# TODO: What is a sensible level dictionary?179# At the moment, we choose the identity.180newdlevels = {-l: -l for l in range(top_levels + mid_levels + bot_levels)}181# The legs have to be renumbered in analogy to unite_embedded_graphs:182newlegs = []183newedges = []184newpoleorders = {}185newdmp = {} # the new embedding is given by the emb_dicts186# For adding the new edges, we have to record the new leg numbers and this will187# happen at different stages (on each component).188# The idea is, for each set of edges, to create a list of the form189# [(starting leg 1, starting leg 2,....), (ending leg 1, ending leg 2,...)]190# and then zip these lists to create the edges.191# The points are currently stored (as points on the respective stratum) in the192# various clutching dictionaries.193# We want to move them to lists of the form described above.194if middle is None:195# If this is a "simple clutch", we only have to add edges between top196# and bottom.197clutch_edges = [] # the new edges to be added198else:199# Otherwise, we have to distinguish between:200# * edges from top to middle:201clutch_edges_tm = []202# * edges from middle to bottom:203clutch_edges_mb = []204# * and edges from top to bottom (going past middle):205clutch_edges_tb = []206leg_number_shift = 0 # the shift for renumbering legs207legs = 0 # leg counter208# We now go through the components, starting with the top:209#210# Note that we also have to choose the appropriate embedding dictionary.211# We will also have to record the clutching information, as the points are all212# going to be renamed, so we have to remember who will be clutched.213#214if middle is None:215# in the "simple clutch" case, it makes sense to also pass216# the clutch points to each component; these are stored in clutch_dict217comp_data = ((top, emb_dict_top, clutch_dict.keys()),218(bottom, emb_dict_bot, clutch_dict.values()))219else:220# We have to sort out the clutching in the loop anyway, so we just pass221# a dummy.222comp_data = ((top, emb_dict_top, {}),223(middle, emb_dict_mid, {}),224(bottom, emb_dict_bot, {}))225# emb_g: graph to be clutched226# emb_dict: dictionary227# marked points of the enveloping stratum of emb_g -> points of X228for emb_g, emb_dict, clutch_points in comp_data:229leg_dict = {} # old number -> new number230for i, l in enumerate(flatten(emb_g.LG.legs)):231# the point numbers on bottom component are shifted by the top232# number233newlegnumber = leg_number_shift + i + 1234leg_dict[l] = newlegnumber235# while we're at it, we add the pole orders:236newpoleorders[newlegnumber] = emb_g.LG.poleorders[l]237legs += 1238# For the dictionary of marked points (for the embedding), we239# must distinguish if this is a marked point or a half-edge.240# Marked points are simply the ones for which we have a key241# in emb_dict :-)242# The newdmp must then map this point to the image of its dmp in243# the new stratum, i.e. the image under emb_dict:244# Note that emb_dict eats stratum points, i.e. the image of the245# leg under dmp.246try:247newdmp[newlegnumber] = emb_dict[emb_g.dmp[l]]248except KeyError:249pass250leg_number_shift += legs251# build (nested) list of legs:252newlegs += [[leg_dict[l] for l in comp] for comp in emb_g.LG.legs]253# finally, the edges are renumbered accordingly:254newedges += [(leg_dict[e[0]], leg_dict[e[1]]) for e in emb_g.LG.edges]255# We now record the clutching information:256# Note that the points to be clutched are points on the Stratum!257#258# Because everything was renamed with leg_dict, we need to *now* remember259# the edges to clutch (see discussion above).260#261# For this, we now have to distinguish if we're working with262# two or three components:263# * In the two component case, the points to be clutched are264# the keys (top) and values (bottom) of clutch_dict.265# Said differently: clutch_dict encodes the edges that will be added.266if middle is None:267# We save "half" of the clutch points in each step, afterwards we just268# have to combine these lists to obtain all new edges.269clutch_edges.append(270tuple(leg_dict[emb_g.dmp_inv[p]] for p in clutch_points))271# * In the three component case,272# * the *keys* of clutch_dict are points of the *top* component273# * the *values* clutch_dict are those points on the *middle* component that274# are clutched to the *top* component275# * the *keys* of clutch_dict_lower are points of the *middle* component276# * the *values* of clutch_dict_lower are points of the *bottom* component that277# are clutched to the *middle* component278# * the *keys* of clutch_dict_long are points of the *top* component279# * the *values* of clutch_dict_long are points of the *bottom* component that280# are clutched to the *top* component (long edges going past the middle)281# Said differently:282# * clutch_dict encodes edges top - middle283# * clutch_dict_lower encodes edges middle - bottom284# * clutch_dict_long encodes edges top - bottom (long edges going past middle)285else:286# We save the tuple of legs on the current component:287if emb_g is top:288clutch_edges_tm.append(tuple(289leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict.keys()290))291clutch_edges_tb.append(tuple(292leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_long.keys()293))294elif emb_g is middle:295clutch_edges_tm.append(tuple(296leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict.values()297))298clutch_edges_mb.append(tuple(299leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_lower.keys()300))301else:302assert emb_g is bottom303clutch_edges_tb.append(tuple(304leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_long.values()305))306clutch_edges_mb.append(tuple(307leg_dict[emb_g.dmp_inv[p]] for p in clutch_dict_lower.values()308))309# Now the actual clutching happens, i.e. adding in the extra edges between310# the points of clutch_dict:311if middle is None:312assert len(clutch_edges) == 2313newedges += list(zip(*clutch_edges))314else:315assert len(clutch_edges_tm) == 2316assert len(clutch_edges_tb) == 2317assert len(clutch_edges_mb) == 2318newedges += list(zip(*clutch_edges_tm)) + \319list(zip(*clutch_edges_tb)) + \320list(zip(*clutch_edges_mb))321322LG = admcycles.diffstrata.levelgraph.LevelGraph(323newgenera, newlegs, newedges, newpoleorders, newlevels324)325326ELG = admcycles.diffstrata.embeddedlevelgraph.EmbeddedLevelGraph(327X, LG, newdmp, newdlevels)328329if not ELG.is_legal():330# This should not happen if R-GRC is checked!331# print("Illegal clutch attempted: %r, %r" % (top,bottom))332raise RuntimeError("Illegal clutch, this should not happen anymore!")333334return ELG335336337class GenDegenerationGraph(SageObject):338"""339The degeneration graph of the Generalised Stratum X.340341A degeneration graph of a (generalised) stratum is built recursively.342For that we need to know:343344* The BICs inside X345* For each BIC its top and bottom component (generalised strata)346* For each BIC the clutching map of these components347* For each top and bottom component of each BIC an embedding of their348respective BICs into BIC(X)349350This allows us to translate products of BICs on X into clutchings of351products of BICs on top and bottom components and gives a recursive352procedure to compute the LevelGraph(s) for any product of BICs in X.353354INPUT:355356X (GeneralisedStratum): The GeneralisedStratum we give the degeneration357graph of.358"""359def __init__(self, X):360self.X = X361self.n = len(self.X.bics) # possibly generates all bics of X (!)362self._top_to_bic = [None for _ in range(363self.n)] # initialise on demand364self._bot_to_bic = [None for _ in range(365self.n)] # initialise on demand366self._middle_to_bic = {} # initialise on demand367self._top_to_bic_inv = [None for _ in range(368self.n)] # initialise on demand369self._bot_to_bic_inv = [None for _ in range(370self.n)] # initialise on demand371self._middle_to_bic_inv = {} # initialise on demand372# Note that while top_to_bic and bot_to_bic aren't injective, their373# images are disjoint, i.e. we can remove the guys we found for top374# from the ones we're searching through for bot (and vice versa).375# This happens for every bic, though.376self._unused_bics_top = [377list(range(self.n)) for _ in range(self.n)]378self._unused_bics_bot = [379list(range(self.n)) for _ in range(self.n)]380# To build all LevelGraphs inside a (generalised) stratum, we store all381# tuples of indices of bics that give a (non-empty!) 3-level graph:382# Note that the multiplicities tell us something about automorphisms383# so we keep track of them and the set in parallel (use384# _append_triple_prod() !!)385self._triple_prods = []386self._triple_prod_set = set()387self._triple_prod_set_list = None388389def __repr__(self):390return "GenDegenerationGraph(X=%r)" % self.X391392def _append_triple_prod(self, t):393# append the tuple t to _triple_prods (both the list and the set...)394self._triple_prods.append(t)395self._triple_prod_set.add(t)396397@property398def triple_prods(self):399"""400A list of profiles of the 3-level graphs in X.401402Note that this is generated on first call.403404Returns:405list: list of tuples of length two of indices of X.bics.406"""407if self._triple_prod_set_list is None:408# the easiest way to initialise is to initialise all bic maps:409for i in range(self.n):410self.top_to_bic(i)411self.bot_to_bic(i)412self._triple_prod_set_list = list(self._triple_prod_set)413return self._triple_prod_set_list414415def top_to_bic(self, i):416"""417A dictionary indices of BICs of X.bic[i].top -> indices of X.bics.418419Note that this is created once and then reused.420421Args:422i (int): Index of X.bics423424Raises:425RuntimeError: Raised if the squish of the clutching of a degeneration of426the top component with X.bic[i].bot is not found in X.bics.427428Returns:429dict: Dictionary mapping BICs of X.bic[i].top to indices of X.bics.430"""431if self._top_to_bic[i] is None:432B = self.X.bics[i]433top_to_bic = {} # index of B.top.bics -> index of X.bics434for j, Bt in enumerate(B.top.bics):435# Note that Bt is embedded (as a Graph) into B.top436# B.top is embedded (as a stratum) into B (via B.emb_top)437G = clutch(self.X, Bt, B.bot, B.clutch_dict,438B.emb_top, B.emb_bot)439if G is None:440# illegal clutch:441raise RuntimeError(442"Illegal clutch, this should not happen anymore!")443# Because the degeneration is clutched on top, squishing the444# bottom level gives a new divisor (i.e. delta(1))445squished_bic = G.delta(1)446for im_j in self._unused_bics_top[i]:447if im_j == i:448continue # B^2 is empty449if squished_bic.is_isomorphic(self.X.bics[im_j]):450top_to_bic[j] = im_j451# Record profile of the 3-level graph:452self._append_triple_prod((im_j, i))453if self._bot_to_bic[i] is None:454# otherwise bot_to_bic has already been constructed and455# this is futile...456try:457self._unused_bics_bot[i].remove(im_j)458except ValueError:459# this is a case where the map is non-injective460pass461break462else: # no break463raise RuntimeError(464"delta(1) of %s, %s, not found in list of BICs of %s" %465(G, squished_bic, self.X))466self._top_to_bic[i] = top_to_bic.copy()467return self._top_to_bic[i]468469def bot_to_bic(self, i):470"""471A dictionary indices of BICs of X.bic[i].bot -> indices of X.bics.472473Note that this is created once and then reused.474475Args:476i (int): Index of X.bics477478Raises:479RuntimeError: Raised if the squish of the clutching of a degeneration of480the bottom component with X.bic[i].top is not found in X.bics.481482Returns:483dict: Dictionary mapping BICs of X.bic[i].bot to indices of X.bics.484"""485if self._bot_to_bic[i] is None:486B = self.X.bics[i]487bot_to_bic = {} # index of B.top.bics -> index of X.bics488for j, Bb in enumerate(B.bot.bics):489# Note that Bb is embedded (as a Graph) into B.bot490# B.bot is embedded (as a stratum) into B (via B.emb_bot)491G = clutch(self.X, B.top, Bb, B.clutch_dict,492B.emb_top, B.emb_bot)493if G is None:494# illegal clutch:495raise RuntimeError(496"Illegal clutch, this should not happen anymore!")497# Because the degeneration is clutched on bottom, squishing the498# top level gives a new divisor (i.e. delta(2))499squished_bic = G.delta(2)500for im_j in self._unused_bics_bot[i]:501if im_j == i:502continue # B^2 is empty503if squished_bic.is_isomorphic(self.X.bics[im_j]):504bot_to_bic[j] = im_j505# Record profile of the 3-level graph:506self._append_triple_prod((i, im_j))507if self._top_to_bic[i] is None:508# otherwise top_to_bic has already been constructed and509# this is futile...510try:511self._unused_bics_top[i].remove(im_j)512except ValueError:513# this is a case where the map is non-injective514pass515break516else: # no break517raise RuntimeError(518"delta(2) of %s, %s, not found in list of BICs of %s" %519(G, squished_bic, self.X))520self._bot_to_bic[i] = bot_to_bic.copy()521return self._bot_to_bic[i]522523def middle_to_bic(self, enh_profile):524"""525A dictionary for each 3-level graph mapping (indices of) BICs of the middle level526to (indices of) BICs of the enveloping stratum.527528Raises a RuntimeError if529530* either enh_profile is not a 3-level graph531* or if a degeneration of the middle level is isomorphic532(after squishing) to more than one BIC of X.533* or if no isomorphic BIC of X is found.534535INPUT:536537enh_profile (tuple): Enhanced profile of a 3-level graph.538539OUTPUT:540541dictionary: indices of bics of the middle level -> indices of X.bics542"""543if enh_profile not in self._middle_to_bic:544p, i = enh_profile545if len(p) != 2:546raise RuntimeError(547"Only 3-level graphs have a well-defined middle! %r" %548(enh_profile,))549# The dictionary we want to create:550mid_to_bic = {}551# The easiest thing is to split the graph and reuse all the clutching552# info this yields, replacing the middle level by one of its BICs:553clutching_info = self.X.doublesplit(enh_profile)554L = clutching_info['middle']555# candidates are those BICs that appear as bottom degenerations556# of the top level *and* as top degenerations of the bottom level:557candidates = set()558top_deg = set(self.top_to_bic(p[1]).values())559for b in self.bot_to_bic(p[0]).values():560if b in top_deg:561candidates.add(b)562# We now clutch in degenerations of the middle level:563for i, B in enumerate(L.bics):564clutching_info['middle'] = B565H = clutch(**clutching_info)566# We now apply to delta to find the new BIC.567# Note that delta is shifted with regard to the profile numbering.568# It also allows us to recover p:569assert H.delta(1).is_isomorphic(self.X.bics[p[0]])570assert H.delta(3).is_isomorphic(self.X.bics[p[1]])571# delta(2) is the one we are interested in:572XB = H.delta(2)573for b in candidates:574if XB.is_isomorphic(self.X.bics[b]):575# check if more than one is found576if i in mid_to_bic:577raise RuntimeError(578"BIC %r of %r seems to be isomorphic to several BICs of %r!" %579(i, L, self.X))580mid_to_bic[i] = b581if i not in mid_to_bic:582raise RuntimeError(583"BIC %r of %r not found in BICs of %r!" %584(i, L, self.X))585self._middle_to_bic[enh_profile] = mid_to_bic.copy()586return self._middle_to_bic[enh_profile]587588def top_to_bic_inv(self, i):589"""590Inverse of top_to_bic.591592More Precisely: A dictionary assigning indices of X.bics a list of indices of593X.top.bics. Note that this can be more than one (if the intersection is not594irreducible).595596Note also that not all indices of X.bics are keys (but if not, they should be597keys of bot_to_bic_inv)!598599Args:600i (int): index of X.bics601602Returns:603dict: index of X.bics -> list of indices of X.top.bics.604"""605if self._top_to_bic_inv[i] is None:606self._top_to_bic_inv[i] = {}607d = self.top_to_bic(i) # possibly built here (!)608for j, im_j in d.items():609try:610self._top_to_bic_inv[i][im_j].append(j)611except KeyError:612self._top_to_bic_inv[i][im_j] = [j]613return self._top_to_bic_inv[i]614615def bot_to_bic_inv(self, i):616"""617Inverse of :meth:`bot_to_bic`.618619More Precisely: A dictionary assigning indices of X.bics a list of indices of620X.bot.bics. Note that this can be more than one (if the intersection is not621irreducible).622623Note also that not all indices of X.bics are keys (but if not, they should be624keys of top_to_bic_inv)!625626Args:627i (int): index of X.bics628629Returns:630dict: index of X.bics -> list of indices of X.bot.bics.631"""632if self._bot_to_bic_inv[i] is None:633self._bot_to_bic_inv[i] = {}634d = self.bot_to_bic(i) # possibly built here (!)635for j, im_j in d.items():636try:637self._bot_to_bic_inv[i][im_j].append(j)638except KeyError:639self._bot_to_bic_inv[i][im_j] = [j]640return self._bot_to_bic_inv[i]641642def middle_to_bic_inv(self, enh_profile):643"""644Inverse of middle_to_bic.645646More Precisely: A dictionary assigning indices of X.bics a list of indices of647{middle level of enh_profile}.bics.648Note that this can be more than one (if the intersection is not649irreducible).650651INPUT:652653enh_profile (tuple): enhanced profile of a 3-level graph.654655OUTPUT:656657index of X.bics -> list of indices of {middle level of enh_profile}.bics.658"""659if enh_profile not in self._middle_to_bic_inv:660self._middle_to_bic_inv[enh_profile] = {}661d = self.middle_to_bic(enh_profile) # possibly built here (!)662for j, im_j in d.items():663try:664self._middle_to_bic_inv[enh_profile][im_j].append(j)665except KeyError:666self._middle_to_bic_inv[enh_profile][im_j] = [j]667return self._middle_to_bic_inv[enh_profile]668669670