| Download
Project: admcycles
Views: 724Visibility: Unlisted (only visible to those who know the link)
Image: ubuntu2004from __future__ import print_function12import itertools34from sympy.utilities.iterables import partitions, permutations, multiset_partitions56# pylint does not know sage7from sage.graphs.graph import Graph # pylint: disable=import-error8from sage.graphs.generators.basic import CompleteBipartiteGraph # pylint: disable=import-error9# sage documentation says compositions are better than OrderedPartitions, no idea why....10from sage.combinat.composition import Compositions as sage_part # pylint: disable=import-error11from sage.combinat.partition import OrderedPartitions # pylint: disable=import-error12from sage.misc.cachefunc import cached_function # pylint: disable=import-error1314from admcycles.stable_graph import StableGraph as stgraph15import admcycles.diffstrata.levelgraph16import admcycles.diffstrata.levelstratum17from admcycles.diffstrata.sig import Signature1819############################################################20############################################################21#### Old BIC generation:22#### This was the first attempt at generating BICs23#### (generating all stable graphs with admcycles and then24#### putting all possible bipartite structures etc on them).25#### It's nice for historic reasons but painfully slow!26#### Definitely use bic_alt below instead!!!27############################################################28############################################################293031def SageGraph(gr):32# converts stgraph gr in Sage graph33legdic={i:vnum for vnum in range(len(gr.legs(copy=False))) for i in gr.legs(vnum,copy=False)}34return Graph([(legdic[a], legdic[b]) for (a,b) in gr.edges(copy=False)],loops=True, multiedges=True)353637def bics(g,orders):38"""39Generate BICs for stratum with signature orders of genus g.4041DO NOT USE!!! USE bic_alt instead!!!4243Args:44g (int): genus45orders (tuple): signature tuple4647Returns:48list: list of BICs49"""50print('WARNING! This is not the normal BIC generation algorithm!')51print("Only use if you know what you're doing!")52n=len(orders)53bound=g+n54result=[] # list of levelgraphs55orderdict={i+1:orders[i] for i in range(n)}5657for e in range(1,bound+1):58# look at which stgraphs in Mbar_g,n with e edges are bipartite59for gr in new_list_strata(g,n,e):60check=SageGraph(gr).is_bipartite(True)61if check[0]:62# check[1] contains dictionary {vertices: 0 or 1}63vert1=[v for v in check[1] if check[1][v]==0]64vert2=[v for v in check[1] if check[1][v]==1]65result+=bics_on_bipgr(gr,vert1,vert2,orderdict)66result+=bics_on_bipgr(gr,vert2,vert1,orderdict)67return result686970def bics_on_bipgr(gr,vertup,vertdown,orderdict):71# takes stable graph and partition of range(len(gr.genera)) into vertup, vertdown72# creates list of possible pole orders at edges73result=[]7475# removed from admcycles so reinserted here for legacy purposes:76def dicunion(*dicts):77return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))7879numvert=len(gr.genera(copy=False))80halfedges=gr.halfedges()81halfedgeinvers={e0:e1 for (e0,e1) in gr.edges(copy=False)}82halfedgeinvers.update({e1:e0 for (e0,e1) in gr.edges(copy=False)})8384levels=[0 for i in range(numvert)]85for i in vertdown:86levels[i]=-18788helist=[[l for l in ll if l in halfedges] for ll in gr.legs(copy=False)] #list of half-edges of each vertex89degs=[] # list (with len numvert) of orders that need to be provided by pole orders of half-edges (not legs)90for v in range(numvert):91degs.append(2*gr.genera(v)-2-sum([orderdict[l] for l in gr.list_markings(v)]))92# if degs<0:93# return []9495weightparts=[]96for v in vertup:97vweightpart=[]98for part in OrderedPartitions(degs[v]+len(helist[v]),len(helist[v])):99# dictionary of weights from edges connected to vertex v100vdic={helist[v][i]:part[i]-1 for i in range(len(helist[v]))}101vdic.update({halfedgeinvers[helist[v][i]]:-part[i]-1 for i in range(len(helist[v]))})102#print vdic103vweightpart.append(vdic)104weightparts+=[vweightpart]105#print weightparts106107for parts in itertools.product(*weightparts):108#print parts109poleorders=dicunion(orderdict,*parts)110CandGraph=admcycles.diffstrata.levelgraph.LevelGraph(gr.genera(copy=False),gr.legs(copy=False),gr.edges(copy=False),poleorders,levels,quiet=True)111if CandGraph.is_legal(True) and CandGraph.checkadmissible(True):112result.append(CandGraph)113return result114115116def new_list_strata(g, n, r):117return [lis[0] for lis in admcycles.admcycles.degeneration_graph(int(g), n, r)[0][r]]118119############################################################120############################################################121122############################################################123############################################################124#### New BIC generation. This is currently used.125############################################################126############################################################127128129@cached_function130def bic_alt_noiso(sig):131"""132Construct all non-horizontal divisors in the stratum sig.133134More precisely, each BIC is LevelGraph with two levels numbered 0, -1135and marked points 1,...,n where the i-th point corresponds to the element136i-1 of the signature.137138Note that this is the method called by GeneralisedStratum.gen_bic.139140Args:141sig (Signature): Signature of the stratum.142143Returns:144list: list of 2-level non-horizontal LevelGraphs.145146EXAMPLES ::147148sage: from admcycles.diffstrata import *149sage: assert comp_list(bic_alt(Signature((1,1))),\150[LevelGraph([1, 0],[[3, 4], [1, 2, 5, 6]],[(3, 5), (4, 6)],{1: 1, 2: 1, 3: 0, 4: 0, 5: -2, 6: -2},[0, -1],True),\151LevelGraph([1, 1, 0],[[4], [3], [1, 2, 5, 6]],[(3, 5), (4, 6)],{1: 1, 2: 1, 3: 0, 4: 0, 5: -2, 6: -2},[0, 0, -1],True),\152LevelGraph([1, 1],[[3], [1, 2, 4]],[(3, 4)],{1: 1, 2: 1, 3: 0, 4: -2},[0, -1],True),\153LevelGraph([2, 0],[[3], [1, 2, 4]],[(3, 4)],{1: 1, 2: 1, 3: 2, 4: -4},[0, -1],True)])154155sage: assert comp_list(bic_alt(Signature((2,))),\156[LevelGraph([1, 1],[[2], [1, 3]],[(2, 3)],{1: 2, 2: 0, 3: -2},[0, -1],True),\157LevelGraph([1, 0],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 2, 2: 0, 3: 0, 4: -2, 5: -2},[0, -1],True)])158159sage: assert comp_list(bic_alt(Signature((4,))),\160[LevelGraph([1, 1, 0],[[2, 4], [3], [1, 5, 6, 7]],[(2, 5), (3, 6), (4, 7)],{1: 4, 2: 0, 3: 0, 4: 0, 5: -2, 6: -2, 7: -2},[0, 0, -1],True),\161LevelGraph([1, 1, 1],[[3], [2], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 0, 3: 0, 4: -2, 5: -2},[0, 0, -1],True),\162LevelGraph([2, 0],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 2, 3: 0, 4: -4, 5: -2},[0, -1],True),\163LevelGraph([2, 0],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 1, 3: 1, 4: -3, 5: -3},[0, -1],True),\164LevelGraph([1, 0],[[2, 3, 4], [1, 5, 6, 7]],[(2, 5), (3, 6), (4, 7)],{1: 4, 2: 0, 3: 0, 4: 0, 5: -2, 6: -2, 7: -2},[0, -1],True),\165LevelGraph([1, 2],[[2], [1, 3]],[(2, 3)],{1: 4, 2: 0, 3: -2},[0, -1],True),\166LevelGraph([2, 1],[[2], [1, 3]],[(2, 3)],{1: 4, 2: 2, 3: -4},[0, -1],True),\167LevelGraph([1, 1],[[2, 3], [1, 4, 5]],[(2, 4), (3, 5)],{1: 4, 2: 0, 3: 0, 4: -2, 5: -2},[0, -1],True)])168169sage: len(bic_alt(Signature((1,1,1,1)))) # long time (2 seconds)170102171172sage: len(bic_alt(Signature((2,2,0,-2))))17361174175sage: len(bic_alt((2,2,0,-2)))17661177"""178if isinstance(sig, tuple):179sig = Signature(sig)180zeros = [i+1 for i,a in enumerate(sig.sig) if a > 0] # for keeping track of the numbered half-edges, we remember the index181z = sig.z182poles = [i+1 for i,a in enumerate(sig.sig) if a < 0]183p = sig.p184marked_points = [i+1 for i,a in enumerate(sig.sig) if a == 0]185mp = len(marked_points)186g = sig.g187orders = {i + 1: ord for i, ord in enumerate(sig.sig)}188189n = sig.n190191# As every g=0 top component needs at least one pole, the bottom max depends on this:192if p > 0:193g_bot_max = g194g_top_min = 0195pole_ind = [0,1]196else:197g_bot_max = g - 1198g_top_min = 1199pole_ind = [0] # poles don't need to be distributed...200201found = []202203for bot_comp_len in range(1,z+mp+1):204# every component on bottom level needs at least one zero or two marked points205# (in case it's genus 0 and has only one edge with a double pole going up...)206# Therefore, we split the zeros into top and bottom and distribute these:207for parts in itertools.chain(multiset_partitions(zeros,2), iter([[zeros,[]]])):208for i in [0,1]:209if mp > 0 or len(parts[i]) >= bot_comp_len:210bottom_zeros = parts[i]211top_zeros = parts[1-i]212else:213continue214# if there are no marked points, every component needs a zero215# otherwise we are more flexible (this could be improved, but is good enoug for now)216if mp == 0:217bot_zero_gen = _distribute_fully(bottom_zeros,bot_comp_len)218else:219bot_zero_gen = _distribute_points(bottom_zeros,bot_comp_len)220for distr_bot_zeros in bot_zero_gen:221# partition genus into top, bottom and graph:222for total_g_bot in range(g_bot_max+1):223for total_g_top in range(g_top_min, g - total_g_bot + 1):224total_g_graph = g - total_g_bot - total_g_top225# partition bottom genus into components226for distr_bot_g in _distribute_part_ordered(total_g_bot,bot_comp_len):227# first test:228# orders on each component must add up to 2g-2, from now on only poles get229# added and we need at least one edge going up (adding at least a pole of order 2)230# So if sum(zeros) < 2g, we're screwed231if not all(sum(orders[bz] for bz in distr_bot_zeros[c])232>= 2*distr_bot_g[c] for c in range(bot_comp_len)):233continue234# start distributing poles, first between top and bottom:235for pole_parts in itertools.chain(multiset_partitions(poles,2), iter([[poles,[]]])):236for ip in pole_ind: # no poles => nothing to distribute237bottom_poles = pole_parts[ip]238top_poles = pole_parts[1-ip]239# poles on top necessary for g=0 components240if total_g_top == 0 and len(top_poles) == 0:241continue242# poles are optional (i.e. not every vertex needs a pole)243for distr_bot_poles in _distribute_points(bottom_poles,bot_comp_len):244# we save the "space" left to distribute among poles from half-edges:245spaces_bot = [(-sum(orders[tz] for tz in distr_bot_zeros[c])246- sum(orders[tp] for tp in distr_bot_poles[c])247+ 2*distr_bot_g[c] - 2) for c in range(bot_comp_len)]248# retest: each space must be at least -2249if not all(s <= -2 for s in spaces_bot):250continue251# iterate over top components252for top_comp_len in range(1,total_g_top+len(top_poles)+1):253# now we know all vertices and the genus distribution, we know the number of edges:254num_of_edges = top_comp_len + bot_comp_len + total_g_graph - 1255# "global" condition on bottom:256# for each edge there will be at least another pole of order 2257if (sum(sum(orders[bz] for bz in distr_bot_zeros[c])258+ sum(orders[bp] for bp in distr_bot_poles[c])259for c in range(bot_comp_len))260- 2*num_of_edges < 2*total_g_bot - 2*bot_comp_len):261continue262# distribute genus and poles263for distr_top_g in _distribute_part_ordered(total_g_top,top_comp_len):264for distr_top_poles in _distribute_points(top_poles,top_comp_len):265# test: if orders add up to more than 2g-2 this won't work266# (effectively this should only concern g=0 components here...)267if not all(sum(orders[tp] for tp in distr_top_poles[c])268<= 2*distr_top_g[c] - 2269for c in range(top_comp_len)):270continue271# distribute remaining zeros optionally272for distr_top_zeros in _distribute_points(top_zeros,top_comp_len):273# again, we record the spaces:274spaces_top = [-sum(orders[tp] for tp in distr_top_poles[c])275- sum(orders[tz] for tz in distr_top_zeros[c])276+ 2*distr_top_g[c] - 2277for c in range(top_comp_len)] # yapf: disable278# retest:279if not all(s >= 0 for s in spaces_top):280continue281# We distribute the edges together with their prongs/poleorders282# before we check for connectednes283# We start on bottom level, because there each half-edge284# comes with a mandatory pole:285for half_edges_bot, orders_he in _place_legs_on_bot(spaces_bot, num_of_edges, n+num_of_edges):286# note for the numbering of half-edges:287# * the HE on bot are numbered n+num_of_edges+1,...,n+2*num_of_edges288# * the HE on top are numbered the same for now, i.e. edges are (l,l) ,289# (and will be renumbered n+1,...,n+num_of_edges in a moment)290for half_edges_top in _place_legs_on_top(spaces_top,orders_he):291# check if graph is connected!292# We create the associated sage graph293# this doesn't have multiple edges, but we only294# care about connectedness295# For this, we build a dictionary {leg: comp bottom}296edge_dict = {}297for j, c in enumerate(half_edges_bot):298for l in c:299# note that the vertices of G will be numbered300# 0,...,top_comp_len+bot_comp_len-1301# so the bot components are numbered by their index302# offset by top_comp_len303edge_dict[l] = j + top_comp_len304# add in the orders for top edges (note the offset)305orders_he[l-num_of_edges] = -2 - orders_he[l]306G = Graph([(j,edge_dict[l]) for j,c in enumerate(half_edges_top)307for l in c], multiedges=False)308if not G.is_connected():309continue310genera = distr_top_g+distr_bot_g311# Distribute order 0 marked points312for distr_mp in _distribute_points(marked_points,len(genera)):313# combine nested lists of legs:314legs = ([distr_top_zeros[c] + distr_top_poles[c]315+ distr_mp[c]316# renumber top HE317+ [l-num_of_edges for l in half_edges_top[c]]318for c in range(top_comp_len)] # top component319+ [distr_bot_zeros[c] + distr_bot_poles[c]320+ distr_mp[c+top_comp_len] # offset321+ half_edges_bot[c]322for c in range(bot_comp_len)] # bot component323) # yapf: disable324# check stability:325if any(len(ls) < 3 for c,ls in enumerate(legs)326if genera[c] == 0):327continue328lg_data = (genera, legs,329[(l,l+num_of_edges) for l in range(n+1,n+num_of_edges+1)],330_merge_dicts(orders,orders_he), # orders as dict331[0]*top_comp_len + [-1]*bot_comp_len, # levels332# True # quiet333)334LG = admcycles.diffstrata.levelgraph.LevelGraph(*lg_data)335if LG.checkadmissible(quiet=True):336if LG.is_legal(quiet=True):337found.append(LG)338else:339# this should not happen!340print("Not admissible(!): ", LG)341found = list(set(found)) # remove duplicates342return found343344345def bic_alt(sig):346"""347The BICs of the stratum sig up to isomorphism of LevelGraphs.348349This should not be used directly, use a GeneralisedStratum or Stratum350instead to obtain EmbeddedLevelGraphs and the correct isomorphism classes.351352Args:353sig (tuple): signature tuple354355Returns:356list: list of LevelGraphs.357"""358return isom_rep(bic_alt_noiso(sig)) # remove duplicates up to iso359360361def _merge_dicts(x,y):362z = x.copy()363z.update(y)364return z365366367def _place_legs_on_bot(space,num_of_points,start):368## iterator distributing n legs with pole orders on (bottom) components according to space (each pole order is at least -2)369## here space is a list of 2g-2-stuff we already placed on each bottom component370## return a pair (pointlist,orderdict) where371## * pointlist is a nested list of points (numbered start+1,...,n)372## * orderdict is a dictionary leg number : order373legal_splits = []374distr = [] # list to hold the current distribution375376def split(n):377## distribute n points onto space and add to distr378# note that space and distr are accessed from back to front...379if n==0:380# done381# correct signs and reverse order382legal_splits.append([[-a for a in c] for c in reversed(distr)])383return384else:385if not space: # check if empty386return387current = space.pop()388remaining_comp = len(space)389# there is a sign issue here: partitions are of positive integers, our pole orders are negative390# each leg has pole order at least 2, so we only list these partitions391# moreover, we may place at most n-remaining_comp points on this component392possibilities = sage_part(-current,min_part=2,max_length=n-remaining_comp).list()393if possibilities: # check if non-empty394for possibility in possibilities:395distr.append(possibility)396# recursion397split(n-len(possibility))398# undo damage399distr.pop()400space.append(current)401# generate splits:402split(num_of_points)403# we now convert the generated lists (of pole orders) into the format: nested list of numbered legs404# Note:405# * the legs are numbered starting from start+1 to start+num_of_points+1406# * the orders are saved in the list of dictionaries edge_orders_bot407for dist in legal_splits:408order_dic = {}409p = start + 1410for c in range(len(dist)):411for a in range(len(dist[c])):412order_dic[p] = dist[c][a]413dist[c][a] = p414p += 1415yield (dist, order_dic)416417418def _place_legs_on_top(space,orders_bot):419## iterator distributing n legs with zero orders (determined by their friends on the bottom component)420## onto the top components according to space421## here space is a list of 2g_comp-2-stuff we already placed on each top component422## return a pointlist (numbered according to keys of orders_bot for now...)423legal_splits = []424distr = [[] for _ in space] # list to hold current distribution425# we sort the points by order, as the ones with the highest order are the hardest to place426# note that this is actually reverse sorted, as the top order is -2-bottom order, but this427# is good, as we want to manipulate the list from the end428ordered_keys = [k for k,v in reversed(sorted(orders_bot.items(), key=lambda o: o[1]))]429430def splits(keys):431# distribute the points (keys) onto spaces432if not keys:433# done if we hit all components and all spaces are 0434if all(distr) and all(s == 0 for s in space):435legal_splits.append([[a for a in c] for c in distr])436return437else:438# check if there are enough points left439remaining_comp = len([hit for hit in distr if not hit]) # components that don't have a point yet440if remaining_comp > len(keys):441return442current = keys.pop()443current_order = -2 - orders_bot[current]444# try to place current on all components445for i in range(len(space)):446if space[i] >= current_order:447space[i] -= current_order448distr[i].append(current)449splits(keys) # recursion450# undo changes:451space[i] += current_order452distr[i].pop()453keys.append(current)454# generate splits:455splits(ordered_keys)456return legal_splits457458459def _distribute_fully(points, n):460## iterator distributing list of points onto n components, where each component gets at least one point461## return a nested list of points462# generate partitions into n subsets of permuted_points:463for part in multiset_partitions(points, n):464# we need to consider all permutations (multiset_partitions sorts!)465for permuted_points in permutations(part):466yield list(permuted_points) # permutations give tuples...467468469def _b_ary_gen(x, b, n):470"""471generator for reverse b-ary integers x of length n472"""473for _ in itertools.repeat(None, n):474r = x % b475x = (x - r) // b476yield r477478479def _distribute_points(points,n):480## iterator distributing list of points onto n components (some might not receive any)481## return a nested list of points482l = len(points)483# n^l possibilities:484for i in range(n**l):485point_list = [[] for j in range(n)]486# n-ary representation of i tells us where the points should go487for pos,d in enumerate(_b_ary_gen(i,n,l)):488point_list[d].append(points[pos])489yield point_list490491492def _distribute_part_ordered(g,n):493## iterator of partitions (g_1,...,g_k) of g of length k <= n distributed onto n points494## return a list of length n (that sums to g)495if n < g:496maxi = n497else:498maxi = None499for part_dict in partitions(g,m=maxi):500# partitions are dictionaries {g_i : multiplicity}501part = []502for k in part_dict.keys():503part += [k]*part_dict[k]504# fill up with zeros:505part += (n-len(part))*[0]506yield part507# we do not have to permute if everything else is permuted....508# for perm_part in set(permutations(part)):509# yield perm_part510511512def isom_rep(L):513"""514Return a list of representatives of isomorphism classes of L.515516TODO: optimise!517"""518dist_list = []519for g in L:520if all(not g.is_isomorphic(h) for h in dist_list):521dist_list.append(g)522return dist_list523524525def comp_list(L, H):526r"""527Compare two lists of LevelGraphs (up to isomorphism).528529Returns a tuple: (list L without H, list H without L)530"""531return ([g for g in L if not any(g.is_isomorphic(h) for h in H)],532[g for g in H if not any(g.is_isomorphic(h) for h in L)])533534535def test_bic_algs(sig_list=None):536"""537Compare output of bics and bic_alt.538539EXAMPLES::540541sage: from admcycles.diffstrata import *542sage: test_bic_algs() # long time (45 seconds) # skip, not really needed + long # doctest: +SKIP543(1, 1): ([], [])544(1, 1, 0, 0, -2): ([], [])545(2, 0, -2): ([], [])546(1, 0, 0, 1): ([], [])547(1, -2, 2, 1): ([], [])548(2, 2): ([], [])549550sage: test_bic_algs([(0,0),(2,1,1)]) # long time (50 seconds) # doctest: +SKIP551(0, 0): ([], [])552(2, 1, 1): ([], [])553554sage: test_bic_algs([(1,0,-1),(2,),(4,),(1,-1)])555WARNING! This is not the normal BIC generation algorithm!556Only use if you know what you're doing!557(1, 0, -1): ([], [])558WARNING! This is not the normal BIC generation algorithm!559Only use if you know what you're doing!560(2,): ([], [])561WARNING! This is not the normal BIC generation algorithm!562Only use if you know what you're doing!563(4,): ([], [])564WARNING! This is not the normal BIC generation algorithm!565Only use if you know what you're doing!566(1, -1): ([], [])567"""568if sig_list is None:569sig_list = [(1,1),(1,1,0,0,-2),(2,0,-2),(1,0,0,1),(1,-2,2,1),(2,2)]570for sig in sig_list:571Sig = Signature(sig)572print("%r: %r" % (Sig.sig, comp_list(bics(Sig.g,Sig.sig),bic_alt(Sig))))573574575