unlisted
ubuntu2004from copy import deepcopy12# pylint does not know sage3from sage.modules.free_module import FreeModule # pylint: disable=import-error4from sage.rings.integer_ring import ZZ # pylint: disable=import-error5from sage.arith.functions import lcm # pylint: disable=import-error6from sage.misc.flatten import flatten # pylint: disable=import-error7from sage.misc.cachefunc import cached_method # pylint: disable=import-error89import admcycles.diffstrata.generalisedstratum10from admcycles.diffstrata.sig import Signature11from admcycles.diffstrata.klevelgraph import KLevelGraph121314def smooth_LG(sig):15"""16The smooth (i.e. one vertex) LevelGraph in the stratum (sig).1718INPUT:1920sig (Signature): signature of the stratum.2122OUTPUT:2324LevelGraph: The smooth graph in the stratum.25"""26if sig.k != 1:27raise ValueError("The signature is not for an abelian differential")28return LevelGraph.fromPOlist(29[sig.g], [list(range(1, sig.n + 1))], [], sig.sig, [0])303132class LevelGraph(KLevelGraph):33r"""34Create a (stable) level graph.3536..NOTE::3738This is a low-level class and should NEVER be invoced directly!39Preferably, EmbeddedLevelGraphs should be used and these should be40generated automatically by Stratum (or GeneralisedStratum).4142.. NOTE::4344We don't inherit from stgraph/StableGraph anymore, as LevelGraphs45should be static objects!4647Extends admcycles stgraph to represent a level graph as a stgraph,48i.e. a list of genera, a list of legs and a list of edges, plus a list of49poleorders for each leg and a list of levels for each vertex.5051Note that the class will warn if the data is not admissible, i.e. if the52graph is not stable or the pole orders at separating nodes across levels do53not add up to -2 or -1 on the same level (unless this is suppressed with54quiet=True).5556INPUT:5758genera : list59List of genera of the vertices of length m.6061legs : list62List of length m, where ith entry is list of legs attached to vertex i.63By convention, legs are unique positive integers.6465edges : list66List of edges of the graph. Each edge is a 2-tuple of legs.6768poleorders : dictionary69Dictionary of the form leg number : poleorder7071levels : list72List of length m, where the ith entry corresponds to the level of73vertex i.74By convention, top level is 0 and levels go down by 1, in particular75they are NEGATIVE numbers!7677quiet : optional boolean (default = False)78Suppresses most error messages.7980ALTERNATIVELY, the pole orders can be supplied as a list by calling81LevelGraph.fromPOlist:8283poleorders : list84List of order of the zero (+) or pole (-) at each leg. The ith element85of the list corresponds to the order at the leg with the marking i+186(because lists start at 0 and the legs are positive integers).8788EXAMPLES:8990Creating a level graph with three components on different levels of genus 1,913 and 0. The bottom level has a horizontal node.::9293sage: from admcycles.diffstrata import *94sage: G = LevelGraph.fromPOlist([1,3,0],[[1,2],[3,4,5],[6,7,8,9]],[(2,3),(5,6),(7,8)],[2,-2,0,6,-2,0,-1,-1,0],[-2,-1,0]); G # doctest: +SKIP95LevelGraph([1, 3, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9]],[(3, 2), (6, 5), (7, 8)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0},[-2, -1, 0],True)9697or alternatively::9899sage: LevelGraph([1, 3, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9]],[(3, 2), (6, 5), (7, 8)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0},[-2, -1, 0],quiet=True)100LevelGraph([1, 3, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9]],[(3, 2), (6, 5), (7, 8)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0},[-2, -1, 0],True)101102We get a warning if the graph has non-stable components: (not any more ;-))::103104sage: G = LevelGraph.fromPOlist([1,3,0,0],[[1,2],[3,4,5],[6,7,8,9],[10]],[(2,3),(5,6),(7,8),(9,10)],[2,-2,0,6,-2,0,-1,-1,0,-2],[-3,-2,0,-1]); G # doctest: +SKIP105Warning: Component 3 is not stable: g = 0 but only 1 leg(s)!106Warning: Graph not stable!107LevelGraph([1, 3, 0, 0],[[1, 2], [3, 4, 5], [6, 7, 8, 9], [10]],[(3, 2), (6, 5), (7, 8), (9, 10)],{1: 2, 2: -2, 3: 0, 4: 6, 5: -2, 6: 0, 7: -1, 8: -1, 9: 0, 10: -2},[-3, -2, 0, -1],True)108"""109def __init__(self, genera, legs, edges, poleorders, levels, quiet=False):110super().__init__(111genera, legs, edges, poleorders, levels, 1, quiet)112# the prong orders are stored in a dictionary edge : prong order113self.prongs = self._gen_prongs()114115@classmethod116def fromPOlist(cls, genera, legs, edges,117poleordersaslist, levels, quiet=False):118"""119This gives a LevelGraph where the poleorders are given as a list, not120directly as a dictionary.121"""122# translate poleorder list to dictionary:123sortedlegs = sorted(flatten(legs))124if len(sortedlegs) != len(poleordersaslist):125raise ValueError("Numbers of legs and pole orders do not agree!" +126" Legs: " + repr(len(sortedlegs)) +127" Pole orders: " + repr(len(poleordersaslist)))128poleorderdict = {sortedlegs[i]: poleordersaslist[i]129for i in range(len(sortedlegs))}130return cls(genera, legs, edges, poleorderdict, levels, quiet)131132@classmethod133def fromKLevelGraph(cls, kLG):134assert kLG.sig.k == 1135return cls(kLG.genera, kLG.legs, kLG.edges,136kLG.poleorders, kLG.levels, quiet=True)137138def __repr__(self):139return "LevelGraph(" + self.input_as_string() + ")"140141def __hash__(self):142return hash(repr(self))143144def __str__(self):145return ("LevelGraph " + repr(self.genera) + ' ' + repr(self.legs) + ' '146+ repr(self.edges) + ' ' + repr(self.poleorders) + ' '147+ repr(self.levels))148149def input_as_string(self):150"""151return a string that can be given as argument to __init__ to give self152"""153return (repr(self.genera) + ',' + repr(self.legs) + ','154+ repr(self.edges) + ',' + repr(self.poleorders) + ','155+ repr(self.levels) + ',True')156157##########################################################158# Interface:159# --- provide a series of methods that translate various data160# (maybe this can be optimised by smart caching later, so only161# these should be used...)162# up to now, we have:163# * prongs_list :164# returns : list of tuples (edge,prong)165# * prong :166# INPUT : edge167# returns : prong order at edge168# * _pointtype :169# INPUT : order (integer)170# returns : pole/marked point/zero (string)171# * is_meromorphic :172# returns : boolean (check for pole among marked points)173# * highestorderpole :174# INPUT : vertex175# returns : leg name (at v) or -1 if none found176#########################################################177@cached_method178def prongs_list(self):179return list(self.prongs.items())180181@cached_method182def prong(self, e):183"""184The prong order is the pole order of the higher-level pole +1185"""186if self.levelofleg(e[0]) > self.levelofleg(e[1]):187return self.orderatleg(e[0]) + 1188else:189return self.orderatleg(e[1]) + 1190191@cached_method192def _pointtype(self, order):193if order < 0:194return "pole"195elif order == 0:196return "marked point"197else:198return "zero"199200@cached_method201def is_meromorphic(self):202"""203Returns True iff at least one of the MARKED POINTS is a pole.204"""205return any(self.orderatleg(l) < 0 for l in self.list_markings())206207@cached_method208def highestorderpole(self, v):209"""210Returns the leg with the highest order free pole at v, -1 if no poles found.211"""212minorder = 0213leg = -1214for l in self.list_markings(v):215if self.orderatleg(l) < minorder:216minorder = self.orderatleg(l)217leg = l218return leg219#########################################################220# end interface221#########################################################222223#################################################################224# Cleanup functions225# USE ONLY IN __init__!!! KLevelGraphs should be static!!!226#################################################################227def _gen_prongs(self):228"""229Generate the dictionary edge : prong order.230The prong order is the pole order of the higher-level pole +1231"""232return {e: self.prong(e) for e in self.edges}233234#################################################################235# Extract a subgraph236#################################################################237238def extract(self, vertices, edges):239"""240Extract the subgraph of self (as a LevelGraph) consisting of vertices241(as list of indices) and edges (as list of edges).242243Returns the levelgraph consisting of vertices, edges and all legs on244vertices (of self) with their original poleorders and the original245level structure.246"""247return self.fromKLevelGraph(248super().extract(vertices, edges))249250def levelGraph_from_subgraph(self, G):251"""252Returns the LevelGraph associated to a subgraph of underlying_graph253(with the level structure induced by self)254"""255return self.fromKLevelGraph(256super().levelGraph_from_subgraph(G))257258def stratum_from_level(self, l, excluded_poles=None):259"""260Return the LevelStratum at (relative) level l.261262INPUT:263264l (int): relative level number (0,...,codim)265266excluded_poles (tuple, defaults to None): a list of poles (legs of the graph,267that are marked points of the stratum!) to be ignored for r-GRC.268269OUTPUT:270271LevelStratum: the LevelStratum, i.e.272273* a list of Signatures (one for each vertex on the level)274* a list of residue conditions, i.e. a list [res_1,...,res_r]275where each res_k is a list of tuples [(i_1,j_1),...,(i_n,j_n)]276where each tuple (i,j) refers to the point j (i.e. index) on the277component i and such that the residues at these points add up278to 0.279* a dictionary of legs, i.e. n -> (i,j) where n is the original280number of the point (on the LevelGraph self) and i is the281number of the component, j the index of the point in the signature tuple.282283Note that LevelStratum is a GeneralisedStratum together with284a leg dictionary.285"""286if self.is_horizontal():287print("Error: Cannot extract levels of a graph with horizontal edges!")288return None289internal_l = self.internal_level_number(l)290# first, we extract the obvious information from self at this level:291vv = self.verticesonlevel(internal_l)292legs_on_level = []293sigs = []294res_cond = []295leg_dict = {}296for i, v in enumerate(vv):297legs_on_level += [deepcopy(self.legsatvertex(v))]298leg_dict.update([(n, (i, j))299for j, n in enumerate(legs_on_level[-1])])300sig_v = Signature(tuple(self.orderatleg(leg)301for leg in legs_on_level[-1]))302sigs += [sig_v]303# Now we hunt for residue conditions.304# We work with the underlying graph to include the "level at infinity":305# We "cheat" here, to catch the edges going up from level l: These are the306# edges of the graph above level l-1 that have a vertex on level l:307# (see below for parsing of these edges)308ee = []309for e in self.UG_above_level(internal_l - 1).edges(sort=True):310for UG_vertex in e[:2]:311if UG_vertex[2] == 'res':312continue313if self.levelofvertex(UG_vertex[0]) == internal_l:314ee.append(e)315break316G = self.UG_above_level(internal_l)317conn_comp = G.connected_components_subgraphs()318# Residue conditions are obtained by sorting the poles on this level319# by connected component of G they are attached to (note that this320# includes the "level at infinity"!)321for c in conn_comp:322# if there is a free pole in c, we ignore this component323if self._redeemed_by_merom_in_comp(324c, excluded_poles=excluded_poles):325continue326res_cond_c = []327# Otherwise we record all poles to attached to this component in one328# residue condition:329for e in ee:330# We work with edges of the underlying graph here, so we have to331# be a bit more careful. Recall that edges of the underlying graph are of332# the form:333# * (UG_vertex, UG_vertex, edge name) if it's an edge of LG or334# * (UG_vertex, UG_vertex, resiedgej) if it's an edge to level infinity335# Here UG_vertex is either of the form (vertex number, genus, 'LG') or336# (i, 0, 'res') where337# i is the number of the vertex at infinity and resiedgej is the edge338# connecting the residue i to the leg j (of self).339if e[0] in c or e[1] in c:340# res_cond contains tuples (i,j) where i is the index of the341# component and j the index of the pole in sig_i.342#343# We need find the vertex on this level (i.e. not in c)344if e[0] in c:345UG_vertex = e[1]346else:347UG_vertex = e[0]348# to find the pole on v, we have to distinguish if this is an349# edge of self or if it connects to a residue at infinity:350edge = e[2]351if 'res' in edge:352# the leg is the number after 'edge' in the edge353# string:354_, leg = edge.split('edge')355leg = int(leg)356else:357# edge consists of (leg_top,leg_bot) and leg_bot is on358# level i:359leg = edge[1]360# We retrieve the stratum point from leg_dict:361res_cond_c += [leg_dict[leg]]362if res_cond_c:363res_cond += [res_cond_c]364return admcycles.diffstrata.generalisedstratum.LevelStratum(365sigs, res_cond, leg_dict)366367#################################################################368# Check Residue condition (via inconvenient vertices/edges)369#################################################################370371@cached_method372def is_inconvenient_vertex(self, v):373"""374Check if vertex is inconvenient, i.e.375* g = 0376* no simple poles377* there exists a pole order m_i such that m_i > sum(m_j)-p-1378Return boolean379"""380# inconvenient vertices are of genus 0 and have no simple poles.381if self.genus(v) > 0 or len(self.simplepolesatvertex(v)) > 0:382return False383# check inequality384ll = self.legsatvertex(v)385poles = [l for l in ll if self.orderatleg(l) < 0]386polesum = sum([-self.orderatleg(l) for l in poles])387p = len(poles)388for l in ll:389if self.orderatleg(l) > polesum - p - 1:390return True391return False392393def _redeemed_by_merom_in_comp(self, G, excluded_poles=None):394"""395Check if there is a pole in the subgraph G (intended to be a connected396component above a vertex).397398excluded_poles are ignored (for r-GRC).399400Returns boolean (True = exists a pole)401"""402if excluded_poles is None:403excluded_poles = []404for w in G.vertices(sort=False):405if w[2] == 'res': # vertex at infinity406continue407for l in self.list_markings(w[0]):408if (self.orderatleg(l) < 0) and (l not in excluded_poles):409return True410return False411412@cached_method413def is_illegal_vertex(self, v, excluded_poles=None):414"""415Check if vertex is inconvenient and is not redeemed, i.e.416* v is inconvenient417* there are no two separate edges to the same connected component above (i.e. loop above)418* if meromorphic: v is not connected to higher level marked poles419420We can also pass a tuple (hashing!) of poles that are to be excluded because of421residue conditions.422423Return boolean424"""425if not self.is_inconvenient_vertex(v):426return False427l = self.levelofvertex(v)428# in the underlying_graph, vertices are of the form (index in genera,429# genus, 'LG'):430v_graph = self.UG_vertex(v)431# edges not going down from v:432ee = [e for e in self.edgesatvertex(v) if self.levelofleg(e[0]) >= l433and self.levelofleg(e[1]) >= l]434# in the holomorphic case, legal inconvenient vertices need at least435# two edges not going down436if len(ee) < 1 and not self.is_meromorphic():437return True438# check if v has two edges into one connected component that don't lie below v,439# i.e. if v can be redeemed:440abovegraph = self.UG_above_level(l - 1)441cc = self.underlying_graph.subgraph([w for w in abovegraph.connected_component_containing_vertex(442v_graph) if w[2] == 'res' or self.levelofvertex(w[0]) >= l])443cc.delete_vertex(v_graph)444conn_comp = cc.connected_components_subgraphs()445if excluded_poles is None:446excluded_poles = []447freepoles = len([l for l in self.list_markings(v) if (448self.orderatleg(l) < 0) and (l not in excluded_poles)])449for G in conn_comp:450# edges from v to G:451# (Careful: cc does not have any edges with v anymore, so we must use abovegraph!)452eeG = [e for e in abovegraph.edges(sort=True)453if (e[0] == v_graph and e[1] in G.vertices(sort=False))454or (e[1] == v_graph and e[0] in G.vertices(sort=False))]455if len(eeG) > 1:456# redeemed :-)457return False458# in the meromorphic case, we also check if a "free" pole exists in459# a connected component above v460if self.is_meromorphic() and self._redeemed_by_merom_in_comp(G,461excluded_poles=excluded_poles):462freepoles += 1463if freepoles >= 2:464# redeemed :-)465return False466return True467468@cached_method469def is_illegal_edge(self, e, excluded_poles=None):470"""471Check if edge is illegal, i.e. if472* e is horizontal (not loop) and473* there no simple loop over e474* there is no "free" pole over each end point475476excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.477478Return boolean479"""480if not self.is_horizontal(e) or (e[0] == e[1]):481return False482# check if there is a simple loop above e, i.e. if the subgraph483# above e is still connected after e is removed (e is not a bridge)484l = self.levelofleg(e[0])485# note that verticesabove checks for >l486abovegraph = self.UG_above_level(l - 1)487# edges are encoded by (self.vertex(e[0]),self.vertex(e[1]),e) in488# underlying_graph!489cut = abovegraph.is_cut_edge(490(self.UG_vertex(491self.vertex(492e[0])), self.UG_vertex(493self.vertex(494e[1])), e))495# True = e is a cut-edge, i.e. connected components increase when removing e, i.e. no loop above,496# i.e. illegal unless also meromorphic497# False = graph has a loop, i.e. not illegal498if not cut:499return False500else:501if self.is_meromorphic():502# if there are "free" poles above the endpoints, we are still503# fine504abovegraph.delete_edge(505(self.UG_vertex(506self.vertex(507e[0])), self.UG_vertex(508self.vertex(509e[1])), e))510polesfound = 0511for l in e:512v = self.vertex(l)513G = abovegraph.connected_component_containing_vertex(514self.UG_vertex(v))515if self._redeemed_by_merom_in_comp(516abovegraph.subgraph(G), excluded_poles=excluded_poles):517polesfound += 1518if polesfound == 2:519# we are fine520return False521# no redemption :/522return True523524@cached_method525def is_legal(self, quiet=False, excluded_poles=None):526"""527Check if self has illegal vertices or edges.528529excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.530531Return boolean532"""533for v in range(len(self.genera)):534if self.is_illegal_vertex(v, excluded_poles=excluded_poles):535if not quiet:536print("Vertex", v, "is illegal!")537return False538for e in self.edges:539if self.is_illegal_edge(e, excluded_poles=excluded_poles):540if not quiet:541print("Edge", e, "is illegal!")542return False543return True544545#################################################################546# Squishing ---- i.e. contracting horizontal edges / levels547#################################################################548549def squish_horizontal(self, e):550"""551Squish the horizontal edge e and return the new graph.552553EXAMPLES::554555sage: from admcycles.diffstrata import *556sage: G = LevelGraph.fromPOlist([1,1], [[1,2],[3,4]], [(2,3)], [1,-1,-1,1],[0,0])557sage: G.squish_horizontal((2,3))558LevelGraph([2],[[1, 4]],[],{1: 1, 4: 1},[0],True)559"""560return LevelGraph(*self._squish_horizontal(e), quiet=True)561562def squish_vertical_slow(self, l, adddata, quiet=False):563"""564Squish the level l (and the next lower level!) and return the new graph.565If addata=True is specified, we additionally return a boolean that tells566us if a level was squished and the legs that no longer exist.567568More precisely, adddata=True returns a tuple (G,boolean,legs) where569* G is the (possibly non-contracted) graph and570* boolean tells us if a level was squished571* legs is the (possibly empty) list of legs that don't exist anymore572573Implementation:574At the moment, we remember all the edges (ee) that connect level l to575level l-1. We then create a new LevelGraph with the same data as self,576the only difference being that all vertices of level l-1 have now been577assigned level l. We then remove all (now horizontal!) edges in ee.578579In particular, all points and edges retain their numbering (only the level might have changed).580581WARNING: Level l-1 does not exist anymore afterwards!!!582583Downside: At the moment we get a warning for each edge in ee, as these584don't have legal pole orders for being horizontal (so checkedgeorders585complains each time the constructor is called :/).586"""587return self.fromKLevelGraph(588super().squish_vertical_slow(l, adddata, quiet))589590def squish_vertical(self, l, quiet=True):591"""592Squish the level l (and the next lower level!) and return the new graph.593594WARNING: Level l-1 does not exist anymore afterwards!!!595596Args:597l (int): (internal) level number598quiet (bool, optional): No output. Defaults to True.599600Returns:601LevelGraph: new levelgraph with one level less.602603EXAMPLES::604605sage: from admcycles.diffstrata import *606sage: G = LevelGraph.fromPOlist([1,2], [[1],[2,3,4]], [(1,2)], [0,-2,1,3],[0,-1])607sage: G.squish_vertical(0)608LevelGraph([3],[[3, 4]],[],{3: 1, 4: 3},[0],True)609"""610return LevelGraph(*self._squish_vertical(l, quiet=quiet), quiet=True)611612def delta(self, k, quiet=False):613"""614Squish all levels except for the k-th.615616Note that delta(1) contracts everything except top-level and that617the argument is interpreted via internal_level_number618619WARNING: Currently giving an out of range level (e.g.6200 or >= maxlevel) squishes the entire graph!!!621622Return the corresponding divisor.623"""624return self.fromKLevelGraph(super().delta(k, quiet))625626#################################################################627# end Squishing628#################################################################629630def relabel(self, legdict, tidyup=True):631new_legs = []632for vlegs in self.legs:633list1 = [legdict[i] for i in vlegs]634if tidyup:635list1.sort()636new_legs.append(list1)637new_edges = [(legdict[e[0]], legdict[e[1]]) for e in self.edges]638new_poleorders = {legdict[i]: j for i, j in self.poleorders.items()}639if tidyup:640new_edges.sort()641new_poleorders = {a: b for a, b in sorted(new_poleorders.items())}642newLG = LevelGraph(self.genera, new_legs, new_edges,643new_poleorders, self.levels)644return newLG645646#################################################################647# Twist groups648#################################################################649650def twist_group(self, quiet=True, with_degree=False):651"""652This should not be needed! The stack factor should be computed653using AdditiveGenerator.stack_factor!!!654655Calculate the index of the simple twist group inside the twist group.656657with_degree=True: return a tuple (e,g) where658* e = index of simple twist in twist659* g = index of twist group in level rotation group660661"""662# horizontal edges => trivial twist group :-)663if self.is_horizontal():664return 1665N = len(self.edges)666M = FreeModule(ZZ, N)667# kernel of projections to Z/prong Z668K = M.submodule([M.basis()[i] * self.prong(e)669for i, e in enumerate(self.edges)])670if not quiet:671print("kernel:", K)672# submodule generated by edges crossing level i673# i.e. image of R^Gamma (level rotation group) in ZZ^edges674t_basis = [sum([M.basis()[i] for i, e in enumerate(self.edges)675if self.crosseslevel(e, self.internal_level_number(l))])676for l in range(1, self.numberoflevels())]677E = M.submodule(t_basis)678if not quiet:679print("t-module:", E)680# simple twist group: lcm of delta(l) edge prongs*t_i681deltas = [self.delta(l, True) for l in range(1, self.numberoflevels())]682if not quiet:683print("deltas:", deltas)684ll = [lcm([d.prong(e) for e in d.edges]) for d in deltas]685if not quiet:686print("lcms:", ll)687st_basis = [sum([ll[l - 1] * M.basis()[i] for i, e in enumerate(self.edges)688if self.crosseslevel(e, self.internal_level_number(l))])689for l in range(1, self.numberoflevels())]690S = M.submodule(st_basis)691if not quiet:692print("simple twist group:")693print(S)694print("Intersection:")695print(K.intersection(E))696# K.intersection(E) = Twist group (in ZZ^edges)697tw_gr = K.intersection(E)698if with_degree:699return (S.index_in(tw_gr), tw_gr.index_in(E))700else:701return S.index_in(tw_gr)702703704