| Download
Project: admcycles
Views: 724Visibility: Unlisted (only visible to those who know the link)
Image: ubuntu2004from __future__ import absolute_import1from __future__ import print_function23from copy import deepcopy45# pylint does not know sage6from sage.structure.sage_object import SageObject # pylint: disable=import-error7from sage.modules.free_module import FreeModule # pylint: disable=import-error8from sage.rings.integer_ring import ZZ # pylint: disable=import-error9from sage.arith.functions import lcm # pylint: disable=import-error10from sage.misc.flatten import flatten # pylint: disable=import-error11from sage.functions.generalized import sign # pylint: disable=import-error12from sage.misc.cachefunc import cached_method # pylint: disable=import-error13from sage.graphs.graph import Graph # pylint: disable=import-error1415from admcycles.admcycles import GraphIsom16from admcycles.stable_graph import StableGraph as stgraph17import admcycles.diffstrata.levelstratum18from admcycles.diffstrata.sig import Signature1920def smooth_LG(sig):21"""22The smooth (i.e. one vertex) LevelGraph in the stratum (sig).2324Args:25sig (Signature): signature of the stratum.2627Returns:28LevelGraph: The smooth graph in the stratum.29"""30return LevelGraph.fromPOlist([sig.g],[list(range(1,sig.n+1))],[],sig.sig,[0])3132class LevelGraph(SageObject):33r"""34Create a (stable) level graph.3536NOTE: This is a low-level class and should NEVER be invoced directly!37Preferably, EmbeddedLevelGraphs should be used and these should be38generated automatically by Stratum (or GeneralisedStratum).3940NOTE: We don't inherit from stgraph/StableGraph anymore, as LevelGraphs41should be static objects!4243Extends admcycles stgraph to represent a level graph as a stgraph,44i.e. a list of genera, a list of legs and a list of edges, plus a list of45poleorders for each leg and a list of levels for each vertex.4647Note that the class will warn if the data is not admissible, i.e. if the48graph is not stable or the pole orders at separating nodes across levels do49not add up to -2 or -1 on the same level (unless this is suppressed with50quiet=True).5152INPUT:5354genera : list55List of genera of the vertices of length m.56legs : list57List of length m, where ith entry is list of legs attached to vertex i.58By convention, legs are unique positive integers.59edges : list60List of edges of the graph. Each edge is a 2-tuple of legs.61poleorders : dictionary62Dictionary of the form leg number : poleorder63levels : list64List of length m, where the ith entry corresponds to the level of65vertex i.66By convention, top level is 0 and levels go down by 1, in particular67they are NEGATIVE numbers!68quiet : optional boolean (default = False)69Suppresses most error messages.7071ALTERNATIVELY, the pole orders can be supplied as a list by calling72LevelGraph.fromPOlist:73poleorders : list74List of order of the zero (+) or pole (-) at each leg. The ith element75of the list corresponds to the order at the leg with the marking i+176(because lists start at 0 and the legs are positive integers).7778EXAMPLES ::7980Creating a level graph with three components on different levels of genus 1,813 and 0. The bottom level has a horizontal node.8283sage: from admcycles.diffstrata import *84sage: 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: +SKIP85LevelGraph([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)8687or alternatively:8889sage: 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],True)90LevelGraph([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)9192We get a warning if the graph has non-stable components: (not any more ;-))9394sage: 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: +SKIP95Warning: Component 3 is not stable: g = 0 but only 1 leg(s)!96Warning: Graph not stable!97LevelGraph([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)98"""99def __init__(self,genera,legs,edges,poleorders,levels,quiet=False):100checks = False101if len(genera) != len(legs):102raise ValueError('genera and legs must have the same length')103self.genera = genera104self.legs = legs105self.edges = edges106self.poleorders = poleorders107self.levels = levels108# the signature consists of the marked points that are not half-edges109sig_list = tuple(poleorders[l] for l in self.list_markings())110self.sig = Signature(sig_list)111# associated stgraph...112self._stgraph = None113self.k = 1 # in case we want to implement k-differentials114self._init_more() # initiate some things and make sure all is in order115if checks:116self.checkadmissible(quiet)117self._has_long_edge = None118self._is_long = {}119self._is_bic = None120121@classmethod122def fromPOlist(cls,genera,legs,edges,poleordersaslist,levels,quiet=False):123"""124This gives a LevelGraph where the poleorders are given as a list, not125directly as a dictionary.126"""127# translate poleorder list to dictionary:128sortedlegs = sorted(flatten(legs))129if len(sortedlegs) != len(poleordersaslist):130raise ValueError("Numbers of legs and pole orders do not agree!"+131" Legs: " + repr(len(sortedlegs)) +132" Pole orders: " + repr(len(poleordersaslist)))133poleorderdict = { sortedlegs[i] : poleordersaslist[i] for i in range(len(sortedlegs)) }134return cls(genera,legs,edges,poleorderdict,levels,quiet)135136def __repr__(self):137return "LevelGraph("+self.input_as_string()+")"138139def __hash__(self):140return hash(repr(self))141142def __str__(self):143return ("LevelGraph " + repr(self.genera)+ ' ' + repr(self.legs) + ' '144+ repr(self.edges) + ' ' + repr(self.poleorders) + ' '145+ repr(self.levels))146147def input_as_string(self):148"""149return a string that can be given as argument to __init__ to give self150"""151return (repr(self.genera)+ ',' + repr(self.legs) + ','152+ repr(self.edges) + ',' + repr(self.poleorders) + ','153+ repr(self.levels)+',True')154155def __eq__(self, other):156if not isinstance(other, LevelGraph):157return False158return (self.genera==other.genera) and (self.levels == other.levels) and (self.legs == other.legs) and (set(self.edges)==set(other.edges)) and (self.poleorders == other.poleorders)159160## reimplementing the admcycles stuff we use:161@cached_method162def g(self):163genus = sum(self.genera) + len(self.edges) - len(self.genera) + ZZ.one()164assert genus == self.sig.g, "Signature %r does not match %r!" % (self.sig,self)165return genus166167@property168def stgraph(self):169if self._stgraph is None:170self._stgraph = stgraph([int(g) for g in self.genera],171[[int(l) for l in leg_list] for leg_list in self.legs],172self.edges)173return self._stgraph174175@cached_method176def list_markings(self,v=None):177r"""178Return the list of markings (non-edge legs) of self at vertex v.179"""180if v is None:181return tuple([j for v in range(len(self.genera))182for j in self.list_markings(v)])183s = set(self.legs[v])184for e in self.edges:185s -= set(e)186return tuple(s)187188##########################################################189#### Interface:190#### --- provide a series of methods that translate various data191#### (maybe this can be optimised by smart caching later, so only192#### these should be used...)193#### up to now, we have:194#### * vertex : (overloaded from admcycles)195#### INPUT : leg196#### returns : vertex of leg (index of genera)197#### * vertex_list :198#### returns : list of vertices (index of genera)199#### * edges_list :200#### returns : list of edges201#### * prongs_list :202#### returns : list of tuples (edge,prong)203#### * levelofvertex :204#### INPUT : index of genera205#### returns : level number (negative)206#### * levelofleg :207#### INPUT : leg label (given as positive integer)208#### returns : level number (negative)209#### * orderatleg :210#### INPUT : leg label (given as positive integer)211#### returns : poleorder (integer)212#### * ordersonvertex :213#### INPUT : index of genera214#### returns : list of poleorders (integers)215#### * verticesonlevel :216#### INPUT : level number (negative)217#### returns : list of indices of genera218#### * edgesatvertex :219#### INPUT : index of genera220#### returns : list of edges221#### * legsatvertex :222#### INPUT : index of genera223#### returns : list of legs224#### * simplepolesatvertex :225#### INPUT : index of genera226#### returns : list of legs with pole order -1227#### * genus :228#### INPUT : index of genera229#### returns : genus230#### * codim :231#### returns : codimension232#### * is_bic :233#### returns : boolean234#### * edgesatlevel :235#### INPUT : level number (negative)236#### returns : list of edges with at least one node at that level (or horizontal)237#### * horizontaledgesatlevel :238#### INPUT : level number (negative)239#### returns : list of horizontal edges240#### * prong :241#### INPUT : edge242#### returns : prong order at edge243#### * nextlowerlevel :244#### INPUT : level number245#### returns : level number of next lower level246#### * lowest_level :247#### returns : level number of lowest level248#### * is_horizontal :249#### INPUT : edge (or none for whole graph)250#### returns : boolean success251#### * has_loop :252#### INPUT : vertex253#### returns : boolean success254#### * edgesgoingupfromlevel :255#### INPUT : level256#### returns : list of edges with e[1] on level257#### * verticesabove :258#### INPUT : level259#### returns : list of vertices with level > l260#### * edgesabove :261#### INPUT : level262#### returns : list of edges with level of each vertex > l263#### * crosseslevel :264#### INPUT : edge, level265#### returns : boolean266#### * edgesgoingpastlevel :267#### INPUT : level268#### returns : list of edges with start level > l and269#### end level < l270#### * _pointtype :271#### INPUT : order (integer)272#### returns : pole/marked point/zero (string)273#### * is_meromorphic :274#### returns : boolean (check for pole among marked points)275#### * highestorderpole :276#### INPUT : vertex277#### returns : leg name (at v) or -1 if none found278#########################################################279@cached_method280def vertex(self,leg):281"""282The vertex (as index of genera) where leg is located.283284Args:285leg (int): leg286287Returns:288int: index of genera289"""290return self.legdict[leg]291@cached_method292def vertex_list(self):293return list(range(len(self.genera)))294@cached_method295def edges_list(self):296return self.edges297@cached_method298def prongs_list(self):299return [(e,p) for e,p in self.prongs.items()]300@cached_method301def levelofvertex(self,v):302return self.levels[v]303@cached_method304def levelofleg(self,leg):305return self.levelofvertex(self.vertex(leg))306@cached_method307def orderatleg(self,leg):308return self.poleorders[leg]309@cached_method310def ordersonvertex(self,v):311return [self.orderatleg(leg) for leg in self.legsatvertex(v)]312@cached_method313def verticesonlevel(self,level):314return [v for v in range(len(self.genera)) if self.levelofvertex(v) == level]315@cached_method316def edgesatvertex(self,v):317return [e for e in self.edges if self.vertex(e[0]) == v or318self.vertex(e[1]) == v]319@cached_method320def legsatvertex(self,v):321return self.legs[v]322@cached_method323def is_halfedge(self,leg):324"""325Check if leg has an edge attached to it.326"""327for e in self.edges:328if e[0] == leg or e[1] == leg:329return True330return False331@cached_method332def simplepolesatvertex(self, v):333return [l for l in self.legsatvertex(v) if self.orderatleg(l) == -1]334335@cached_method336def genus(self, v=None):337"""338Return the genus of vertex ``v``.339340If ``v`` is ``None``, return genus of the complete ``LevelGraph``.341"""342if v is None:343return self.g() # from admcycles344else:345return self.genera[v]346347@cached_method348def numberoflevels(self):349return len(set(self.levels))350def is_bic(self):351if self._is_bic is None:352self._is_bic = self.numberoflevels() == 2 and not self.is_horizontal()353return self._is_bic354@cached_method355def codim(self):356"""357Return the codim = No. of levels of self + horizontal edges.358"""359return (self.numberoflevels() - 1360+ len([e for e in self.edges if self.is_horizontal(e)]))361@cached_method362def edgesatlevel(self,level):363"""364Return list of edges with at least one node at level.365"""366return [e for e in self.edges if self.levelofleg(e[0]) == level or367self.levelofleg(e[1]) == level]368@cached_method369def horizontaledgesatlevel(self,level):370return [e for e in self.edgesatlevel(level) if self.is_horizontal(e)]371@cached_method372def prong(self, e):373"""374The prong order is the pole order of the higher-level pole +1375"""376if self.levelofleg(e[0]) > self.levelofleg(e[1]):377return self.orderatleg(e[0]) + 1378else:379return self.orderatleg(e[1]) + 1380@cached_method381def nextlowerlevel(self,l):382"""383Return the next lower level number or False if no legal level384(e.g. lowest level) is given as input.385386Point of discussion: Is it better to tidy up the level numbers387to be directly ascending or not?388389Pro tidy: * this becomes obsolete ;)390* easier to check isomorphisms?391Con tidy: * we "see" where we came from392* easier to undo/glue back in after cutting393"""394try:395llindex = self.sortedlevels.index(l) - 1396except ValueError:397return False398if llindex == -1:399return False400else:401return self.sortedlevels[llindex]402@cached_method403def internal_level_number(self,i):404"""405Return the internal i-th level, e.g.406407self.levels = [0,-2,-5,-3]408409then410411internal_level_number(0) is 0412internal_level_number(1) is -2413internal_level_number(2) is -3414internal_level_number(3) is -4415416Returns None if the level does not exist.417"""418reference_levels = list(reversed(sorted(list(set(self.levels)))))419i = abs(i)420if i >= len(reference_levels):421return None422else:423return reference_levels[i]424@cached_method425def level_number(self,l):426"""427Return the relative level number (positive!) of l, e.g.428429self.levels = [0,-2,-5,-3]430431then432433level_number(0) is 0434level_number(-2) is 1435level_number(-3) is 2436level_number(-5) is 3437438Returns None if the level does not exist.439"""440reference_levels = list(reversed(sorted(list(set(self.levels)))))441try:442return reference_levels.index(l)443except ValueError:444return None445@cached_method446def is_long(self,e):447"""448Check if edge e is long, i.e. passes through more than one level.449"""450try:451return self._is_long[e]452except KeyError:453il = abs(self.level_number(self.levelofleg(e[0])) - self.level_number(self.levelofleg(e[1]))) > 1454self._is_long[e] = il455return il456@cached_method457def has_long_edge(self):458if self._has_long_edge is None:459for e in self.edges:460if self.is_long(e):461self._has_long_edge = True462break463else:464self._has_long_edge = False465return self._has_long_edge466@cached_method467def lowest_level(self):468return self.sortedlevels[0]469@cached_method470def is_horizontal(self,e=None):471"""472Check if edge e is a horizontal edge or if self has a horizontal edge.473"""474if e is None:475for e in self.edges:476if self.is_horizontal(e):477return True478return False479if not (e in self.edges):480print("Warning: " + repr(e) + " is not an edge of this graph!")481return False482if self.levelofleg(e[0]) == self.levelofleg(e[1]):483return True484else:485return False486@cached_method487def has_loop(self,v):488"""489Check if vertex v has a loop.490"""491for e in self.edgesatvertex(v):492if self.vertex(e[0]) == self.vertex(e[1]):493return True494return False495@cached_method496def edgesgoingupfromlevel(self,l):497"""498Return the edges going up from level l.499500This uses that e[0] is not on a lower level than e[1]!501"""502return [e for e in self.edges if self.levelofleg(e[1])==l and503not self.is_horizontal(e)]504@cached_method505def verticesabove(self,l):506"""507Return list of all vertices above level l.508509If l is None, return all vertices.510"""511if l is None:512return list(range(len(self.genera)))513return [v for v in range(len(self.genera)) if self.levelofvertex(v) > l]514@cached_method515def edgesabove(self,l):516"""517Return a list of all edges above level l, i.e. start and end vertex518have level > l.519"""520return [e for e in self.edges if self.levelofleg(e[0]) > l521and self.levelofleg(e[1]) > l]522@cached_method523def crosseslevel(self,e,l):524"""525Check if e crosses level l (i.e. starts > l and ends <= l)526"""527return self.levelofleg(e[0]) > l and self.levelofleg(e[1]) <= l528@cached_method529def edgesgoingpastlevel(self,l):530"""531Return list of edges that go from above level l to below level l.532"""533return [e for e in self.edges if self.levelofleg(e[0]) > l534and self.levelofleg(e[1]) < l]535@cached_method536def _pointtype(self,order):537if order < 0:538return "pole"539elif order == 0:540return "marked point"541else:542return "zero"543@cached_method544def is_meromorphic(self):545"""546Returns True iff at least one of the MARKED POINTS is a pole.547"""548return any(self.orderatleg(l) < 0 for l in self.list_markings())549@cached_method550def highestorderpole(self,v):551"""552Returns the leg with the highest order free pole at v, -1 if no poles found.553"""554minorder = 0555leg = -1556for l in self.list_markings(v):557if self.orderatleg(l) < minorder:558minorder = self.orderatleg(l)559leg = l560return leg561#########################################################562#### end interface563#########################################################564565#########################################################566#### Checks567#########################################################568@cached_method569def checkadmissible(self,quiet=False):570"""571Run all kinds of checks on the level graph. Currently:572* Check if orders on each komponent add up to k*(2g-2)573* Check if graph is stable574* Check if orders at edges sum to -2575* Check if orders respect level crossing576"""577admissible = True578if not self.checkorders(quiet):579if not quiet:580print("Warning: Illegal orders!")581admissible = False582if not self.is_stable(quiet):583if not quiet:584print("Warning: Graph not stable!")585admissible = False586if not self.checkedgeorders(quiet):587if not quiet:588print("Warning: Illegal orders at a node!")589admissible = False590return admissible591592@cached_method593def checkorders(self,quiet=False):594"""595Check if the orders add up to k*(2g-2) on each component.596"""597for v, g in enumerate(self.genera):598if sum(self.ordersonvertex(v)) != self.k*(2*g-2):599if not quiet:600print("Warning: Component " + repr(v) + " orders add up to " +601repr(sum(self.ordersonvertex(v))) +602" but component is of genus " + repr(g))603return False604return True605606@cached_method607def is_stable(self,quiet=False):608"""609Check if graph is stable.610"""611# total graph:612e = 2*self.genus() - 2 + len(self.leglist)613if e < 0:614if not quiet:615print("Warning: Total graph not stable! 2g-2+n = " + repr(e))616return False617618# components:619for v in range(len(self.genera)):620if 3*self.genus(v) - 3 + len(self.legsatvertex(v)) < 0:621if not quiet:622print("Warning: Component " + repr(v) + " is not stable: g = "623+ repr(self.genus(v)) + " but only "624+ repr(len(self.legsatvertex(v))) + " leg(s)!")625return False626return True627628@cached_method629def checkedgeorders(self,quiet=False):630"""631Check that the orders at nodes (i.e. edges) sum to -1 and that lower632level has lower order.633"""634for e in self.edges:635orders = self.orderatleg(e[0]) + self.orderatleg(e[1])636if orders != -2:637if not quiet:638print("Warning: Orders at edge " + repr(e) + " add up to " +639repr(orders) + " instead of -2!")640return False641# iff the pole order at e[0] is > the poleorder at e[1], e[0] should be on a higher level than e[1]642if (sign(self.orderatleg(e[0]) - self.orderatleg(e[1]))643!= sign(self.levelofleg(e[0]) - self.levelofleg(e[1]))):644if not quiet:645print("Warning: Orders at edge " + repr(e) +646" do not respect level crossing!")647print("Poleorders are",648(self.orderatleg(e[0]),self.orderatleg(e[1])),649"but levels are",650(self.levelofleg(e[0]),self.levelofleg(e[1]))651)652return False653return True654655#################################################################656### End checks657#################################################################658659#################################################################660### Cleanup functions661### USE ONLY IN __init__!!! LevelGraphs should be static!!!662#################################################################663def _gen_prongs(self):664"""665Generate the dictionary edge : prong order.666The prong order is the pole order of the higher-level pole +1667"""668return { e : self.prong(e) for e in self.edges }669670def _init_more(self):671"""672This should be used _only_ for intialisation!673674Make sure everything is in order, in particular:675* sortedlevels is fine676* leglist is fine677* legdict is fine678* maxleg is fine679* maxlevel is fine680* prongs are fine681* underlying graph is fine682"""683self.sortedlevels = sorted(self.levels)684# legs is a nested list:685self.leglist = flatten(self.legs)686# legs as dictionary687self.legdict = { l:v for v in range(len(self.genera))688for l in self.legs[v] }689self.maxleg = max([max(j+[0]) for j in self.legs])690# maxlevel is named a bit misleading, as it is the LOWEST level691# (the max of the absolute values...)692self.maxlevel = max([abs(l) for l in self.levels])693# the prong orders are stored in a dictionary edge : prong order694self.prongs = self._gen_prongs()695# construct the "underlying graph" (as a sage graph)696# the edges are labeled by their "real" name,697# the vertices are of the form (i,g_i,'LG')698# NOTE: In the case of an EmbeddedLevelGraph, vertices "at infinity" might699# be added to this!700self.underlying_graph = Graph([[self.UG_vertex(i) for i in range(len(self.genera))],701[(self.UG_vertex(self.vertex(e[0])), self.UG_vertex(self.vertex(e[1])), e)702for e in self.edges]],703format='vertices_and_edges', loops=True, multiedges=True)704705def UG_vertex(self, v):706"""707Vertex of the underlying graph corresponding to v.708709Args:710v (int): vertex number (index of self.genera)711712Returns:713tuple: tuple encoding the vertex of the Underlying Graph.714"""715return (v, self.genera[v], 'LG')716717#################################################################718#### Extract a subgraph719#################################################################720def extract(self,vertices,edges):721"""722Extract the subgraph of self (as a LevelGraph) consisting of vertices723(as list of indices) and edges (as list of edges).724725Returns the levelgraph consisting of vertices, edges and all legs on726vertices (of self) with their original poleorders and the original727level structure.728"""729newvertices = deepcopy([self.genera[i] for i in vertices])730newlegs = deepcopy([self.legs[i] for i in vertices])731newedges = deepcopy(edges)732newpoleorders = deepcopy({l : self.orderatleg(l)733for l in flatten(newlegs)})734newlevels = deepcopy([self.levels[i] for i in vertices])735return LevelGraph(newvertices,newlegs,newedges,newpoleorders,newlevels)736737def levelGraph_from_subgraph(self,G):738"""739Returns the LevelGraph associated to a subgraph of underlying_graph740(with the level structure induced by self)741"""742return self.extract(G.vertices(),G.edge_labels())743744def stratum_from_level(self,l,excluded_poles=None):745"""746Return the LevelStratum at (relative) level l.747748Args:749l (int): relative level number (0,...,codim)750excluded_poles (tuple, defaults to None): a list of poles (legs of the graph,751that are marked points of the stratum!) to be ignored for r-GRC.752753Returns:754LevelStratum: the LevelStratum, i.e.755* a list of Signatures (one for each vertex on the level)756* a list of residue conditions, i.e. a list [res_1,...,res_r]757where each res_k is a list of tuples [(i_1,j_1),...,(i_n,j_n)]758where each tuple (i,j) refers to the point j (i.e. index) on the759component i and such that the residues at these points add up760to 0.761* a dictionary of legs, i.e. n -> (i,j) where n is the original762number of the point (on the LevelGraph self) and i is the763number of the component, j the index of the point in the signature tuple.764Note that LevelStratum is a GeneralisedStratum together with765a leg dictionary.766"""767if self.is_horizontal():768print("Error: Cannot extract levels of a graph with horizontal edges!")769return None770internal_l = self.internal_level_number(l)771# first, we extract the obvious information from self at this level:772vv = self.verticesonlevel(internal_l)773legs_on_level = []774sigs = []775res_cond = []776leg_dict = {}777for i,v in enumerate(vv):778legs_on_level += [deepcopy(self.legsatvertex(v))]779leg_dict.update([(n,(i,j)) for j,n in enumerate(legs_on_level[-1])])780sig_v = Signature(tuple(self.orderatleg(leg) for leg in legs_on_level[-1]))781sigs += [sig_v]782# Now we hunt for residue conditions.783# We work with the underlying graph to include the "level at infinity":784# We "cheat" here, to catch the edges going up from level l: These are the785# edges of the graph above level l-1 that have a vertex on level l:786# (see below for parsing of these edges)787ee = []788for e in self.UG_above_level(internal_l - 1).edges():789for UG_vertex in e[:2]:790if UG_vertex[2] == 'res':791continue792if self.levelofvertex(UG_vertex[0]) == internal_l:793ee.append(e)794break795G = self.UG_above_level(internal_l)796conn_comp = G.connected_components_subgraphs()797# Residue conditions are obtained by sorting the poles on this level798# by connected component of G they are attached to (note that this799# includes the "level at infinity"!)800for c in conn_comp:801# if there is a free pole in c, we ignore this component802if self._redeemed_by_merom_in_comp(c,excluded_poles=excluded_poles):803continue804res_cond_c = []805# Otherwise we record all poles to attached to this component in one806# residue condition:807for e in ee:808# We work with edges of the underlying graph here, so we have to809# be a bit more careful. Recall that edges of the underlying graph are of810# the form:811# * (UG_vertex, UG_vertex, edge name) if it's an edge of LG or812# * (UG_vertex, UG_vertex, resiedgej) if it's an edge to level infinity813# Here UG_vertex is either of the form (vertex number, genus, 'LG') or814# (i, 0, 'res') where815# i is the number of the vertex at infinity and resiedgej is the edge816# connecting the residue i to the leg j (of self).817if e[0] in c or e[1] in c:818# res_cond contains tuples (i,j) where i is the index of the819# component and j the index of the pole in sig_i.820#821# We need find the vertex on this level (i.e. not in c)822if e[0] in c:823UG_vertex = e[1]824else:825UG_vertex = e[0]826# to find the pole on v, we have to distinguish if this is an827# edge of self or if it connects to a residue at infinity:828edge = e[2]829if 'res' in edge:830# the leg is the number after 'edge' in the edge string:831_, leg = edge.split('edge')832leg = int(leg)833else:834# edge consists of (leg_top,leg_bot) and leg_bot is on level i:835leg = edge[1]836# We retrieve the stratum point from leg_dict:837res_cond_c += [leg_dict[leg]]838if res_cond_c:839res_cond += [res_cond_c]840return admcycles.diffstrata.levelstratum.LevelStratum(sigs,res_cond,leg_dict)841842def UG_vertices_above_level(self,l):843"""844The vertices above (internal) level l (including possible vertices at infinity).845846Used for checking residue conditions.847848Args:849l (int): internal level number850851Returns:852list: list of vertices of self.underlying_graph853"""854# note that verticesabove checks for >l855vertices_above = [self.UG_vertex(i) for i in self.verticesabove(l)]856vertices_above += [v for v in self.underlying_graph.vertices() if v[2] == 'res']857return vertices_above858859def UG_above_level(self,l):860"""861Subgraph of Underlying Graph (including vertices at infinity) (strictly) above862(relative!) level l.863864Args:865l (int): relative level number866867Returns:868SAGE Graph: subgraph of self.underlying_graph with all vertices strictly869above level l.870"""871internal_l = self.internal_level_number(l)872vertices = self.UG_vertices_above_level(internal_l)873abovegraph = self.underlying_graph.subgraph(vertices)874return abovegraph875876#################################################################877#### Check Residue condition (via inconvenient vertices/edges)878#################################################################879880@cached_method881def is_inconvenient_vertex(self,v):882"""883Check if vertex is inconvenient, i.e.884* g = 0885* no simple poles886* there exists a pole order m_i such that m_i > sum(m_j)-p-1887Return boolean888"""889# inconvenient vertices are of genus 0 and have no simple poles.890if self.genus(v) > 0 or len(self.simplepolesatvertex(v)) > 0:891return False892# check inequality893ll = self.legsatvertex(v)894poles = [l for l in ll if self.orderatleg(l) < 0]895polesum = sum([-self.orderatleg(l) for l in poles])896p = len(poles)897for l in ll:898if self.orderatleg(l) > polesum - p - 1:899return True900return False901902def _redeemed_by_merom_in_comp(self,G,excluded_poles=None):903"""904Check if there is a pole in the subgraph G (intended to be a connected905component above a vertex).906907excluded_poles are ignored (for r-GRC).908909Returns boolean (True = exists a pole)910"""911if excluded_poles is None:912excluded_poles = []913for w in G.vertices():914if w[2] == 'res': # vertex at infinity915continue916for l in self.list_markings(w[0]):917if (self.orderatleg(l) < 0) and (not l in excluded_poles):918return True919return False920921@cached_method922def is_illegal_vertex(self,v,excluded_poles=None):923"""924Check if vertex is inconvenient and is not redeemed, i.e.925* v is inconvenient926* there are no two seperate edges to the same connected component above (i.e. loop above)927* if meromorphic: v is not connected to higher level marked poles928929We can also pass a tuple (hashing!) of poles that are to be excluded because of930residue conditions.931932Return boolean933"""934if not self.is_inconvenient_vertex(v):935return False936l = self.levelofvertex(v)937# in the underlying_graph, vertices are of the form (index in genera, genus, 'LG'):938v_graph = self.UG_vertex(v)939# edges not going down from v:940ee = [e for e in self.edgesatvertex(v) if self.levelofleg(e[0]) >= l941and self.levelofleg(e[1]) >= l]942# in the holomorphic case, legal inconvenient vertices need at least two edges not going down943if len(ee) < 1 and not self.is_meromorphic():944return True945# check if v has two edges into one connected component that don't lie below v,946# i.e. if v can be redeemed:947abovegraph = self.UG_above_level(l-1)948cc = self.underlying_graph.subgraph([w949for w in abovegraph.connected_component_containing_vertex(v_graph)950if w[2] == 'res' or self.levelofvertex(w[0]) >= l])951cc.delete_vertex(v_graph)952conn_comp = cc.connected_components_subgraphs()953if excluded_poles is None:954excluded_poles = []955freepoles = len([l for l in self.list_markings(v)956if (self.orderatleg(l) < 0) and (not l in excluded_poles)])957for G in conn_comp:958# edges from v to G:959# (Careful: cc does not have any edges with v anymore, so we must use abovegraph!)960eeG = [e for e in abovegraph.edges()961if (e[0] == v_graph and e[1] in G.vertices())962or (e[1] == v_graph and e[0] in G.vertices())]963if len(eeG) > 1:964# redeemed :-)965return False966# in the meromorphic case, we also check if a "free" pole exists in a connected component above v967if self.is_meromorphic() and self._redeemed_by_merom_in_comp(G,excluded_poles=excluded_poles):968freepoles += 1969if freepoles >= 2:970# redeemed :-)971return False972return True973974@cached_method975def is_illegal_edge(self,e,excluded_poles=None):976"""977Check if edge is illegal, i.e. if978* e is horizontal (not loop) and979* there no simple loop over e980* there is no "free" pole over each end point981982excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.983984Return boolean985"""986if not self.is_horizontal(e) or (e[0] == e[1]):987return False988# check if there is a simple loop above e, i.e. if the subgraph989# above e is still connected after e is removed (e is not a bridge)990l = self.levelofleg(e[0])991# note that verticesabove checks for >l992abovegraph = self.UG_above_level(l-1)993# edges are encoded by (self.vertex(e[0]),self.vertex(e[1]),e) in994# underlying_graph!995cut = abovegraph.is_cut_edge((self.UG_vertex(self.vertex(e[0])), self.UG_vertex(self.vertex(e[1])), e))996# True = e is a cut-edge, i.e. connected components increase when removing e, i.e. no loop above,997# i.e. illegal unless also meromorphic998# False = graph has a loop, i.e. not illegal999if not cut:1000return False1001else:1002if self.is_meromorphic():1003# if there are "free" poles above the endpoints, we are still fine1004abovegraph.delete_edge((self.UG_vertex(self.vertex(e[0])), self.UG_vertex(self.vertex(e[1])), e))1005polesfound = 01006for l in e:1007v = self.vertex(l)1008G = abovegraph.connected_component_containing_vertex(self.UG_vertex(v))1009if self._redeemed_by_merom_in_comp(abovegraph.subgraph(G),excluded_poles=excluded_poles):1010polesfound += 11011if polesfound == 2:1012# we are fine1013return False1014# no redemption :/1015return True10161017@cached_method1018def is_legal(self,quiet=False,excluded_poles=None):1019"""1020Check if self has illegal vertices or edges.10211022excluded_poles may be a tuple (hashing!) of poles to be excluded for r-GRC.10231024Return boolean1025"""1026for v in range(len(self.genera)):1027if self.is_illegal_vertex(v,excluded_poles=excluded_poles):1028if not quiet:1029print("Vertex", v, "is illegal!")1030return False1031for e in self.edges:1032if self.is_illegal_edge(e,excluded_poles=excluded_poles):1033if not quiet:1034print("Edge", e, "is illegal!")1035return False1036return True10371038#################################################################1039#### Squishing ---- i.e. contracting horizontal edges / levels1040#################################################################10411042def squish_horizontal(self,e,adddata=False):1043"""1044Squish the horizontal edge e and return the new graph.1045If adddata=True is specified, we additionally return the level graph1046that was removed together with the data needed to glue it back in,1047i.e. such that composition with split_horizontal is the identity.10481049More precisely: return a tuple (G,(v,dl,lg,loop)) consisting of1050* the contracted LevelGraph G1051* the "remaining" vertex v1052* a dictionary dl remembering how the legs were split between the1053original vertices1054* a list lg consisting of the genera of the original vertices1055* a boolean loop1056"""1057# make sure v <= w1058[v,w] = sorted([self.vertex(e[0]),self.vertex(e[1])])10591060# generate extra data1061if adddata:1062if v == w:1063loop = True1064lg = [self.genus(v)]1065else:1066loop = False1067lg = [self.genus(v),self.genus(w)]1068dl = dict([(l,0) for l in self.legsatvertex(v)] +1069[(l,1) for l in self.legsatvertex(w)])1070del dl[e[0]]1071del dl[e[1]]1072extradata = (v,dl,lg,loop)10731074if not self.is_horizontal(e):1075print("Warning: " + repr(e) + " is not a horizontal edge -- Not contracting.")1076if adddata:1077return (self,extradata)1078else:1079return self10801081newgenera = deepcopy(self.genera)1082newlegs = deepcopy(self.legs)1083newedges = deepcopy(self.edges)1084newpoleorders = deepcopy(self.poleorders)1085newlevels = deepcopy(self.levels)1086if v == w:1087# --horizontal loop getting contracted---1088# add genus to node:1089newgenera[v] += 11090else:1091# --horizontal edge getting contracted---1092# transfer genus and legs from w to v:1093newgenera[v] += newgenera[w]1094newlegs[v] += newlegs[w]1095# remove all traces of w1096newgenera.pop(w)1097newlegs.pop(w)1098newlevels.pop(w)1099# remove edge:1100newedges.remove(e)1101# remove legs1102newlegs[v].remove(e[0])1103newlegs[v].remove(e[1])1104# remove poleorders1105del newpoleorders[e[1]]1106del newpoleorders[e[0]]11071108returngraph = LevelGraph(newgenera,newlegs,newedges,newpoleorders,newlevels,True)11091110if adddata:1111return (returngraph,extradata)1112else:1113return returngraph11141115def squish_vertical_slow(self,l,adddata=False,quiet=False):1116"""1117Squish the level l (and the next lower level!) and return the new graph.1118If addata=True is specified, we additionally return a boolean that tells1119us if a level was squished and the legs that no longer exist.11201121More precisely, adddata=True returns a tuple (G,boolean,legs) where1122* G is the (possibly non-contracted) graph and1123* boolean tells us if a level was squished1124* legs is the (possibly empty) list of legs that don't exist anymore11251126Implementation:1127At the moment, we remember all the edges (ee) that connect level l to1128level l-1. We then create a new LevelGraph with the same data as self,1129the only difference being that all vertices of level l-1 have now been1130assigned level l. We then remmove all (now horizontal!) edges in ee.11311132In particular, all points and edges retain their numbering (only the level might have changed).11331134WARNING: Level l-1 does not exist anymore afterwards!!!11351136Downside: At the moment we get a warning for each edge in ee, as these1137don't have legal pole orders for being horizontal (so checkedgeorders1138complains each time the constructor is called :/).1139"""1140if l is None:1141return self1142ll = self.nextlowerlevel(l)1143if ll is False:1144if not quiet:1145print("Warning: Illegal level to contract: " + repr(l))1146if adddata:1147return (self,False,None)1148else:1149return self11501151if not quiet:1152print("Squishing levels", l, "and", ll)11531154vv = self.verticesonlevel(ll)1155# edges that go to next lower level, i.e. will be contracted1156ee = [e for e in self.edgesatlevel(l) if self.levelofleg(e[1]) == ll1157and not self.is_horizontal(e)]11581159if not quiet:1160print("Contracting edges", ee)11611162newgenera = deepcopy(self.genera)1163newlegs = deepcopy(self.legs)1164newedges = deepcopy(self.edges)1165newpoleorders = deepcopy(self.poleorders)1166newlevels = deepcopy(self.levels)11671168# raise level1169for v in vv:1170newlevels[v] = l1171returngraph = LevelGraph(newgenera,newlegs,newedges,newpoleorders,newlevels,True)1172# contract edges that are now horizontal:1173for e in ee:1174returngraph = returngraph.squish_horizontal(e)11751176if adddata:1177return (returngraph,True,flatten(ee))1178else:1179return returngraph11801181def squish_vertical(self,l,adddata=False,quiet=True):1182"""1183Squish the level l (and the next lower level!) and return the new graph.11841185Implementation:1186At the moment, we remember all the edges (ee) that connect level l to1187level l-1. We then create a new LevelGraph with the same data as self,1188the only difference being that all vertices of level l-1 have now been1189assigned level l. We then remmove all edges in ee.11901191(In contrast to the old squish_vertical_slow, this is now done in1192one step and not recursively so we avoid a lot of the copying of graphs.)11931194In particular, all points and edges retain their numbering (only the level might have changed).11951196WARNING: Level l-1 does not exist anymore afterwards!!!11971198Args:1199l (int): (internal) level number1200adddata (bool, optional): For legacy reasons, do not use. Defaults to False.1201quiet (bool, optional): No output. Defaults to True.12021203Returns:1204LevelGraph: new levelgraph with one level less.1205"""1206if l is None:1207return self1208ll = self.nextlowerlevel(l)1209levelset = set([l, ll])1210if ll is False:1211if not quiet:1212print("Warning: Illegal level to contract: " + repr(l))1213else:1214return self1215vertices = self.verticesonlevel(l) + self.verticesonlevel(ll)1216# edges that go to next lower level, i.e. will be contracted1217edges = [e for e in self.edges1218if set([self.levelofleg(e[0]), self.levelofleg(e[1])]) == levelset]1219# we use two dictionaries for quick lookup while we reorder the info:1220genus_of_vertex = {v : self.genus(v) for v in vertices}1221vertex_of_leg = {leg : v for v in vertices for leg in self.legsatvertex(v)}1222legs_of_vertex = {v : self.legsatvertex(v)[:] for v in vertices}1223deleted_legs = []1224while edges:1225start, end = edges.pop()1226v = vertex_of_leg[start]1227w = vertex_of_leg[end]1228del vertex_of_leg[start]1229del vertex_of_leg[end]1230legs_of_vertex[v].remove(start)1231legs_of_vertex[w].remove(end)1232deleted_legs.extend([start,end])1233if v == w:1234# if e (now) connects a vertex to itself, increase its genus1235genus_of_vertex[v] += 11236else:1237# if e connects two different vertices, combine their data1238# (moving everything from w to v)1239genus_of_vertex[v] += genus_of_vertex[w]1240del genus_of_vertex[w]1241for leg in legs_of_vertex[w]:1242vertex_of_leg[leg] = v1243legs_of_vertex[v].append(leg)1244del legs_of_vertex[w]1245# Build new graph with this data:1246# We use sets for more efficient lookup:1247vertices = set(vertices)1248deleted_legs = set(deleted_legs)12491250newgenera = []1251newlegs = []1252newlevels = []1253# we copy the untouched vertices and insert the new ones:1254for v in range(len(self.genera)):1255if v in vertices:1256if v in genus_of_vertex:1257newgenera.append(genus_of_vertex[v])1258newlegs.append(legs_of_vertex[v][:])1259newlevels.append(l)1260# otherwise it has been deleted1261else:1262newgenera.append(self.genus(v))1263newlegs.append(self.legsatvertex(v)[:])1264newlevels.append(self.levelofvertex(v))12651266# for the edges, we remove those with a delted edge:1267newedges = []1268for start, end in self.edges:1269if start in deleted_legs:1270assert end in deleted_legs1271continue1272assert not end in deleted_legs1273newedges.append((start, end))1274# for the new poleorders, we only have to remove the deleted legs:1275newpoleorders = {leg : p for leg, p in self.poleorders.items() if not leg in deleted_legs}12761277return LevelGraph(newgenera,newlegs,newedges,newpoleorders,newlevels,True)12781279def delta(self,k,quiet=False):1280"""1281Squish all levels except for the k-th.12821283Note that delta(1) contracts everything except top-level and that1284the argument is interpreted via internal_level_number12851286WARNING: Currently giving an out of range level (e.g.12870 or >= maxlevel) squishes the entire graph!!!12881289Return the corresponding divisor.1290"""1291G = self1292if self.is_horizontal():1293print("Error: Cannot delta a graph with horizontal edges!")1294return deepcopy(G)1295# recursive squishing forces us to go backwards, as the lower squished1296# level is always removed.1297for i in reversed(range(self.numberoflevels()-1)):1298# unfortunately delta and squish use slightly incompatible level1299# numbering, as the "illegal" squish here is k-11300if abs(k)-1 == i:1301continue1302if not quiet:1303print("Squishing level", i)1304# note that it is important that internal_level_number of1305# self and not G is called in the argument, as the relative1306# numbering of levels in G changes, but the internal names don't1307G = G.squish_vertical(self.internal_level_number(i),quiet=quiet)1308return G13091310#################################################################1311#### end Squishing1312#################################################################13131314#################################################################1315#### Isomorphisms1316#### DEPRECATED!!!! USE EmbeddedLevelGraphs instead!1317####1318#### This only remains for the BIC generation comparison tests.1319#################################################################1320def _init_invariant(self):1321"""1322DEPRECATED!! DO NOT USE!!13231324Compute a bunch of invariants (as a quick check for not isommorphic).1325Up to now we have:1326* sorted list of genera (by level)1327* number of edges1328* number of levels1329* names and pole orders of marked points1330* sorted list of pole orders1331"""1332self._invariant = (1333len(self.edges),1334self.codim(),1335[(l,self.orderatleg(l)) for l in sorted(self.list_markings())],1336sorted([self.orderatleg(l) for l in self.leglist]))1337def invariant(self):1338"""1339DEPRECATED!!! DO NOT USE!!!1340"""1341self._init_invariant()1342return self._invariant13431344def _genisom_preserves_levels(self,other,dV):1345"""1346Check if a "vertex isomorphism" preserves the (relative) level structure1347"""1348return all(self.sortedlevels.index(self.levelofvertex(sv))1349== other.sortedlevels.index(other.levelofvertex(ov))1350for sv, ov in dV.items())13511352def _legisom_preserves_poleorders(self,other,dL):1353"""1354Check if a "leg isomorphism" preserves pole orders.1355"""1356return all(self.orderatleg(sl) == other.orderatleg(ol)1357for sl, ol in dL.items())13581359def _leveliso(self,other,dV):1360"""1361Give a dictionary translating the levels of self to other in accordance1362with the dictionary of vertices dV.1363"""1364return {l : other.levelofvertex(dV[self.verticesonlevel(l)[0]])1365for l in self.levels}13661367def is_isomorphic(self,other,adddata=False):1368"""1369DEPRECATED!!! Instead, use EmbeddedLevelGraph!!!13701371Check if self is isomorphic to other.1372Return boolean + list of isomorphisms if adddata=True.13731374Note that our isomorphisms are stgraph isomorphisms plus a dictionary1375translating the level structure of self into that of other.13761377At the moment we use admcycles.GraphIsom and check which of these1378preserve the LevelGraph structure (using the above invariants, though!)1379Probably it would be much faster to reimplement this for LevelGraphs1380as it seems quite redundant as is...1381"""1382isoms = GraphIsom(self.stgraph, other.stgraph)1383# check if the stable graph isomorphisms preserve level structure1384# GraphIsoms consist of a tuple (dV,dL), where dV is a dictionary1385# translating the vertices and dL is one for the legs.1386levelisoms = [[dV,dL,self._leveliso(other,dV)] for [dV,dL] in isoms1387if self._genisom_preserves_levels(other,dV)1388and self._legisom_preserves_poleorders(other,dL)]1389if adddata:1390return (levelisoms != [], levelisoms)1391else:1392return (levelisoms != [])13931394#################################################################1395#### Twist groups1396#################################################################1397def twist_group(self,quiet=True,with_degree=False):1398"""1399This should not be needed! The stack factor should be computed1400using AdditiveGenerator.stack_factor!!!14011402Calculate the index of the simple twist group inside the twist group.14031404with_degree=True: return a tuple (e,g) where1405* e = index of simple twist in twist1406* g = index of twist group in level rotation group14071408"""1409# horizontal edges => trivial twist group :-)1410if self.is_horizontal():1411return 11412N = len(self.edges)1413M = FreeModule(ZZ,N)1414# kernel of projections to Z/prong Z1415K = M.submodule([M.basis()[i]*self.prong(e)1416for i,e in enumerate(self.edges)])1417if not quiet:1418print("kernel:", K)1419# submodule generated by edges crossing level i1420# i.e. image of R^Gamma (level rotation group) in ZZ^edges1421t_basis = [sum([M.basis()[i] for i,e in enumerate(self.edges)1422if self.crosseslevel(e,self.internal_level_number(l))])1423for l in range(1,self.numberoflevels())]1424E = M.submodule(t_basis)1425if not quiet:1426print("t-module:", E)1427# simple twist group: lcm of delta(l) edge prongs*t_i1428deltas = [self.delta(l,True) for l in range(1,self.numberoflevels())]1429if not quiet:1430print("deltas:", deltas)1431ll = [lcm([d.prong(e) for e in d.edges]) for d in deltas]1432if not quiet:1433print("lcms:", ll)1434st_basis = [sum([ll[l-1] * M.basis()[i] for i,e in enumerate(self.edges)1435if self.crosseslevel(e,self.internal_level_number(l))])1436for l in range(1,self.numberoflevels())]1437S = M.submodule(st_basis)1438if not quiet:1439print("simple twist group:")1440print(S)1441print("Intersection:")1442print(K.intersection(E))1443# K.intersection(E) = Twist group (in ZZ^edges)1444tw_gr = K.intersection(E)1445if with_degree:1446return (S.index_in(tw_gr),tw_gr.index_in(E))1447else:1448return S.index_in(tw_gr)14491450#################################################################1451#### Plotting1452#################################################################1453def plot_obj(self):1454"""1455Return a sage graphics object generated from a sage graph.14561457TODO: Some things that are still ugly:1458* No legs!!1459* currently the vertices have labels (index,genus)1460* loops are vertical not horizontal1461"""1462# Create the underlying graph with edge label prong order.1463G = Graph([self.genera,1464[(self.vertex(e[0]),self.vertex(e[1]),self.prong(e))1465for e in self.edges]],1466format='vertices_and_edges', loops=True, multiedges=True)1467# unfortunately, sage insists of displaying the vertex name (index)1468# and not only the label (genus), so we display both...1469G.relabel(list(enumerate(self.genera)))1470h={l:[(v,self.genus(v)) for v in self.verticesonlevel(l)]1471for l in self.levels}1472# at the moment we just display a list of legs and orders at the bottom1473legtext = "Marked points: "1474for l in sorted(set(self.levels)):1475points_at_level = [leg for leg in self.list_markings()1476if self.levelofleg(leg) == l]1477if not points_at_level:1478continue1479legtext += "\nAt level %s: "%l1480for leg in points_at_level:1481legtext += "\n" + self._pointtype(leg)1482legtext += " of order " + repr(self.orderatleg(leg))1483print(legtext)1484return G.plot(edge_labels=True, vertex_size=1000,1485heights=h, layout='ranked') + text(legtext,(0,-0.1),1486vertical_alignment='top',axis_coords=True,axes=False)148714881489