from __future__ import absolute_import, print_function
from six.moves import range
from copy import deepcopy
import itertools
import pickle
from sage.arith.all import factorial, bernoulli, gcd, lcm
from sage.combinat.all import Subsets
from sage.functions.other import floor, ceil, binomial
from sage.groups.perm_gps.permgroup import PermutationGroup
from sage.matrix.constructor import matrix
from sage.matrix.special import block_matrix
from sage.modules.free_module_element import vector
from sage.combinat.all import Permutations, Subsets, Partitions, Partition
from sage.functions.other import floor, ceil, binomial
from sage.arith.all import factorial, bernoulli, gcd, lcm, multinomial
from sage.misc.cachefunc import cached_function
from sage.misc.misc_c import prod
from sage.modules.free_module_element import vector
from sage.rings.all import Integer, Rational, QQ, ZZ
from . import DR
initialized = True
g = Integer(0)
n = Integer(3)
def reset_g_n(gloc, nloc):
gloc = Integer(gloc)
nloc = Integer(nloc)
if gloc < 0 or nloc < 0:
raise ValueError
global g, n
g = gloc
n = nloc
Hdatabase = {}
from .stable_graph import StableGraph, GraphIsom
stgraph = StableGraph
def trivgraph(g, n):
"""
Return the trivial graph in genus `g` with `n` markings.
EXAMPLES::
sage: from admcycles.admcycles import trivgraph
sage: trivgraph(1,1)
[1] [[1]] []
"""
return stgraph([g], [list(range(1, n + 1))], [])
def list_strata(g,n,r):
if r==0:
return [stgraph([g],[list(range(1, n+1))],[])]
Lold=list_strata(g,n,r-1)
Lnew=[]
for G in Lold:
for v in range(G.num_verts()):
Lnew+=G.degenerations(v)
if not Lnew:
return []
Ldupfree=[Lnew[0]]
count=1
for i in range(1, len(Lnew)):
newgraph=True
for j in range(count):
if Lnew[i].is_isomorphic(Ldupfree[j]):
newgraph=False
break
if newgraph:
Ldupfree+=[Lnew[i]]
count+=1
return Ldupfree
@cached_function
def degeneration_graph(g, n, rmax=None):
if rmax is None:
rmax = 3 *g-3 +n
if rmax>3 *g-3 +n:
rmax = 3 *g-3 +n
try:
deglist, invlist = degeneration_graph.cached(g,n)
r0 = 3 *g-3 +n
except KeyError:
for r0 in range(3*g - 3 + n, -2, -1):
try:
deglist, invlist = degeneration_graph.cached(g,n,r0)
break
except KeyError:
pass
if r0 == -1:
deglist=[[[stgraph([g], [list(range(1, n+1))],[]), (), set(), set()]]]
invlist=[[[deglist[0][0][0].invariant()], [-1, 0]]]
for r in range(r0+1, rmax+1):
deglist+=[[]]
templist=[]
for i in range(len(deglist[r-1])):
Gr=deglist[r-1][i][0]
deg_Gr=Gr.degenerations()
for dGr in deg_Gr:
lis=tuple(range(len(deglist[r-1][i][1])))
templist+=[[dGr, dGr.halfedges(), set([(i,lis)]), set([])]]
def inva(o):
return o[0].invariant()
templist.sort(key=inva)
current_invariant=None
tempbdry=[]
tempinv=[]
count=0
for Gr in templist:
inv=Gr[0].invariant()
if inv != current_invariant:
tempinv+=[inv]
tempbdry+=[count-1]
current_invariant=inv
count+=1
tempbdry+=[len(templist)-1]
count=0
invlist+=[[[],[]]]
for i in range(len(tempinv)):
isomcl=[]
for j in range(tempbdry[i]+1, tempbdry[i+1]+1):
Gr=templist[j][0]
new_graph=True
for k in range(len(isomcl)):
Gr2=isomcl[k]
ans, Iso = Gr.is_isomorphic(Gr2[0], certificate=True)
if ans:
new_graph=False
dicl = Iso[1]
upstairs=list(templist[j][2])[0][0]
lisnew=tuple((Gr2[1].index(dicl[h]) for h in deglist[r-1][upstairs][1]))
Gr2[2].add((upstairs, lisnew))
deglist[r-1][upstairs][3].add((count+k,lisnew))
break
if new_graph:
isomcl+=[templist[j]]
upstairs=list(templist[j][2])[0][0]
lisnew=list(templist[j][2])[0][1]
deglist[r-1][upstairs][3].add((count+len(isomcl)-1, lisnew))
deglist[r]+=isomcl
invlist[r][0]+=[isomcl[0][0].invariant()]
invlist[r][1]+=[count-1]
count+=len(isomcl)
invlist[r][1]+=[count-1]
if rmax > r0 and r0 != -1:
degeneration_graph.cache.pop(((g,n,r0),()))
return (deglist,invlist)
def deggrfind(Gr,markdic=None):
g = Gr.g()
n = len(Gr.list_markings())
r = Gr.num_edges()
if markdic is not None:
Gr_rename = Gr.copy(mutable=True)
Gr_rename.rename_legs(markdic)
else:
markdic={i:i for i in range(1, n+1)}
Gr_rename=Gr
Gr_rename.set_immutable()
Inv=Gr_rename.invariant()
(deglist,invlist) = degeneration_graph(g,n,r)
InvIndex=invlist[r][0].index(Inv)
for i in range(invlist[r][1][InvIndex]+1, invlist[r][1][InvIndex+1]+1):
ans, Isom = Gr_rename.is_isomorphic(deglist[r][i][0], certificate=True)
if ans:
dicl={l:Isom[1][markdic[l]] for l in markdic}
dicl.update({l:Isom[1][l] for l in Gr.halfedges()})
return (r,i,Isom[0],dicl)
print('Help, cannot find ' + repr(Gr))
print((g,n,r))
print((Inv,list_strata(2, 2, 0)[0].invariant()))
def Astructures(Gamma,A,identGamma=None,identA=None):
if A.num_edges() > Gamma.num_edges():
return []
g = Gamma.g()
mark = Gamma.list_markings()
n=len(mark)
if g != A.g():
print('Error, Astructuring graphs of different genera')
print(Gamma)
print(A)
raise ValueError('A very specific bad thing happened')
return []
if set(mark) != set(A.list_markings()):
print('Error, Astructuring graphs with different markings')
return []
markdic={mark[i-1]:i for i in range(1, n+1)}
(deglist,invlist) = degeneration_graph(g, n, Gamma.num_edges())
if identA is None:
(rA,iA,dicvA,diclA)=deggrfind(A,markdic)
else:
(rA,iA,dicvA,diclA)=identA
if identGamma is None:
(rG,iG,dicvG,diclG)=deggrfind(Gamma,markdic)
else:
(rG,iG,dicvG,diclG)=identGamma
morphisms=set([(iG,tuple(range(len(deglist[rG][iG][1]))))])
for r in range(rG,rA,-1):
new_morphisms=set()
for mor in morphisms:
for (tar,psi) in deglist[r][mor[0]][2]:
composition=tuple((mor[1][j] for j in psi))
new_morphisms.add((tar,composition))
morphisms=new_morphisms
new_morphisms = set([mor for mor in morphisms if mor[0]==iA])
morphisms=new_morphisms
Auto = GraphIsom(deglist[rG][iG][0],deglist[rG][iG][0])
new_morphisms=set()
for (Autov,Autol) in Auto:
for mor in morphisms:
composition=tuple((Autol[deglist[rG][iG][1][j]] for j in mor[1]))
new_morphisms.add((mor[0],composition))
morphisms=new_morphisms
finalmorphisms=[]
dicmarkings={h:h for h in mark}
Ahalfedges=A.halfedges()
Ghalfedges=Gamma.halfedges()
dicAtild={deglist[rA][iA][1][i]:i for i in range(len(deglist[rA][iA][1]))}
diclGinverse={diclG[h]:h for h in Gamma.halfedges()}
for (targ,mor) in morphisms:
dicl={ h: diclGinverse[mor[dicAtild[diclA[h]]]] for h in Ahalfedges}
dicl.update(dicmarkings)
if A.num_verts() == 1:
dicv = {ver: 0 for ver in range(Gamma.num_verts())}
else:
dicv = {Gamma.vertex(dicl[h]): A.vertex(h) for h in dicl}
remaining_vertices = set(range(Gamma.num_verts())) - set(dicv)
remaining_edges = set([e for e in Gamma.edges(copy=False) if e[0] not in dicl.values()])
current_vertices = set(dicv)
while remaining_vertices:
newcurrent_vertices=set()
for e in deepcopy(remaining_edges):
if Gamma.vertex(e[0]) in current_vertices:
vnew=Gamma.vertex(e[1])
remaining_edges.remove(e)
if vnew not in current_vertices:
dicv[vnew]=dicv[Gamma.vertex(e[0])]
remaining_vertices.discard(vnew)
newcurrent_vertices.add(vnew)
continue
if Gamma.vertex(e[1]) in current_vertices:
vnew=Gamma.vertex(e[0])
remaining_edges.remove(e)
if vnew not in current_vertices:
dicv[vnew]=dicv[Gamma.vertex(e[1])]
remaining_vertices.discard(vnew)
newcurrent_vertices.add(vnew)
current_vertices=newcurrent_vertices
finalmorphisms+=[(dicv,dicl)]
return finalmorphisms
def common_degenerations(G1,G2,modiso=False,rename=False):
g=G1.g()
mkings=G1.list_markings()
n=len(mkings)
switched = False
if G1.num_edges() > G2.num_edges():
temp = G1
G1 = G2
G2 = temp
switched = True
if rename:
markdic={mkings[i]:i+1 for i in range(n)}
renamedicl1={l:l+n+1 for l in G1.leglist()}
renamedicl2={l:l+n+1 for l in G2.leglist()}
renamedicl1.update(markdic)
renamedicl2.update(markdic)
G1 = G1.copy()
G2 = G2.copy()
G1.rename_legs(renamedicl1)
G2.rename_legs(renamedicl2)
G1.set_immutable()
G2.set_immutable()
(r1,i1,dicv1,dicl1)=deggrfind(G1)
(r2,i2,dicv2,dicl2)=deggrfind(G2)
(deglist,invlist)=degeneration_graph(g,n,r1+r2)
commdeg=[]
descendants1=set([i1])
for r in range(r1+1, r2+1):
descendants1=set([d[0] for i in descendants1 for d in deglist[r-1][i][3]])
descendants2=set([i2])
for r in range(r2, r1+r2+1):
for i in descendants1.intersection(descendants2):
Gamma=deglist[r][i][0]
Gammafind=(r,i,{j:j for j in range(Gamma.num_verts())}, {l:l for l in Gamma.leglist()})
G1structures=Astructures(Gamma,G1,identGamma=Gammafind,identA=(r1,i1,dicv1,dicl1))
G2structures=Astructures(Gamma,G2,identGamma=Gammafind,identA=(r2,i2,dicv2,dicl2))
if modiso is True:
AutGamma=GraphIsom(Gamma,Gamma)
AutGammatild=[(dicv,{dicl[l]:l for l in dicl}) for (dicv,dicl) in AutGamma]
tempdeg=[]
numlegs=len(Gamma.leglist())
for (dv1,dl1) in G1structures:
for (dv2,dl2) in G2structures:
numcoveredlegs=len(set(list(dl1.values())+list(dl2.values())))
if numcoveredlegs==numlegs:
if switched:
if rename:
tempdeg.append((Gamma,dv2,{l:dl2[renamedicl2[l]] for l in renamedicl2},dv1,{l:dl1[renamedicl1[l]] for l in renamedicl1}))
else:
tempdeg.append((Gamma,dv2,dl2,dv1,dl1))
else:
if rename:
tempdeg.append((Gamma,dv1,{l:dl1[renamedicl1[l]] for l in renamedicl1},dv2,{l:dl2[renamedicl2[l]] for l in renamedicl2}))
else:
tempdeg.append((Gamma,dv1,dl1,dv2,dl2))
if modiso is True:
while tempdeg:
(Gamma,dv1,dl1,dv2,dl2)=tempdeg.pop(0)
commdeg.append((Gamma,dv1,dl1,dv2,dl2))
for (dicv,dicltild) in AutGammatild:
try:
tempdeg.remove((Gamma,{v: dv1[dicv[v]] for v in dicv},{l: dicltild[dl1[l]] for l in dl1},{v: dv2[dicv[v]] for v in dicv},{l: dicltild[dl2[l]] for l in dl2}))
except:
pass
else:
commdeg+=tempdeg
if r<r1+r2:
descendants1=set([d[0] for i in descendants1 for d in deglist[r][i][3]])
descendants2=set([d[0] for i in descendants2 for d in deglist[r][i][3]])
return commdeg
class Gstgraph(object):
def __init__(self,G,gamma,vertact,legact,character,hdata=None):
self.G=G
self.gamma=gamma
self.vertact=vertact
self.legact=legact
self.character=character
if hdata is None:
self.hdata=self.hurwitz_data()
else:
self.hdata=hdata
def copy(self, mutable=True):
G = Gstgraph.__new__(Gstgraph)
G.G = self.G
G.gamma = self.gamma.copy(mutable=mutable)
G.vertact = self.vertact.copy()
G.legact = self.legact.copy()
G.character = self.character.copy()
return G
def __repr__(self):
return repr(self.gamma)
def __deepcopy__(self,memo):
raise RuntimeError("do not deepcopy")
return Gstgraph(self.G,deepcopy(self.gamma),deepcopy(self.vertact),deepcopy(self.legact),deepcopy(self.character),hdata=deepcopy(self.hdata))
def dim(self,v=None):
if v is None:
return self.quotient_graph().dim()
return self.extract_vertex(v).dim()
def rename_legs(self,di):
if not self.gamma.is_mutable():
self.gamma = self.gamma.copy()
self.gamma.rename_legs(di)
temp_legact={}
temp_character={}
for k in self.legact:
temp_legact[(k[0],di.get(k[1],k[1]))]=di.get(self.legact[k],self.legact[k])
for k in self.character:
temp_character[di.get(k,k)]=self.character[k]
def vstabilizer(self,i):
gen=[]
for g in self.G:
if self.vertact[(g,i)]==i:
gen+=[g]
try:
return self.G.ambient_group().subgroup(gen)
except AttributeError:
return self.G.subgroup(gen)
def lstabilizer(self,j):
try:
return self.G.ambient_group().subgroup([self.character[j][0]])
except AttributeError:
return self.G.subgroup([self.character[j][0]])
def to_prodHclass(self):
return prodHclass(stgraph([self.gamma.g()],[list(self.gamma.list_markings())],[]),[self.to_decHstratum()])
def to_decHstratum(self):
dicv={}
dicvinv={}
dicl={}
quotgr=self.quotient_graph(dicv, dicvinv, dicl)
spaces = {v: (self.gamma.genera(dicvinv[v]), HurwitzData(self.vstabilizer(dicvinv[v]),[self.character[l] for l in quotgr.legs(v, copy=False)])) for v in range(quotgr.num_verts())}
masterdicl={}
for v in range(quotgr.num_verts()):
n=1
gord=spaces[v][1].G.order()
tGraph=trivGgraph(*spaces[v])
vinv=dicvinv[v]
legreps=[]
for l in quotgr.legs(v, copy=False):
for g in self.G:
if self.legact[(g,l)] in self.gamma.legs(vinv, copy=False):
legreps.append(self.legact[(g,l)])
break
(quotrep,di)=leftcosetaction(self.G,spaces[v][1].G)
for l in legreps:
for g in spaces[v][1].G:
for gprime in quotrep:
masterdicl[ self.legact[(gprime*g,l)]] = tGraph.legact[(g,n)]
n+=floor(QQ(gord)/self.character[l][1])
vertdata=[]
for w in range(self.gamma.num_verts()):
a=dicv[w]
wdicl = {l:masterdicl[l] for l in self.gamma.legs(w, copy=False)}
vertdata.append([a,wdicl])
return decHstratum(deepcopy(self.gamma),spaces,vertdata)
def extract_vertex(self,i):
G_new=self.vstabilizer(i)
lgs = self.gamma.legs(i, copy=True)
egs=self.gamma.edges_between(i,i)
gamma_new=stgraph([self.gamma.genera(i)],[lgs],egs)
vertact_new={}
for g in G_new:
vertact_new[(g,0)]=0
legact_new={}
for g in G_new:
for j in lgs:
legact_new[(g,j)]=self.legact[(g,j)]
character_new={}
for j in lgs:
character_new[j]=deepcopy(self.character[j])
return Gstgraph(G_new,gamma_new,vertact_new,legact_new,character_new)
def equivariant_glue_vertex(self,i,Gr,divGr={},divs={},dil={}):
divGr.clear()
divs.clear()
dil.clear()
for j in range(self.gamma.num_verts()):
divs[j]=j
G=self.G
H=Gr.G
(L,di)=leftcosetaction(G,H)
oldvertno = self.gamma.num_verts() - len(L)
Gr_mod = Gr.copy()
m = max(self.gamma._maxleg, Gr.gamma._maxleg)
a = set().union(*Gr.gamma.legs(copy=False))
b = set(self.gamma.legs(i, copy=False))
e = a-b
augment_dic={}
for l in e:
m+=1
augment_dic[l]=m
Gr_mod.rename_legs(augment_dic)
legdiclist={}
orbi={}
oldlegsi = self.gamma.legs(i, copy=False)
for g in L:
orbi[g]=self.vertact[(g,i)]
Gr_transl = Gr_mod.copy()
transl_dic={l:self.legact[(g,l)] for l in oldlegsi}
Gr_transl.rename_legs(transl_dic)
divGr_temp={}
divs_temp={}
dil_temp={}
self.gamma.glue_vertex(divs[self.vertact[(g,i)]], Gr_transl.gamma, divGr=divGr_temp, divs=divs_temp, dil=dil_temp)
divs.pop(self.vertact[(g,i)])
for j in divs:
divs[j]=divs_temp[divs[j]]
legdiclist.update({(g,j):dil_temp[j] for j in dil_temp})
for j in e:
for g1 in range(len(L)):
for g2 in range(len(L)):
for h in H:
self.legact[(L[g2]*h*L[g1].inverse(),legdiclist[(L[g1],augment_dic[j])])]=legdiclist[(L[g2],augment_dic[Gr.legact[(h,j)]])]
self.character[legdiclist[(L[g1],augment_dic[j])]]=(L[g1]*Gr.character[j][0]*L[g1].inverse(),Gr.character[j][1],Gr.character[j][2])
self.vertact_reconstruct()
dil.update({j:legdiclist[(L[0],augment_dic[j])] for j in e})
divGr.update({j:oldvertno+j for j in range(Gr.gamma.num_verts())})
def quotient_graph(self, dicv={}, dicvinv={}, dicl={}):
r"""
Computes the quotient graph of self under the group action.
dicv, dicl are dictionaries that record the quotient map of vertices and legs,
dicvinv is a dictionary giving some section of dicv (going from vertices of
quotient to vertices of self).
EXAMPLES::
sage: from admcycles.admcycles import trivGgraph, HurData
sage: G = PermutationGroup([(1, 2)])
sage: H = HurData(G, [G[1], G[1], G[1], G[1]])
sage: tG = trivGgraph(1, H); tG.quotient_graph()
[0] [[1, 2, 3, 4]] []
sage: H = HurData(G, [G[1], G[1], G[1], G[1], G[0]])
sage: tG = trivGgraph(1, H); tG.quotient_graph()
[0] [[1, 2, 3, 4, 5]] []
sage: H = HurData(G, [G[1], G[1], G[1], G[1], G[1]])
sage: tG = trivGgraph(1, H); tG.quotient_graph() # no such cover exists (RH formula)
Traceback (most recent call last):
...
ValueError: Riemann-Hurwitz formula not satisfied in this Gstgraph.
TESTS::
sage: from admcycles.admcycles import trivGgraph, HurData
sage: G = PermutationGroup([(1, 2, 3)])
sage: H = HurData(G, [G[1]])
sage: tG = trivGgraph(2, H); tG.quotient_graph()
[1] [[1]] []
sage: H = HurData(G, [G[1] for i in range(6)])
sage: tG = trivGgraph(1, H); tG.quotient_graph() # quotient vertex would have genus -1
Traceback (most recent call last):
...
ValueError: Riemann-Hurwitz formula not satisfied in this Gstgraph.
"""
dicv.clear()
dicvinv.clear()
dicl.clear()
vertlist = list(range(self.gamma.num_verts()))
leglist=[]
for v in vertlist:
leglist += self.gamma.legs(v, copy=False)
countv=0
for v in deepcopy(vertlist):
if v in vertlist:
dicvinv[countv]=v
for g in self.G:
if self.vertact[(g,v)] in vertlist:
dicv[self.vertact[(g,v)]]=countv
vertlist.remove(self.vertact[(g,v)])
countv+=1
for l in deepcopy(leglist):
if l in leglist:
for g in self.G:
if self.legact[(g,l)] in leglist:
dicl[self.legact[(g,l)]]=l
leglist.remove(self.legact[(g,l)])
quot_leg=[[] for j in range(countv)]
legset=set(dicl.values())
for l in legset:
quot_leg[dicv[self.gamma.vertex(l)]]+=[l]
quot_genera=[]
for v in range(countv):
Gv=self.vstabilizer(dicvinv[v]).order()
b=sum([1-QQ(1)/(self.character[l][1]) for l in quot_leg[v]])
qgenus = ((2 *self.gamma.genera(dicvinv[v])-2)/QQ(Gv)+2 -b)/QQ(2)
if not (qgenus.is_integer() and qgenus>=0):
raise ValueError('Riemann-Hurwitz formula not satisfied in this Gstgraph.')
quot_genera+=[ZZ(qgenus)]
quot_edges=[]
for e in self.gamma.edges(copy=False):
if e[0] in legset:
quot_edges+=[(e[0],dicl[e[1]])]
quot_graph = stgraph(quot_genera,quot_leg, quot_edges, mutable=True)
quot_graph.tidy_up()
quot_graph.set_immutable()
return quot_graph
def hurwitz_data(self):
quotgr=self.quotient_graph()
marks=list(quotgr.list_markings())
marks.sort()
return HurwitzData(self.G,[self.character[l] for l in marks])
def delta_degree(self,v):
r"""
Gives the degree of the delta-map from the Hurwitz space parametrizing the curve
placed on vertex v to the corresponding stable-maps space.
Note: currently only works for cyclic groups G.
EXAMPLES::
sage: from admcycles.admcycles import trivGgraph, HurData
sage: G = PermutationGroup([(1, 2)])
sage: H = HurData(G, [G[1], G[1], G[1], G[1]])
sage: tG = trivGgraph(1, H); tG.delta_degree(0) # unique cover with 2 automorphisms
1/2
sage: H = HurData(G, [G[1], G[1], G[1], G[1], G[0]])
sage: tG = trivGgraph(1, H); tG.delta_degree(0) # unique cover with 1 automorphism
1
sage: H = HurData(G, [G[1], G[1], G[1], G[1], G[1]])
sage: tG = trivGgraph(1, H); tG.delta_degree(0) # no such cover exists (RH formula)
0
sage: G = PermutationGroup([(1, 2, 3)])
sage: H = HurData(G, [G[1]])
sage: tG = trivGgraph(2, H); tG.delta_degree(0) # no such cover exists (repr. theory)
0
"""
if self.gamma.num_verts() > 1:
return self.extract_vertex(v).delta_degree(0)
try:
quotgr=self.quotient_graph()
except ValueError:
return 0
gprime = quotgr.genera(0)
n=self.G.order()
if (n==2 and len([1 for l in quotgr.legs(0, copy=False) if self.character[l][1]==2])%2==1) or (n>2 and prod([self.character[l][0]**(ZZ(self.character[l][2]).inverse_mod(n)) for l in quotgr.legs(0, copy=False) if self.character[l][1]!=1]) != self.G.one()):
return 0
sigmagcd = ceil(QQ(n)/lcm([self.character[l][1] for l in quotgr.legs(0, copy=False)]))
primfac=set(sigmagcd.prime_factors())
numhom=0
for S in Subsets(primfac):
numhom+=(-1)**len(S)*(QQ(n)/prod(S))**(2 *gprime)
return numhom*prod([QQ(n)/self.character[l][1] for l in quotgr.legs(0, copy=False)])/QQ(n)
def vertact_reconstruct(self):
if self.gamma.num_verts() == 1:
self.vertact={(g,0):0 for g in self.G}
else:
self.vertact={(g,v): self.gamma.vertex(self.legact[(g,self.gamma.legs(v, copy=False)[0])]) for g in self.G for v in range(self.gamma.num_verts())}
def degenerations(self,v):
if self.gamma.num_verts() > 1:
v0=self.extract_vertex(v)
L=v0.degenerations(0)
M = [self.copy() for j in range(len(L))]
for j in range(len(L)):
M[j].equivariant_glue_vertex(v,L[j])
return M
if self.gamma.num_verts() == 1:
dicv={}
dicvinv={}
dicl={}
quot_Graph=self.quotient_graph(dicv=dicv, dicvinv=dicvinv, dicl=dicl)
quot_degen=quot_Graph.degenerations(0)
degen=[]
if self.G.is_cyclic():
G=self.G
n=G.order()
Ggenerators=G.gens()
for ge in Ggenerators:
if ge.order()==n:
sigma=ge
break
GtoNum={}
NumtoG={}
mu=sigma
for j in range(1, n+1):
GtoNum[mu]=j % n
NumtoG[j % n]=mu
mu=mu*sigma
e={}
k={}
nu={}
for dege in quot_degen:
last = dege.edges(copy=False)[dege.num_edges() - 1]
legs = set(dege.leglist())
legs.difference_update(last)
for alpha in legs:
e[alpha]=self.character[alpha][1]
r=floor(GtoNum[self.character[alpha][0]]*e[alpha]/QQ(n))
k[alpha]=(self.character[alpha][2]*r.inverse_mod(e[alpha]))% e[alpha]
nu[alpha]=k[alpha].inverse_mod(e[alpha])
if dege.num_verts() == 2:
I1 = dege.legs(0, copy=True)
last = dege.edges(copy=False)[dege.num_edges() - 1]
I1.remove(last[0])
I2 = dege.legs(1, copy=True)
I2.remove(last[1])
I = I1+I2
n1_min=lcm([e[alpha] for alpha in I1])
n2_min=lcm([e[alpha] for alpha in I2])
for n1_add in range(1, floor(QQ(n)/n1_min + 1)):
for n2_add in range(1, floor(QQ(n)/n2_min + 1)):
n1=n1_add*n1_min
n2=n2_add*n2_min
if n1.divides(n) and n2.divides(n) and lcm(n1,n2)==n:
A1=-floor(sum([QQ(n1)/e[alpha]*nu[alpha] for alpha in I1]))
e_new=floor(QQ(n1)/gcd(n1,A1))
nu1=floor(QQ(A1%n1)/(QQ(n1)/e_new))
nu2=e_new-nu1
k1=nu1.inverse_mod(e_new)
k2=nu2.inverse_mod(e_new)
I1_temp=deepcopy(I1)
I2_temp=deepcopy(I2)
possi={}
if I1:
possi[I1[0]]=[0]
I1_temp.pop(0)
if I2:
possi[I2[0]]=list(range(gcd(n1,QQ(n)/n2)))
I2_temp.pop(0)
else:
if I2:
possi[I2[0]]=[0]
I2_temp.pop(0)
for i in I1_temp:
possi[i]=list(range(QQ(n)/n1))
for i in I2_temp:
possi[i]=list(range(QQ(n)/n2))
mark_dist=itertools.product(*[possi[j] for j in I])
g1=floor((n1*(2 *dege.genera(0)-2)+n1*(sum([1-QQ(1)/e[alpha] for alpha in I1])+1-QQ(1)/e_new))/QQ(2)+1)
g2=floor((n2*(2 *dege.genera(1)-2)+n2*(sum([1-QQ(1)/e[alpha] for alpha in I2])+1-QQ(1)/e_new))/QQ(2)+1)
if g1<0 or g2<0:
continue
new_genera=[g1 for j in range(QQ(n)/n1)]+[g2 for j in range(QQ(n)/n2)]
new_legs=[[] for j in range(QQ(n)/n1+QQ(n)/n2)]
new_legact=deepcopy(self.legact)
new_edges = self.gamma.edges()
new_character=deepcopy(self.character)
m0=self.gamma._maxleg+1
m=self.gamma._maxleg+1
for j in range(QQ(n)/e_new):
new_legs[j % (QQ(n)/n1)]+=[m]
new_legs[(j % (QQ(n)/n2))+(QQ(n)/n1)]+=[m+1]
new_edges+=[(m,m+1)]
new_legact[(sigma,m)]=m0+((m+2 -m0)%(2 *QQ(n)/e_new))
new_legact[(sigma,m+1)]=new_legact[(sigma,m)]+1
new_character[m]=(NumtoG[(QQ(n)/e_new)%n], e_new, k1)
new_character[m+1]=(NumtoG[(QQ(n)/e_new)%n], e_new, k2)
m+=2
for jplus in range(2, n+1):
for m in range(m0, m0 + 2*QQ(n)/e_new):
new_legact[(NumtoG[jplus%n],m)]=new_legact[(sigma,new_legact[(NumtoG[(jplus-1)%n],m)])]
for md in mark_dist:
new_legs_plus=deepcopy(new_legs)
for alpha in range(len(I1)):
for j in range(QQ(n)/e[I[alpha]]):
new_legs_plus[(md[alpha]+j)%(QQ(n)/n1)]+=[self.legact[(NumtoG[j],I[alpha])]]
for alpha in range(len(I1),len(I)):
for j in range(QQ(n)/e[I[alpha]]):
new_legs_plus[(md[alpha]+j)%(QQ(n)/n2)+(QQ(n)/n1)]+=[self.legact[(NumtoG[j],I[alpha])]]
new_Ggraph=Gstgraph(G,stgraph(new_genera,new_legs_plus,new_edges),{},new_legact,new_character,hdata=self.hdata)
new_Ggraph.vertact_reconstruct()
degen+=[new_Ggraph]
if dege.num_verts() == 1:
I = dege.legs(0, copy=True)
last = dege.edges(copy=False)[dege.num_edges() - 1]
I.remove(last[0])
I.remove(last[1])
n0_min=lcm([e[alpha] for alpha in I])
for n0_add in range(1, floor(QQ(n)/n0_min + 1)):
n0=n0_add*n0_min
if n0.divides(n):
for e_new in n0.divisors():
for g0 in [x for x in range(floor(QQ(n)/(2*n0)+1)) if gcd(x,QQ(n)/n0)==1]:
for nu1 in [j for j in range(e_new) if gcd(j,e_new)==1]:
if g0==0 and nu1>QQ(e_new)/2:
continue
nu2=e_new-nu1
k1=Integer(nu1).inverse_mod(e_new)
k2=Integer(nu2).inverse_mod(e_new)
I_temp=deepcopy(I)
possi={}
if I:
possi[I[0]]=[0]
I_temp.pop(0)
for i in I_temp:
possi[i]=list(range(QQ(n)/n0))
mark_dist=itertools.product(*[possi[j] for j in I])
gen0=floor((n0*(2 *dege.genera(0)-2)+n0*(sum([1-QQ(1)/e[alpha] for alpha in I])+2 -QQ(2)/e_new))/QQ(2)+1)
if gen0<0:
continue
new_genera=[gen0 for j in range(QQ(n)/n0)]
new_legs=[[] for j in range(QQ(n)/n0)]
new_legact=deepcopy(self.legact)
new_edges = self.gamma.edges()
new_character=deepcopy(self.character)
m0=self.gamma._maxleg+1
m=self.gamma._maxleg+1
for j in range(QQ(n)/e_new):
new_legs[j % (QQ(n)/n0)]+=[m]
new_legs[(j+g0) % (QQ(n)/n0)]+=[m+1]
new_edges+=[(m,m+1)]
new_legact[(sigma,m)]=m0+((m+2 -m0)%(2 *QQ(n)/e_new))
new_legact[(sigma,m+1)]=new_legact[(sigma,m)]+1
new_character[m]=(NumtoG[(QQ(n)/e_new)%n],e_new, k1)
new_character[m+1]=(NumtoG[(QQ(n)/e_new)%n],e_new, k2)
m+=2
for jplus in range(2, n+1):
for m in range(m0, m0+2*QQ(n)/e_new):
new_legact[(NumtoG[jplus%n],m)]=new_legact[(sigma,new_legact[(NumtoG[(jplus-1)%n],m)])]
for md in mark_dist:
new_legs_plus=deepcopy(new_legs)
for alpha in range(len(I)):
for j in range(QQ(n)/e[I[alpha]]):
new_legs_plus[(md[alpha]+j)%(QQ(n)/n0)]+=[self.legact[(NumtoG[j],I[alpha])]]
new_Ggraph=Gstgraph(G,stgraph(new_genera,new_legs_plus,new_edges),{},new_legact,new_character,hdata=self.hdata)
new_Ggraph.vertact_reconstruct()
degen+=[new_Ggraph]
return degen
else:
print('Degenerations for non-cyclic groups not implemented!')
return []
def equiGraphIsom(Gr1,Gr2):
if Gr1.G != Gr2.G:
return []
Iso = GraphIsom(Gr1.gamma,Gr2.gamma)
GIso=[]
for I in Iso:
Gequiv=True
for g in Gr1.G.gens():
for v in I[0]:
if I[0][Gr1.vertact[(g,v)]] != Gr2.vertact[(g,I[0][v])]:
Gequiv=False
break
for l in I[1]:
if I[1][Gr1.legact[(g,l)]] != Gr2.legact[(g,I[1][l])]:
Gequiv=False
break
for l in I[1]:
if not Gequiv:
break
for j in [t for t in range(Gr1.character[l][1]) if gcd(t,Gr1.character[l][1])==1]:
if Gr1.character[l][0]**j == Gr2.character[I[1][l]][0] and not ((j*Gr1.character[l][2]-Gr2.character[I[1][l]][2])%Gr1.character[l][1])==0:
Gequiv=False
break
if Gequiv:
GIso+=[I]
return GIso
class HurwitzData:
def __init__(self,G,l):
self.G=G
self.l=l
def __eq__(self,other):
if not isinstance(other,HurwitzData):
return False
return (self.G==other.G) and (self.l==other.l)
def __hash__(self):
return hash((self.G,tuple(self.l)))
def __repr__(self):
return repr((self.G,self.l))
def __deepcopy__(self,memo):
return HurwitzData(self.G,deepcopy(self.l))
def nummarks(self):
g=self.G.order()
N=sum([QQ(g)/r[1] for r in self.l])
return floor(N)
def HurData(G,l):
return HurwitzData(G,[(h,h.order(),min(h.order(),1)) for h in l])
def trivGgraph(gen,D):
G=D.G
vertact={}
for g in D.G:
vertact[(g,0)]=0
n=0
legact={}
character={}
for e in D.l:
H=G.subgroup([e[0]])
(L,dic)=leftcosetaction(G,H)
for g in G:
for j in range(len(L)):
legact[(g,j+1+n)] = dic[(g,j)]+1+n
for j in range(len(L)):
character[j+1+n]=(L[j]*e[0]*L[j].inverse(), e[1],e[2])
n+=len(L)
gamma=stgraph([gen],[list(range(1, n+1))],[])
return Gstgraph(G,gamma,vertact,legact,character,hdata=D)
@cached_function
def list_Hstrata(g,H,r):
if r==0:
return [trivGgraph(g,H)]
Lold=list_Hstrata(g,H,r-1)
Lnew=[]
for Gr in Lold:
for v in range(Gr.gamma.num_verts()):
Lnew+=Gr.degenerations(v)
if not Lnew:
return []
Ldupfree=[Lnew[0]]
count=1
for i in range(1, len(Lnew)):
newgraph=True
for j in range(count):
if equiGraphIsom(Lnew[i],Ldupfree[j]):
newgraph=False
break
if newgraph:
Ldupfree+=[Lnew[i]]
count+=1
return Ldupfree
@cached_function
def list_quotgraphs(g,H,r,localize=True):
Hgr=list_Hstrata(g,H,r)
result=[]
for gr in Hgr:
dicv={}
dicvinv={}
dicl={}
qgr=gr.quotient_graph(dicv,dicvinv,dicl)
if localize:
dgfind=deggrfind(qgr)
defaultdicv=dgfind[2]
defaultdicl=dgfind[3]
defaultdicvinv={defaultdicv[v]:v for v in defaultdicv}
defaultdiclinv={defaultdicl[l]:l for l in defaultdicl}
result.append([qgr,dgfind[1],{v: defaultdicv[dicv[v]] for v in dicv},{w:dicvinv[defaultdicvinv[w]] for w in defaultdicvinv},{l:defaultdicl[dicl[l]] for l in dicl},defaultdiclinv])
else:
result.append([qgr,dicv,dicvinv,dicl])
return result
def cyclicGstgraph(Gr,n,perm,cha,sigma=None):
if sigma is None:
G=PermutationGroup(tuple(range(1, n+1)))
sigma=G.gens()[0]
else:
G=sigma.parent()
perm_dic={}
character={}
for cycleno in range(len(perm)):
e=floor(QQ(n)/len(perm[cycleno]))
for j in range(len(perm[cycleno])):
perm_dic[perm[cycleno][j]] = perm[cycleno][(j+1)%len(perm[cycleno])]
character[perm[cycleno][j]]=(sigma**(len(perm[cycleno])), e, cha[cycleno])
legact={(sigma,k):perm_dic[k] for k in perm_dic}
mu=sigma
for j in range(2, n+1):
mu2=mu*sigma
legact.update({(mu2,k):perm_dic[legact[(mu,k)]] for k in perm_dic})
mu=mu2
Gr_new=Gstgraph(G,Gr,{},legact,character)
Gr_new.vertact_reconstruct()
return Gr_new
def leftcosetaction(G,H):
C=G.cosets(H, side='left')
L=[C[i][0] for i in range(len(C))]
d={}
for g in G:
for i in range(len(C)):
gtild=g*L[i]
for j in range(len(C)):
if gtild in C[j]:
d[(g,i)]=j
return (L,d)
class tautclass(object):
r"""
A tautological class on the moduli space \Mbar_{g,n} of stable curves.
Internally, it is represented by a list ``terms`` of objects of type
``decstratum``. In most cases, a ``tautclass`` should be created using
functions like ``kappaclass``, ``psiclass`` and
``StableGraph.boundary_pushforward``, but it is possible to create it
from a list of elements of type ``decstratum``.
EXAMPLES::
sage: from admcycles.admcycles import *
sage: gamma = StableGraph([1,2],[[1,2],[3]],[(2,3)])
sage: ds1 = decstratum(gamma, kappa=[[1],[]]); ds1
Graph : [1, 2] [[1, 2], [3]] [(2, 3)]
Polynomial : 1*(kappa_1^1 )_0
sage: ds2 = decstratum(gamma, kappa=[[],[1]]); ds2
Graph : [1, 2] [[1, 2], [3]] [(2, 3)]
Polynomial : 1*(kappa_1^1 )_1
sage: t = tautclass([ds1, ds2])
sage: (t - gamma.to_tautclass() * kappaclass(1,3,1)).is_zero()
True
"""
def __init__(self, terms):
self.terms = terms
def forgetful_pullback(self,legs,rename=True):
r"""
Returns the pullback of a given tautological class under the map pi : \bar M_{g,A cup B} --> \bar M_{g,A}.
INPUT:
legs : list
List B of legs that are forgotten by the map pi.
EXAMPLES::
sage: from admcycles import *
sage: psiclass(2,1,2).forgetful_pullback([3])
Graph : [1] [[1, 2, 3]] []
Polynomial : 1*psi_2^1
<BLANKLINE>
Graph : [1, 0] [[1, 4], [2, 3, 5]] [(4, 5)]
Polynomial : (-1)*
"""
return sum([tautclass([])]+[c.forgetful_pullback(legs,rename) for c in self.terms])
def forgetful_pushforward(self,legs):
r"""
Returns the pushforward of a given tautological class under the map pi : \bar M_{g,n} --> \bar M_{g,{1,...,n} \ A}.
INPUT:
legs : list
List A of legs that are forgotten by the map pi.
EXAMPLES::
sage: from admcycles import *
sage: s1=psiclass(3,1,3)^2;s1.forgetful_pushforward([2,3])
Graph : [1] [[1]] []
Polynomial : 1*
::
sage: t=tautgens(2,2,1)[1]+2*tautgens(2,2,1)[3]
sage: t.forgetful_pushforward([1])
Graph : [2] [[2]] []
Polynomial : 3*
<BLANKLINE>
Graph : [2] [[2]] []
Polynomial : 2*
"""
return tautclass([t.forgetful_pushforward(legs) for t in self.terms]).consolidate()
def rename_legs(self,dic,rename=False):
for t in self.terms:
t.rename_legs(dic,rename)
return self
def __add__(self,other):
if other==0:
return deepcopy(self)
new=deepcopy(self)
new.terms+=deepcopy(other.terms)
return new.consolidate()
def __neg__(self):
return (-1)*self
def __sub__(self,other):
return self + (-1)*other
def __iadd__(self,other):
if other==0:
return self
if isinstance(other,tautclass):
self.terms+=other.terms
return self
if isinstance(other,decstratum):
self.terms.append(other)
return self
def __radd__(self,other):
if other==0:
return deepcopy(self)
else:
return self+other
def __mul__(self,other):
return self.__rmul__(other)
def __rmul__(self,other):
if isinstance(other,tautclass):
result=tautclass([])
for t1 in self.terms:
for t2 in other.terms:
result+=t1*t2
return result
else:
new=deepcopy(self)
for i in range(len(new.terms)):
new.terms[i]=other*new.terms[i]
return new
def __pow__(self, exponent):
if isinstance(exponent, (Integer, Rational, int)):
if exponent == 0:
L = self.gnr_list()
gnset = set((g, n) for g, n,_ in L)
if len(gnset) == 1:
return fundclass(*gnset.pop())
else:
return 1
else:
return prod(exponent*[self])
def coeff_subs(self,dic):
r"""
If coefficients of self are polynomials, it tries to substitute variable assignments
given by dictionary ``dic``. This is done inplace, so the class is changed by this
operation.
EXAMPLES::
sage: from admcycles import psiclass
sage: R.<a1, a2> = PolynomialRing(QQ,2)
sage: t = a1 * psiclass(1,2,2) + a2 * psiclass(2,2,2); t
Graph : [2] [[1, 2]] []
Polynomial : a1*psi_1^1
<BLANKLINE>
Graph : [2] [[1, 2]] []
Polynomial : a2*psi_2^1
sage: t.coeff_subs({a2:1-a1})
Graph : [2] [[1, 2]] []
Polynomial : a1*psi_1^1
<BLANKLINE>
Graph : [2] [[1, 2]] []
Polynomial : (-a1 + 1)*psi_2^1
"""
safetycopy=deepcopy(self)
try:
for t in self.terms:
coelist=t.poly.coeff
for i in range(len(coelist)):
coelist[i]=coelist[i].subs(dic)
return self
except:
print('Substitution failed!')
return safetycopy
def __repr__(self):
s=''
for i in range(len(self.terms)):
s+=repr(self.terms[i])+'\n\n'
return s.rstrip('\n\n')
def toTautvect(self,g=None,n=None,r=None):
if g is None or n is None or r is None:
l = self.gnr_list()
if len(l) == 1:
g, n, r = l[0]
return converttoTautvect(self,g,n,r)
def toTautbasis(self,g=None,n=None,r=None,moduli='st'):
r"""
Computes vector expressing the class in a basis of the tautological ring.
If moduli is given, computes expression for the tautological ring of an open subset
of \Mbar_{g,n}.
Options:
- 'st' : all stable curves
- 'tl' : treelike curves (all cycles in the stable graph have length 1)
- 'ct' : compact type (stable graph is a tree)
- 'rt' : rational tails (there exists vertex of genus g)
- 'sm' : smooth curves
TESTS::
sage: from admcycles import StableGraph, psiclass
sage: b = StableGraph([1],[[1,2,3]],[(2,3)]).to_tautclass()
sage: b.toTautbasis()
(10, -10, -14)
sage: b.toTautbasis(moduli='ct')
(0, 0)
sage: c = psiclass(1,2,2)**2
sage: for mod in ('st', 'tl', 'ct', 'rt', 'sm'):
....: print(c.toTautbasis(moduli = mod))
(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
(0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)
(5/6, -1/6, 1/3, 0, 0)
(1)
()
"""
if g is None or n is None or r is None:
l = self.gnr_list()
if len(l) == 1:
g, n, r = l[0]
return Tautvecttobasis(converttoTautvect(self,g,n,r),g,n,r,moduli)
def toprodtautclass(self,g,n):
return prodtautclass(stgraph([g],[list(range(1, n+1))],[]), [[t] for t in deepcopy(self.terms)])
def dimension_filter(self):
for i in range(len(self.terms)):
self.terms[i].dimension_filter()
return self.consolidate()
def degree_cap(self,dmax):
for i in range(len(self.terms)):
self.terms[i].degree_cap(dmax)
return self.consolidate()
def degree_part(self,d):
result=tautclass([])
for i in range(len(self.terms)):
result+=self.terms[i].degree_part(d)
return result.consolidate()
def simplify(self,g=None,n=None,r=None):
r"""
Simplifies self by combining terms with same tautological generator, returns self.
EXAMPLES::
sage: from admcycles import psiclass
sage: t = psiclass(1,2,1) + 11*psiclass(1,2,1); t
Graph : [2] [[1]] []
Polynomial : 1*psi_1^1
<BLANKLINE>
Graph : [2] [[1]] []
Polynomial : 11*psi_1^1
sage: t.simplify()
Graph : [2] [[1]] []
Polynomial : 12*psi_1^1
sage: t
Graph : [2] [[1]] []
Polynomial : 12*psi_1^1
"""
L = self.gnr_list()
if not L:
return self
if r is not None:
g, n, _ = L[0]
self.terms= Tautv_to_tautclass(self.toTautvect(g,n,r),g,n,r).terms
return self
else:
result=tautclass([])
for gnr in L:
result+=Tautv_to_tautclass(self.toTautvect(*gnr),*gnr)
self.terms=result.terms
return self
def simplified(self,g=None,n=None,r=None):
r"""
Returns a simplified version of self by combining terms with same tautological generator,
leaving self invariant. If r is specified, only return the corresponding degree r part of self.
"""
L = self.gnr_list()
if not L:
return deepcopy(self)
if r is not None:
g, n, _ = L[0]
return Tautv_to_tautclass(self.toTautvect(g,n,r),g,n,r)
else:
result=tautclass([])
for gnr in L:
result+=Tautv_to_tautclass(self.toTautvect(*gnr),*gnr)
return result
def FZsimplify(self,r=None):
r"""
Returns representation of self as a tautclass formed by a linear combination of
the preferred tautological basis.
If r is given, only take degree r part.
EXAMPLES::
sage: from admcycles import fundclass, psiclass
sage: t = 7*fundclass(0,4) + psiclass(1,0,4) + 3 * psiclass(2,0,4) - psiclass(3,0,4)
sage: s = t.FZsimplify(); s
Graph : [0] [[1, 2, 3, 4]] []
Polynomial : 3*(kappa_1^1 )_0
<BLANKLINE>
Graph : [0] [[1, 2, 3, 4]] []
Polynomial : 7*
sage: u = t.FZsimplify(r=0); u
Graph : [0] [[1, 2, 3, 4]] []
Polynomial : 7*
"""
L = self.gnr_list()
if not L:
return deepcopy(self)
if r is not None:
g, n, _ = L[0]
return Tautvb_to_tautclass(self.toTautbasis(g,n,r),g,n,r)
else:
result=tautclass([])
for gnr in L:
result+=Tautvb_to_tautclass(self.toTautbasis(*gnr),*gnr)
return result
def consolidate(self):
for i in range(len(self.terms) - 1, -1, -1):
self.terms[i].consolidate()
if not self.terms[i].poly.monom:
self.terms.pop(i)
return self
def evaluate(self):
r"""
Computes integral against the fundamental class of the corresponding moduli space,
e.g. the degree of the zero-cycle part of the tautological class.
EXAMPLES::
sage: from admcycles import kappaclass
sage: t = kappaclass(1,1,1)
sage: t.evaluate()
1/24
"""
return sum([t.evaluate() for t in self.terms])
def fund_evaluate(self, g=None, n=None):
r"""
Computes degree zero part of the class as multiple of the fundamental class.
EXAMPLES::
sage: from admcycles import psiclass
sage: t = psiclass(1,2,1)
sage: s = t.forgetful_pushforward([1]); s
Graph : [2] [[]] []
Polynomial : 2*
sage: s.fund_evaluate()
2
"""
if g is None or n is None:
l = self.gnr_list()
if not l:
return 0
g, n, _ = l[0]
return self.toTautvect(g,n,0)[0]
def gnr_list(self):
r"""
Returns a list [(g,n,r), ...] of all genera g, number n of markings and degrees r
for the terms appearing in the class.
EXAMPLES::
sage: from admcycles import psiclass
sage: a = psiclass(1,2,3)
sage: t = a + a*a
sage: t.gnr_list()
[(2, 3, 2), (2, 3, 1)]
"""
return list(set(s for t in self.terms for s in t.gnr_list()))
def is_zero(self, moduli='st'):
r"""
Return whether this class is a known tautological relation (using
Pixton's implementation of the generalized Faber-Zagier relations).
If optional argument `moduli` is given, it checks the vanishing on
an open subset of \Mbar_{g,n}.
Options:
- 'st' : all stable curves
- 'tl' : treelike curves (all cycles in the stable graph have length 1)
- 'ct' : compact type (stable graph is a tree)
- 'rt' : rational tails (there exists vertex of genus g)
- 'sm' : smooth curves
EXAMPLES::
sage: from admcycles import kappaclass, lambdaclass
sage: diff = kappaclass(1,3,0) - 12*lambdaclass(1,3,0)
sage: diff.is_zero()
False
sage: diff.is_zero(moduli='sm')
True
"""
return all(self.toTautbasis(*gnr, moduli=moduli).is_zero() for gnr in self.gnr_list())
class KappaPsiPolynomial(object):
r"""
Polynomial in kappa and psi-classes on a common stable graph.
The data is stored as a list monomials of entries (kappa,psi) and a list
coeff of their coefficients. Here (kappa,psi) is a monomial in kappa, psi
classes, represented as
- ``kappa``: list of length self.gamma.num_verts() of lists of the form [3,0,2]
meaning that this vertex carries kappa_1**3*kappa_3**2
- ``psi``: dictionary, associating nonnegative integers to some legs, where
psi[l]=3 means that there is a psi**3 at this leg for a kppoly p. The values
can not be zero.
If ``p`` is such a polynomial, ``p[i]`` is of the form ``(kappa,psi,coeff)``.
EXAMPLES::
sage: from admcycles.admcycles import kppoly
sage: p1 = kppoly([([[0, 1], [0, 0, 2]], {1:2, 2:3}), ([[], [0, 1]], {})], [-3, 5])
sage: p1
(-3)*(kappa_2^1 )_0 (kappa_3^2 )_1 psi_1^2 psi_2^3 +5*(kappa_2^1 )_1
"""
def __init__(self, monom, coeff):
self.monom = monom
self.coeff = coeff
assert len(self.monom) == len(self.coeff)
def copy(self):
r"""
TESTS::
sage: from admcycles.admcycles import kppoly
sage: p = kppoly([([[], [0, 1]], {0:3, 1:2})], [5])
sage: p.copy()
5*(kappa_2^1 )_1 psi_0^3 psi_1^2
"""
res = KappaPsiPolynomial.__new__(KappaPsiPolynomial)
res.monom = [([x[:] for x in k], p.copy()) for k,p in self.monom]
res.coeff = self.coeff[:]
return res
def __iter__(self):
return iter([self[i] for i in range(len(self))])
def __getitem__(self,i):
return self.monom[i]+(self.coeff[i],)
def __len__(self):
return len(self.monom)
def __neg__(self):
return (-1)*self
def __add__(self,other):
r"""
TESTS:
Check that mutable data are not shared between the result and the terms::
sage: from admcycles.admcycles import kappacl, psicl
sage: A = kappacl(0,2,2)
sage: B = psicl(3,2)
sage: C = A + B
sage: C.monom[1][1].update({2:3})
sage: A
1*(kappa_2^1 )_0
sage: B
1*psi_3^1
"""
if other==0:
return self.copy()
new = self.copy()
for (k,p,c) in other:
try:
ind = new.monom.index((k,p))
new.coeff[ind] += c
if new.coeff[ind] == 0:
new.monom.pop(ind)
new.coeff.pop(ind)
except ValueError:
if c != 0:
new.monom.append((k[:], p.copy()))
new.coeff.append(c)
return new
def deg(self,i=0):
if not self.monom:
return None
else:
(kappa,psi)=self.monom[i]
return sum([sum((j+1)*kvec[j] for j in range(len(kvec))) for kvec in kappa])+sum(psi.values())
def graphpullback(self,dicv,dicl):
if len(self.monom)==0:
return kppoly([],[])
numvert_self=len(self.monom[0][0])
numvert = len(dicv)
preim = [[] for i in range(numvert_self)]
for v in dicv:
preim[dicv[v]].append(v)
resultpoly = kppoly([], [])
for (kappa,psi,coeff) in self:
psipolydict = {dicl[l]: psi[l] for l in psi}
psipoly = kppoly([([[] for i in range(numvert)],psipolydict)],[1])
kappapoly = prod([prod([sum([kappacl(w, k+1, numvert) for w in preim[v]])**kappa[v][k] for k in range(len(kappa[v]))]) for v in range(numvert_self)])
resultpoly += coeff * psipoly * kappapoly
return resultpoly
def rename_legs(self,dic):
for count in range(len(self.monom)):
self.monom[count]=(self.monom[count][0],{dic.get(l,l): self.monom[count][1][l] for l in self.monom[count][1]})
return self
def expand_vertices(self,start,numvert):
for count in range(len(self.monom)):
self.monom[count] = ([[] for i in range(start)]+self.monom[count][0] + [[] for i in range(numvert-start-len(self.monom[count][0]))], self.monom[count][1])
return self
def __radd__(self, other):
if other == 0:
return self.copy()
def __mul__(self,other):
if isinstance(other,kppoly):
return kppoly([([kappaadd(kappa1[i],kappa2[i]) for i in range(len(kappa1))],{l:psi1.get(l,0)+psi2.get(l,0) for l in set(list(psi1)+list(psi2))}) for (kappa1,psi1) in self.monom for (kappa2,psi2) in other.monom],[a*b for a in self.coeff for b in other.coeff]).consolidate()
else:
return self.__rmul__(other)
def __rmul__(self,other):
if isinstance(other,kppoly):
return self.__mul__(other)
else:
new = self.copy()
for i in range(len(self)):
new.coeff[i] *= other
return new
def __pow__(self,exponent):
if isinstance(exponent,Integer) or isinstance(exponent,Rational) or isinstance(exponent,int):
return prod(exponent*[self])
def __repr__(self):
s=''
for (kappa,psi,coeff) in self:
coestring=repr(coeff)
if coestring.find('+')!=-1 or coestring.find('-')!=-1:
coestring='('+coestring+')'
s+=coestring+'*'
count=-1
for l in kappa:
count+=1
if len(l)==0:
continue
s+='('
for j in range(len(l)):
if l[j]==0:
continue
s+='kappa_'+ repr(j+1)+'^'+repr(l[j])+' '
s+=')_' + repr(count)+' '
for l in psi:
if psi[l]==0:
continue
s+='psi_'+repr(l)+'^'+repr(psi[l])+' '
s+='+'
return s.rstrip('+')
def consolidate(self):
r"""
Remove trailing zeroes in kappa and l with psi[l]=0 and things with coeff=0
and sum up again.
TESTS::
sage: from admcycles.admcycles import kppoly
sage: kppoly([([[], [0, 1]], {})], [5]).consolidate()
5*(kappa_2^1 )_1
sage: kppoly([([[0, 0], [0,1,0]], {})], [3]).consolidate()
3*(kappa_2^1 )_1
sage: kppoly([([[], [0,1]], {})], [0]).consolidate() # known bug
0
"""
for (kappa,psi) in self.monom:
for kap in kappa:
remove_trailing_zeros(kap)
for l in list(psi):
if psi[l] == 0:
psi.pop(l)
newself=kppoly([],[])+self
self.monom=newself.monom
self.coeff=newself.coeff
return self
kppoly = KappaPsiPolynomial
def kappacl(vertex,index,numvert,g=None,n=None):
if index==0:
return (2 *g-2 +n)*onekppoly(numvert)
if index<0:
return kppoly([],[])
li=[[] for i in range(numvert)]
li[vertex]=(index-1)*[0]+[1]
return kppoly([(li,{})],[1])
def psicl(leg,numvert):
li=[[] for i in range(numvert)]
return kppoly([(li,{leg:1})],[1])
def onekppoly(numvert):
return kppoly([([[] for cnt in range(numvert)],{})],[1])
class decstratum(object):
r"""
A tautological class given by a boundary stratum, decorated by a polynomial in
kappa-classes on the vertices and psi-classes on the legs (i.e. half-edges
and markings)
The internal structure is as follows
- ``gamma``: underlying stgraph for the boundary stratum
- ``kappa``: list of length gamma.num_verts() of lists of the form [3,0,2]
meaning that this vertex carries kappa_1^3*kappa_3^2
- ``psi``: dictionary, associating nonnegative integers to some legs, where
psi[l]=3 means that there is a psi^3 at this leg
We adopt the convention that: kappa_a = pi_*(psi_{n+1}^{a+1}), where
pi is the universal curve over the moduli space, psi_{n+1} is the psi-class
at the marking n+1 that is forgotten by pi.
"""
def __init__(self,gamma,kappa=None,psi=None,poly=None):
self.gamma = gamma.copy(mutable=False)
if kappa is None:
kappa=[[] for i in range(gamma.num_verts())]
if psi is None:
psi={}
if poly is None:
self.poly=kppoly([(kappa,psi)],[1])
else:
if isinstance(poly,kppoly):
self.poly=poly
else:
self.poly=poly*onekppoly(gamma.num_verts())
def copy(self, mutable=True):
S = decstratum.__new__(decstratum)
S.gamma = self.gamma.copy(mutable)
S.poly = self.poly.copy()
return S
def automorphism_number(self):
r"""Returns number of automorphisms of underlying graph fixing decorations by kappa and psi-classes.
Currently assumes that self has exaclty one nonzero term."""
kappa,psi=self.poly.monom[0]
g,n,r = self.gnr_list()[0]
markings=range(1,n+1)
num=DR.num_of_stratum(Pixtongraph(self.gamma,kappa,psi),g,r,markings)
return DR.autom_count(num,g,r,markings,DR.MODULI_ST)
def rename_legs(self,dic,rename=False):
if rename:
legl=self.gamma.leglist()
shift=max(legl+list(dic.values())+[0])+1
dicenh={l:dic.get(l,l+shift) for l in legl}
else:
dicenh=dic
if not self.gamma.is_mutable():
self.gamma = self.gamma.copy()
self.gamma.rename_legs(dicenh)
self.poly.rename_legs(dicenh)
return self
def __repr__(self):
return 'Graph : ' + repr(self.gamma) +'\n'+ 'Polynomial : ' + repr(self.poly)
def split(self):
result=[]
numvert = self.gamma.num_verts()
lvdict={l:v for v in range(numvert) for l in self.gamma.legs(v, copy=False)}
for (kappa,psi,coeff) in self.poly:
psidiclist=[{} for v in range(numvert)]
for l in psi:
psidiclist[lvdict[l]][l]=psi[l]
newentry=[kppoly([([kappa[v]],psidiclist[v])],[1]) for v in range(numvert)]
newentry[0]*=coeff
result.append(newentry)
return result
def __neg__(self):
return (-1)*self
def __mul__(self,other):
return self.__rmul__(other)
def __rmul__(self,other):
if isinstance(other,Hdecstratum):
return other.__mul__(self)
if isinstance(other,decstratum):
if other.gamma.num_edges() > self.gamma.num_edges():
return other.__rmul__(self)
if self.gamma.num_edges() == other.gamma.num_edges() == 0:
return tautclass([decstratum(self.gamma, poly=self.poly*other.poly)])
p1=self.convert_to_prodtautclass()
p2=self.gamma.boundary_pullback(other)
p=p1*p2
return p.pushforward()
if isinstance(other,tautclass):
return tautclass([self*t for t in other.terms])
else:
new=self.copy()
new.poly*=other
return new
def toTautvect(self,g=None,n=None,r=None):
return converttoTautvect(self,g,n,r)
def toTautbasis(self,g=None,n=None,r=None):
return Tautvecttobasis(converttoTautvect(self,g,n,r),g,n,r)
def dimension_filter(self):
for i in range(len(self.poly.monom)):
(kappa,psi)=self.poly.monom[i]
for v in range(self.gamma.num_verts()):
if sum([(k+1)*kappa[v][k] for k in range(len(kappa[v]))]) + sum([psi[l] for l in self.gamma.legs(v, copy=False) if l in psi]) > 3 *self.gamma.genera(v) - 3 + self.gamma.num_legs(v):
self.poly.coeff[i]=0
break
return self.consolidate()
def degree_cap(self,dmax):
for i in range(len(self.poly.monom)):
if self.poly.deg(i) + self.gamma.num_edges() > dmax:
self.poly.coeff[i]=0
return self.consolidate()
def degree_part(self,d):
result=self.copy()
for i in range(len(result.poly.monom)):
if result.poly.deg(i) + result.gamma.num_edges() != d:
result.poly.coeff[i]=0
return result.consolidate()
def consolidate(self):
self.poly.consolidate()
return self
def evaluate(self):
answer=0
for (kappa,psi,coeff) in self.poly:
temp=1
for v in range(self.gamma.num_verts()):
psilist=[psi.get(l,0) for l in self.gamma.legs(v, copy=False)]
kappalist=[]
for j in range(len(kappa[v])):
kappalist+=[j+1 for k in range(kappa[v][j])]
if sum(psilist+kappalist) != 3 *self.gamma.genera(v)-3 +len(psilist):
temp = 0
break
temp*=DR.socle_formula(self.gamma.genera(v),psilist,kappalist)
answer+=coeff*temp
return answer
def forgetful_pushforward(self,markings,dicv=False):
result = self.copy(mutable=True)
result.dimension_filter()
vertim = list(range(self.gamma.num_verts()))
for m in markings:
v = result.gamma.vertex(m)
if result.gamma.genera(v) == 0 and len(result.gamma.legs(v, copy=False)) == 3:
vlegs = result.gamma.legs(v, copy=True)
vlegs.remove(m)
vmarks=set(vlegs).intersection(set(result.gamma.list_markings()))
if vmarks:
mprime=list(vmarks)[0]
vlegs.remove(mprime)
leg=vlegs[0]
legprime=result.gamma.leginversion(leg)
for (kappa,psi,coeff) in result.poly:
if legprime in psi:
temp=psi.pop(legprime)
psi[mprime]=temp
result.gamma.forget_markings([m])
result.gamma.stabilize()
vertim.pop(v)
for (kappa,psi,coeff) in result.poly:
kappa.pop(v)
else:
g=result.gamma.genera(v)
n = result.gamma.num_legs(v) - 1
numvert = result.gamma.num_verts()
newpoly=kppoly([],[])
for (kappa,psi,coeff) in result.poly:
trunckappa=deepcopy(kappa)
trunckappa[v]=[]
truncpsi=deepcopy(psi)
for l in result.gamma.legs(v, copy=False):
truncpsi.pop(l,0)
trunckppoly=kppoly([(trunckappa,truncpsi)],[coeff])
kappav=kappa[v]
psiv={l:psi.get(l,0) for l in result.gamma.legs(v, copy=False) if l !=m}
psivpoly=kppoly([([[] for i in range(numvert)],psiv)],[1])
a_m = psi.get(m,0)
cposs=[]
for b in kappav:
cposs.append(list(range(b+1)))
currpoly=kppoly([],[])
for cvect in itertools.product(*cposs):
currpoly+=prod([binomial(kappav[i],cvect[i]) for i in range(len(cvect))])*prod([kappacl(v,i+1, numvert)**cvect[i] for i in range(len(cvect))])*psivpoly*kappacl(v,a_m+sum([(kappav[i]-cvect[i])*(i+1) for i in range(len(cvect))])-1, numvert, g=g, n=n)
if a_m==0:
kappapoly=prod([kappacl(v, i+1, numvert)**kappav[i] for i in range(len(kappav))])
def psiminuspoly(l):
psivminus=deepcopy(psiv)
psivminus[l]-=1
return kppoly([([[] for i in range(numvert)],psivminus)],[1])
currpoly+=sum([psiminuspoly(l) for l in psiv if psiv[l]>0])*kappapoly
newpoly+=currpoly*trunckppoly
result.poly=newpoly
result.gamma.forget_markings([m])
result.dimension_filter()
if dicv:
return (result,{v:vertim[v] for v in range(len(vertim))})
else:
return result
def forgetful_pullback(self,newmark,rename=True):
if newmark==[]:
return tautclass([self])
if rename:
mleg=max(self.gamma._maxleg+1, max(newmark)+1)
rndic={}
rnself = self.copy()
for e in self.gamma.edges(copy=False):
if e[0] in newmark:
rndic[e[0]]=mleg
mleg+=1
if e[1] in newmark:
rndic[e[1]]=mleg
mleg+=1
rnself.gamma.rename_legs(rndic)
for l in rndic:
for (kappa,psi,coeff) in rnself.poly:
if l in psi:
psi[rndic[l]]=psi.pop(l)
else:
rnself=self
a=newmark[0]
nextnewmark=deepcopy(newmark)
nextnewmark.pop(0)
partpullback=[]
for v in range(self.gamma.num_verts()):
grv = rnself.gamma.copy()
grv._legs[v] += [a]
grv.tidy_up()
grv.set_immutable()
polyv=kppoly([],[])
for (kappa,psi,coeff) in rnself.poly:
binomdist=[list(range(0, m+1)) for m in kappa[v]]
for bds in itertools.product(*binomdist):
kappa_new=deepcopy(kappa)
kappa_new[v]=list(bds)
psi_new=deepcopy(psi)
psi_new[a]=sum([(i+1)*(kappa[v][i]-bds[i]) for i in range(len(bds))])
polyv=polyv+kppoly([(kappa_new,psi_new)],[((-1)**(sum([(kappa[v][i]-bds[i]) for i in range(len(bds))])))*coeff*prod([binomial(kappa[v][i],bds[i]) for i in range(len(bds))])])
rnself.gamma.tidy_up()
newleg = max(rnself.gamma._maxleg+1, a+1)
grl={}
for l in rnself.gamma.legs(v, copy=False):
new_gr = rnself.gamma.copy()
new_gr._legs[v].remove(l)
new_gr._legs[v] += [newleg]
new_gr._genera += [0]
new_gr._legs += [[newleg+1, a, l]]
new_gr._edges += [(newleg, newleg+1)]
new_gr.tidy_up()
new_gr.set_immutable()
grl[l] = new_gr
polyl = {l:kppoly([],[]) for l in rnself.gamma.legs(v, copy=False)}
for (kappa,psi,coeff) in rnself.poly:
for j in set(psi).intersection(set(rnself.gamma.legs(v, copy=False))):
if psi[j]==0:
continue
kappa_new=deepcopy(kappa)+[[]]
psi_new=deepcopy(psi)
psi_new[newleg]=psi_new.pop(j)-1
polyl[j]=polyl[j]+kppoly([(kappa_new,psi_new)],[-coeff])
partpullback+=[decstratum(grv,poly=polyv)]+[decstratum(grl[l], poly=polyl[l]) for l in rnself.gamma.legs(v, copy=False) if len(polyl[l])>0]
T=tautclass(partpullback)
return T.forgetful_pullback(nextnewmark)
def convert_to_prodtautclass(self):
terms = []
for (kappa,psi,coeff) in self.poly:
currterm=[]
for v in range(self.gamma.num_verts()):
psiv={(lnum+1):psi[self.gamma._legs[v][lnum]] for lnum in range(len(self.gamma._legs[v])) if self.gamma._legs[v][lnum] in psi}
currterm.append(decstratum(stgraph([self.gamma.genera(v)], [list(range(1, len(self.gamma._legs[v])+1))], []), kappa=[kappa[v]],psi=psiv))
currterm[0].poly*=coeff
terms.append(currterm)
return prodtautclass(self.gamma,terms)
def gnr_list(self):
g = self.gamma.g()
n = self.gamma.n()
e = self.gamma.num_edges()
L = ((g, n, e + self.poly.deg(i)) for i in range(len(self.poly.monom)))
return list(set(L))
class Htautclass(object):
def __init__(self, terms):
self.terms = terms
def __neg__(self):
return (-1)*self
def __add__(self,other):
if other==0:
return deepcopy(self)
new=deepcopy(self)
new.terms+=deepcopy(other.terms)
return new.consolidate()
def __radd__(self,other):
if other==0:
return self
else:
return self+other
def __rmul__(self,other):
new=deepcopy(self)
for i in range(len(new.terms)):
new.terms[i]=other*new.terms[i]
return new
def __mul__(self,other):
if isinstance(other,tautclass):
return sum([a*b for a in self.terms for b in other.terms])
def __repr__(self):
s=''
for i in range(len(self.terms)):
s+=repr(self.terms[i])+'\n\n'
return s.rstrip('\n\n')
def consolidate(self):
for i in range(len(self.terms)):
self.terms[i].consolidate()
return self
def to_prodHclass(self):
if self.terms==[]:
raise ValueError('Htautclass does not know its graph, failed to convert to prodHclass')
else:
gamma0=stgraph([self.terms[0].Gr.gamma.g()],[self.terms[0].Gr.gamma.list_markings()],[])
terms=[t.to_decHstratum() for t in self.terms]
return prodHclass(gamma0,terms)
def quotient_pushforward(self):
res=tautclass([])
for c in self.terms:
res+=c.quotient_pushforward()
return res
def quotient_pullback(self, other):
if self.terms==[]:
return deepcopy(self)
g=self.terms[0].Gr.gamma.g()
hdata=self.terms[0].Gr.hdata
trivGg=trivGgraph(g,hdata)
Gord=hdata.G.order()
deltadeg=trivGg.delta_degree(0)
push=(QQ(1)/deltadeg)*self.quotient_pushforward()
pro=push*other
pro.dimension_filter()
result=Htautclass([])
edgenums=set(t.gamma.num_edges() for t in pro.terms)
Hstrata={i:list_Hstrata(g,hdata,i) for i in edgenums}
Hquots={i:list_quotgraphs(g,hdata,i,True) for i in edgenums}
for t in pro.terms:
(r,ti,tdicv,tdicl)=deggrfind(t.gamma)
preims=[j for j in range(len(Hstrata[r])) if Hquots[r][j][1]==ti]
for j in preims:
newGr = Hstrata[r][j].copy()
numvert = newGr.gamma.num_verts()
multiplicity=prod([newGr.lstabilizer(e0).order() for (e0,e1) in Hquots[r][j][0].edges(copy=False)]) * t.gamma.automorphism_number() / QQ(len(equiGraphIsom(newGr,newGr)))
(quotientgraph,inde,dicv,dicvinv,dicl,diclinv) = Hquots[r][j]
dv={v: tdicv[v] for v in range(t.gamma.num_verts())}
newpoly=kppoly([],[])
for (kappa,psi,coeff) in t.poly:
newcoeff=coeff
newkappa=[[] for u in range(numvert)]
for v in range(len(kappa)):
newkappa[dicvinv[tdicv[v]]]=kappa[v]
newcoeff*=(QQ(1)/newGr.vstabilizer(dicvinv[tdicv[v]]).order())**(sum(kappa[v]))
newpsi={diclinv[tdicl[l]]:psi[l] for l in psi}
newcoeff*=prod([(newGr.lstabilizer(diclinv[tdicl[l]]).order())**(psi[l]) for l in psi])
newpoly+=kppoly([(newkappa,newpsi)],[newcoeff])
newpoly*=multiplicity
result.terms.append(Hdecstratum(newGr,poly=newpoly))
return result
class Hdecstratum(object):
def __init__(self,Gr,kappa=None,psi=None,poly=None):
self.Gr=Gr
if kappa is None:
kappa = [[] for i in range(Gr.gamma.num_verts())]
if psi is None:
psi={}
if poly is None:
self.poly=kppoly([(kappa,psi)],[1])
else:
self.poly=poly
def __neg__(self):
return (-1)*self
def __rmul__(self,other):
if isinstance(other,decstratum):
return self.__mul__(other)
else:
new=deepcopy(self)
new.poly*=other
return new
def to_decHstratum(self):
result=self.Gr.to_decHstratum()
result.poly*=self.poly
return result
def __mul__(self,other):
if isinstance(other,decstratum):
if self.Gr.gamma.num_edges() == 0:
mindeg = ceil(QQ(other.gamma.num_edges())/self.Gr.G.order())
maxdeg = other.gamma.num_edges()
numotheraut = other.gamma.automorphism_number()
Ggrdegenerations=[list_Hstrata(self.Gr.gamma.g(),self.Gr.hdata, r) for r in range(maxdeg+1)]
commdeg=[]
multiplicityvector=[]
for deg in range(mindeg, maxdeg+1):
for Gamma3 in Ggrdegenerations[deg]:
currentdeg=[]
currentmultiplicity=[]
otstructures=Astructures(Gamma3.gamma,other.gamma)
if otstructures==[]:
continue
AutGr=equiGraphIsom(Gamma3,Gamma3)
while otstructures:
(dicvcurr,diclcurr)=otstructures[0]
multiplicity=0
for (dicv,dicl) in AutGr:
dicvnew={v:dicvcurr[dicv[v]] for v in dicv}
diclnew={l:dicl[diclcurr[l]] for l in diclcurr}
try:
otstructures.remove((dicvnew,diclnew))
multiplicity+=1
except:
pass
coveredlegs=set()
for g in Gamma3.G:
for l in diclcurr.values():
coveredlegs.add(Gamma3.legact[(g,l)])
if set(Gamma3.gamma.halfedges()).issubset(coveredlegs):
currentdeg += [({v:0 for v in range(Gamma3.gamma.num_verts())}, {l:l for l in self.Gr.gamma.leglist()}, dicvcurr, diclcurr)]
multiplicity*=QQ(1)/len(AutGr)
currentmultiplicity+=[multiplicity]
commdeg+=[(Gamma3,currentdeg)]
multiplicityvector+=[currentmultiplicity]
result = Htautclass([])
for count in range(len(commdeg)):
if commdeg[count][1]==[]:
continue
Gamma3 = commdeg[count][0]
gammaresultpoly=kppoly([],[])
for count2 in range(len(commdeg[count][1])):
(vdict1,ldict1,vdict2,ldict2) = commdeg[count][1][count2]
preim1 = [[] for i in range(self.Gr.gamma.num_verts())]
for v in vdict1:
preim1[vdict1[v]]+=[v]
preim2 = [[] for i in range(other.gamma.num_verts())]
for v in vdict2:
preim2[vdict2[v]]+=[v]
numvert = Gamma3.gamma.num_verts()
quotdicl={}
quotgraph=Gamma3.quotient_graph(dicl=quotdicl)
numpreim={l:0 for l in quotgraph.halfedges()}
for l in self.Gr.gamma.halfedges():
numpreim[quotdicl[ldict1[l]]]+=1
for l in other.gamma.halfedges():
numpreim[quotdicl[ldict2[l]]]+=1
excesspoly=onekppoly(numvert)
for (e0,e1) in quotgraph.edges(copy=False):
if numpreim[e0]>1:
excesspoly*=( (-1)*(psicl(e0,numvert)+psicl(e1,numvert)))**(numpreim[e0]-1)
resultpoly=kppoly([],[])
for (kappa1,psi1,coeff1) in self.poly:
for (kappa2,psi2,coeff2) in other.poly:
psipolydict={ldic1t[l]: psi1[l] for l in psi1}
for l in psi2:
psipolydict[ldict2[l]]=psipolydict.get(ldict2[l],0)+psi2[l]
psipoly=kppoly([([[] for i in range(numvert)],psipolydict)],[1])
kappapoly1=prod([prod([sum([kappacl(w, k+1, numvert) for w in preim1[v]])**kappa1[v][k] for k in range(len(kappa1[v]))]) for v in range(self.Gr.gamma.num_verts())])
kappapoly2=prod([prod([sum([kappacl(w, k+1, numvert) for w in preim2[v]])**kappa2[v][k] for k in range(len(kappa2[v]))]) for v in range(other.gamma.num_verts())])
resultpoly+=coeff1*coeff2*psipoly*kappapoly1*kappapoly2
resultpoly*=multiplicityvector[count][count2]*excesspoly
gammaresultpoly+=resultpoly
result.terms+=[Htautclass([Hdecstratum(Gamma3,poly=gammaresultpoly)])]
return result
def __repr__(self):
return 'Graph : ' + repr(self.Gr) +'\n'+ 'Polynomial : ' + repr(self.poly)
def consolidate(self):
self.poly.consolidate()
return self
def quotient_pushforward(self):
Gord=self.Gr.G.order()
qdicv={}
qdicvinv={}
qdicl={}
quotgr=self.Gr.quotient_graph(dicv=qdicv, dicvinv=qdicvinv, dicl=qdicl)
preimlist = [[] for i in range(quotgr.num_verts())]
for v in range(self.Gr.gamma.num_verts()):
preimlist[qdicv[v]]+=[v]
deltadeg={w: self.Gr.delta_degree(qdicvinv[w]) for w in qdicvinv}
resultpoly=kppoly([],[])
for (kappa,psi,coeff) in self.poly:
kappanew=[]
coeffnew=coeff
psinew={}
for w in range(quotgr.num_verts()):
kappatemp=[]
for v in preimlist[w]:
kappatemp=kappaadd(kappatemp,kappa[v])
kappanew+=[kappatemp]
coeffnew*=(QQ(Gord)/len(preimlist[w]))**sum(kappatemp)
for l in psi:
psinew[qdicl[l]]=psinew.get(qdicl[l],0)+psi[l]
coeffnew*=(QQ(1)/self.Gr.character[l][1])**psi[l]
coeffnew*=prod(deltadeg.values())
resultpoly+=kppoly([(kappanew,psinew)],[coeffnew])
return tautclass([decstratum(quotgr,poly=resultpoly)])
def remove_trailing_zeros(l):
r"""
Remove the trailing zeroes t the end of l.
EXAMPLES::
sage: from admcycles.admcycles import remove_trailing_zeros
sage: l = [0, 1, 0, 0]
sage: remove_trailing_zeros(l)
sage: l
[0, 1]
sage: remove_trailing_zeros(l)
sage: l
[0, 1]
sage: l = [0, 0]
sage: remove_trailing_zeros(l)
sage: l
[]
sage: remove_trailing_zeros(l)
sage: l
[]
"""
while l and l[-1] == 0:
l.pop()
def kappaadd(a,b):
if len(a)<len(b):
aprime=a+(len(b)-len(a))*[0]
else:
aprime=a
if len(b)<len(a):
bprime=b+(len(a)-len(b))*[0]
else:
bprime=b
return [aprime[i]+bprime[i] for i in range(len(aprime))]
def Graphtodecstratum(G):
genera=[]
kappa=[]
for i in range(1, G.M.nrows()):
genera.append(G.M[i,0][0])
kappa.append([G.M[i,0][j] for j in range(1, G.M[i,0].degree()+1)])
legs=[[] for i in genera]
edges=[]
psi={}
legname=G.M.ncols()
for j in range(1, G.M.ncols()):
for i in range(1, G.M.nrows()):
if G.M[i,j] != 0:
break
if G.M[0,j] != 0:
legs[i-1].append(ZZ(G.M[0,j]))
if G.M[i,j].degree()>=1:
psi[G.M[0,j]]=G.M[i,j][1]
if G.M[0,j] == 0:
if G.M[i,j][0] == 2:
legs[i-1]+=[legname, legname+1]
edges.append((legname,legname+1))
if G.M[i,j].degree()>=1:
psi[legname]=G.M[i,j][1]
if G.M[i,j].degree()>=2:
psi[legname+1]=G.M[i,j][2]
legname+=2
if G.M[i,j][0] == 1:
if G.M.nrows()<=i+1:
print('Attention')
print(G.M)
for k in range(i+1, G.M.nrows()):
if G.M[k,j] != 0:
break
legs[i-1]+=[legname]
legs[k-1]+=[legname+1]
edges.append((legname,legname+1))
if G.M[i,j].degree()>=1:
psi[legname]=G.M[i,j][1]
if G.M[k,j].degree()>=1:
psi[legname+1]=G.M[k,j][1]
legname+=2
return decstratum(stgraph(genera,legs,edges),kappa=kappa,psi=psi)
@cached_function
def generating_indices(g,n,r,FZ=False):
if FZ or r<=(3 *g-3 +n)/QQ(2) or not (g <=1 or (g==2 and n <= 19)):
rel=matrix(DR.list_all_FZ(g,r,tuple(range(1, n+1))))
relconv=matrix([DR.convert_vector_to_monomial_basis(rel.row(i),g,r,tuple(range(1, n+1))) for i in range(rel.nrows())])
del(rel)
reverseindices=list(range(relconv.ncols()))
reverseindices.reverse()
reordrelconv=relconv.matrix_from_columns(reverseindices)
del(relconv)
reordrelconv.echelonize()
reordnongens=reordrelconv.pivots()
nongens=[reordrelconv.ncols()-i-1 for i in reordnongens]
gens = [i for i in range(reordrelconv.ncols()) if not i in nongens]
gtob={gens[j]: vector(QQ,len(gens),{j:1}) for j in range(len(gens))}
for i in range(len(reordnongens)):
gtob[nongens[i]]=vector([-reordrelconv[i,reordrelconv.ncols()-j-1] for j in gens])
genstobasis.set_cache([gtob[i] for i in range(reordrelconv.ncols())],
*(g,n,r))
return gens
else:
gencompdeg=generating_indices(g,n,3 *g-3 +n-r,True)
maxrank=len(gencompdeg)
currrank = 0
M=matrix(maxrank,0)
gens=[]
count=0
while currrank<maxrank:
Mnew=block_matrix([[M,matrix(DR.pairing_submatrix(tuple(gencompdeg),(count,),g,3 *g-3 +n-r,tuple(range(1, n+1))))]],subdivide=False)
if Mnew.rank()>currrank:
gens.append(count)
M=Mnew
currrank=Mnew.rank()
count+=1
return gens
def Hintnumbers(g,dat,indices=None, redundancy=False):
Hbar = Htautclass([Hdecstratum(trivGgraph(g,dat),poly=onekppoly(1))])
dimens = Hbar.terms[0].Gr.dim()
markins=tuple(range(1, 1 + len(Hbar.terms[0].Gr.gamma.list_markings())))
strata=DR.all_strata(g,dimens,markins)
decst=[Graphtodecstratum(Grr) for Grr in strata]
intnumbers=[]
if indices is None or redundancy:
effindices=list(range(len(strata)))
else:
effindices=indices
for i in effindices:
intnumbers+=[(Hbar*(tautclass([ Graphtodecstratum(strata[i])]))).quotient_pushforward().evaluate()]
if not redundancy:
return intnumbers
M=DR.FZ_matrix(g,dimens,markins)
Mconv=matrix([DR.convert_vector_to_monomial_basis(M.row(i),g,dimens,markins) for i in range(M.nrows())])
relcheck= Mconv*vector(intnumbers)
if relcheck==2 *relcheck:
print('Intersection numbers are consistent, number of checks: '+repr(len(relcheck)))
else:
print('Intersection numbers not consistent for '+repr((g,dat.G,dat.l)))
print('relcheck = '+repr(relcheck))
if indices is None:
return intnumbers
else:
return [intnumbers[j] for j in indices]
def Hidentify(g,dat,method='pull',vecout=False,redundancy=False,markings=None):
r"""
Identifies the (pushforward of the) fundamental class of \bar H_{g,dat} in
terms of tautological classes.
INPUT:
- ``g`` -- integer; genus of the curve C of the cover C -> D
- ``dat`` -- HurwitzData; ramification data of the cover C -> D
- ``method`` -- string (default: `'pull'`); method of computation
method='pull' means we pull back to the boundary strata and recursively
identify the result, then use linear algebra to restrict the class of \bar H
to an affine subvectorspace (the corresponding vector space is the
intersection of the kernels of the above pullback maps), then we use
intersections with kappa,psi-classes to get additional information.
If this is not sufficient, return instead information about this affine
subvectorspace.
method='pair' means we compute all intersection pairings of \bar H with a
generating set of the complementary degree and use this to reconstruct the
class.
- ``vecout`` -- bool (default: `False`); return a coefficient-list with respect
to the corresponding list of generators tautgens(g,n,r).
NOTE: if vecout=True and markings is not the full list of markings 1,..,n, it
will return a vector for the space with smaller number of markings, where the
markings are renamed in an order-preserving way.
- ``redundancy`` -- bool (default: `True`); compute pairings with all possible
generators of complementary dimension (not only a generating set) and use the
redundant intersections to check consistency of the result; only valid in case
method='pair'.
- ``markings`` -- list (default: `None`); return the class obtained by the
fundamental class of the Hurwitz space by forgetting all points not in
markings; for markings = None, return class without forgetting any points.
EXAMPLES::
sage: from admcycles.admcycles import trivGgraph, HurData, Hidentify
sage: G = PermutationGroup([(1, 2)])
sage: H = HurData(G, [G[1], G[1]])
sage: t = Hidentify(2, H, markings=[]); t # Bielliptic locus in \Mbar_2
Graph : [2] [[]] []
Polynomial : 30*(kappa_1^1 )_0
<BLANKLINE>
Graph : [1, 1] [[6], [7]] [(6, 7)]
Polynomial : (-9)*
sage: G = PermutationGroup([(1, 2, 3)])
sage: H = HurData(G, [G[1]])
sage: t = Hidentify(2, H, markings=[]); t.is_zero() # corresponding space is empty
True
"""
Hbar = trivGgraph(g,dat)
r = Hbar.dim()
n = len(Hbar.gamma.list_markings())
if markings is None:
markings=list(range(1, n+1))
N=len(markings)
msorted=sorted(markings)
markingdictionary={i+1:msorted[i] for i in range(N)}
invmarkingdictionary={markingdictionary[j]:j for j in markingdictionary}
if dat.G.order()==1:
if len(markings)<n:
return tautclass([])
else:
return tautclass([decstratum(stgraph([g],[list(range(1, n+1))],[]))])
if not cohom_is_taut(g,len(markings),2 *(3 *g-3 +len(markings)-r)):
print('Careful, Hurwitz cycle '+repr((g,dat))+' might not be tautological!\n')
found=False
try:
Hdb=Hdatabase[(g,dat)]
for marks in Hdb:
if set(markings).issubset(set(marks)):
forgottenmarks=set(marks)-set(markings)
result=deepcopy(Hdb[marks])
result=result.forgetful_pushforward(list(forgottenmarks))
found=True
except KeyError:
pass
if method=='pair' and not found:
if not (g <= 1 or (g==2 and n <= 19)):
print('Careful, potentially outside of Gorenstein range!\n')
genscompl= generating_indices(g,n,r)
gens = generating_indices(g,n,3 *g-3 +n-r)
intnumbers = Hintnumbers(g,dat,indices=genscompl,redundancy=redundancy)
Mpairing = matrix(DR.pairing_submatrix(tuple(genscompl),tuple(gens),g,r,tuple(range(1, n+1))))
x = Mpairing.solve_right(vector(intnumbers))
strata = DR.all_strata(g,3 *g-3 +n-r, tuple(range(1, n+1)))
stratmod=[]
for i in range(len(gens)):
if x[i]==0:
continue
currstrat=Graphtodecstratum(strata[gens[i]])
currstrat.poly*=x[i]
stratmod.append(currstrat)
result= tautclass(stratmod)
if (g,dat) not in Hdatabase:
Hdatabase[(g,dat)]={}
Hdatabase[(g,dat)][tuple(range(1, n+1))]=result
if len(markings)<n:
result=Hidentify(g,dat,method,False,redundancy,markings)
if method=='pull' and not found:
gens = generating_indices(g,N,3 *g-3 +N-r)
ngens=len(gens)
bdrydiv=list_strata(g,N,1)
A=pullback_matrix(g,N,3 *g-3 +N-r,irrbdry=False)
irrpullback = False
if g >= 1 and (A.rank() < ngens or redundancy):
irrpullback = True
irrbd = stgraph([g-1], [list(range(1, N+3))], [(N+1, N+2)])
A = block_matrix([[A], [pullback_matrix(g, N, 3*g - 3 + N - r, bdry=irrbd)]], subdivide=False)
kpinters=False
if A.rank() < ngens or redundancy:
kpinters=True
(B,kppolynomials)=kpintersection_matrix(g,N,3 *g-3 +N-r)
A=block_matrix([[A],[B]],subdivide=False)
if A.rank()<ngens:
print('Matrix rank in computation of '+repr((g,dat))+' not full!\n')
b=[]
forgetmarks=list(set(range(1, n+1))-set(markings))
bar_H=prodHclass(stgraph([g],[list(range(1, N+1))],[]),[decHstratum(stgraph([g],[list(range(1, N+1))],[]), {0: (g,dat)}, [[0,markingdictionary]])])
for bdry in bdrydiv:
if bdry.num_verts()!=1:
b+=list(bar_H.gamma0_pullback(bdry).toprodtautclass().totensorTautbasis(3 *g-3 +N-r,vecout=True))
if irrpullback:
b+=list(bar_H.gamma0_pullback(irrbd).toprodtautclass().totensorTautbasis(3 *g-3 +N-r,vecout=True))
if kpinters:
b+=[(bar_H*tautclass([kppo])).evaluate() for kppo in kppolynomials]
try:
x = A.solve_right(vector(b))
except ValueError:
global examA
examA=A
global examb
examb=b
global Hexam
Hexam=(g,dat,method,vecout,redundancy,markings)
raise ValueError('In Hidentify, matrix equation has no solution')
result=Tautvb_to_tautclass(x,g,N,3 *g-3 +N-r)
result.rename_legs(markingdictionary,rename=True)
if not found:
if (g,dat) not in Hdatabase:
Hdatabase[(g,dat)]={}
Hdatabase[(g,dat)][tuple(markings)]=deepcopy(result)
if vecout:
result.rename_legs(invmarkingdictionary,rename=True)
return result.toTautvect(g,len(markings),3 *g-3 +len(markings)-r)
else:
return result
def pullback_matrix(g,n,d,bdry=None,irrbdry=True):
genindices = generating_indices(g,n,d)
strata = DR.all_strata(g, d, tuple(range(1, n+1)))
gens=[Graphtodecstratum(strata[i]) for i in genindices]
ngens=len(genindices)
if bdry is None:
A=matrix(0,ngens)
bdrydiv=list_strata(g,n,1)
for bdry in bdrydiv:
if irrbdry or bdry.num_verts() != 1:
A=block_matrix([[A],[pullback_matrix(g,n,d,bdry)]],subdivide=False)
return A
else:
pullbacks=[bdry.boundary_pullback(cl).totensorTautbasis(d,True) for cl in gens]
return matrix(pullbacks).transpose()
def kpintersection_matrix(g,n,d):
r"""
Computes the matrix B whose columns are the intersection numbers of our preferred
generating_indices(g,n,d) with the list kppolynomials of kappa-psi-polynomials of
opposite degree. Returns the tuple (B,kppolynomials), where the latter are decstrata.
TESTS::
sage: from admcycles.admcycles import kpintersection_matrix
sage: kpintersection_matrix(0,5,2)[0]
[1]
"""
genindices = generating_indices(g,n,d)
strata = DR.all_strata(g,d,tuple(range(1, n+1)))
opstrata = DR.all_strata(g,3 *g-3 +n-d,tuple(range(1, n+1)))
opgenindices = list(range(len(opstrata)))
gens=[Graphtodecstratum(strata[i]) for i in genindices]
ngens=len(genindices)
kppolynomials=[Graphtodecstratum(opstrata[i]) for i in opgenindices]
kppolynomials=[s for s in kppolynomials if s.gamma.num_edges()==0]
B=[[(gen*s).evaluate() for gen in gens] for s in kppolynomials]
return (matrix(B),kppolynomials)
class prodtautclass(object):
r"""
A tautological class on the product of several spaces \bar M_{g_i, n_i},
which correspond to the vertices of a stable graph.
One way to construct such a tautological class is to pull back an other
tautological class under a boundary gluing map.
Internally, the product class is stored as a list ``self.terms``, where
each entry is of the form ``[ds(0),ds(1), ..., ds(m)]``, where ``ds(j)``
are decstratums.
Be careful that the marking-names on the ``ds(j)`` are 1,2,3, ...
corresponding to legs 0,1,2, ... in gamma.legs(j).
If argument prodtaut is given, it is a list (of length gamma.num_verts())
of tautclasses with correct leg names and this should produce the
prodtautclass given as pushforward of the product of these classes that is,
``self.terms`` is the list of all possible choices of a decstratum from the
various factors.
"""
def __init__(self, gamma, terms=None, protaut=None):
self.gamma = gamma.copy(mutable=False)
if terms is not None:
self.terms = terms
elif protaut is not None:
declists=[t.terms for t in protaut]
self.terms=[deepcopy(list(c)) for c in itertools.product(*declists)]
else:
self.terms = [[decstratum(StableGraph([self.gamma.genera(i)], [list(range(1, self.gamma.num_legs(i)+1))], [])) for i in range(self.gamma.num_verts())]]
def pushforward(self):
result=tautclass([])
for t in self.terms:
maxleg=max(self.gamma.leglist()+[0])+1
genera=[]
legs=[]
edges = self.gamma.edges()
numvert = sum(s.gamma.num_verts() for s in t)
poly = onekppoly(numvert)
for i in range(len(t)):
s = t[i]
gr = s.gamma
start = len(genera)
genera.extend(gr.genera())
renamedic = {j+1: self.gamma._legs[i][j] for j in range(len(self.gamma._legs[i]))}
for (e0,e1) in gr._edges:
renamedic[e0] = maxleg
renamedic[e1] = maxleg+1
maxleg += 2
legs += [[renamedic[l] for l in ll] for ll in gr._legs]
edges += [(renamedic[e0], renamedic[e1]) for (e0,e1) in gr._edges]
grpoly = deepcopy(s.poly)
grpoly.rename_legs(renamedic)
grpoly.expand_vertices(start,numvert)
poly*=grpoly
result+=tautclass([decstratum(stgraph(genera,legs,edges),poly=poly)])
return result
def partial_pushforward(self,gamma0,dicv,dicl):
gamma=self.gamma
preimv=[[] for v in gamma0.genera(copy=False)]
for w in dicv:
preimv[dicv[w]].append(w)
usedlegs=dicl.values()
preimgr=[]
for v in range(gamma0.num_verts()):
preimgr.append(stgraph([gamma.genera(w) for w in preimv[v]], [gamma.legs(w) for w in preimv[v]], [e for e in gamma.edges(copy=False) if (gamma.vertex(e[0]) in preimv[v] and gamma.vertex(e[1]) in preimv[v]) and not (e[0] in usedlegs or e[1] in usedlegs)]))
result=prodtautclass(gamma0,[])
rndics=[{dicl[gamma0.legs(v, copy=False)[j]]:j+1 for j in range(gamma0.num_legs(v))} for v in range(gamma0.num_verts())]
for t in self.terms:
tempclasses=[prodtautclass(preimgr[v],[[t[i] for i in preimv[v]]]) for v in range(gamma0.num_verts())]
temppush=[s.pushforward() for s in tempclasses]
for v in range(gamma0.num_verts()):
temppush[v].rename_legs(rndics[v],rename=True)
for comb in itertools.product(*[tp.terms for tp in temppush]):
result.terms.append(list(comb))
return result
def toprodHclass(self):
gamma0 = self.gamma.copy(mutable=False)
terms=[]
trivgp=PermutationGroup([()])
trivgpel=trivgp[0]
result=prodHclass(gamma0,[])
for t in self.terms:
dicv0={}
dicl0={l:l for l in self.gamma.leglist()}
spaces={}
vertdata=[]
maxleg=max(self.gamma.leglist()+[0])+1
genera=[]
legs=[]
edges = self.gamma.edges()
numvert = sum(s.gamma.num_verts() for s in t)
poly = onekppoly(numvert)
for i in range(len(t)):
s = t[i]
gr = s.gamma
start = len(genera)
genera += gr.genera(copy=False)
dicv0.update({j:i for j in range(start, len(genera))})
renamedic = {j+1: self.gamma.legs(i, copy=False)[j] for j in range(self.gamma.num_legs(i))}
for (e0,e1) in gr.edges():
renamedic[e0]=maxleg
renamedic[e1]=maxleg+1
maxleg+=2
legs+=[[renamedic[l] for l in ll] for ll in gr._legs]
edges+=[(renamedic[e0],renamedic[e1]) for (e0,e1) in gr._edges]
spaces.update({j:[genera[j],HurwitzData(trivgp,[(trivgpel,1,0) for counter in range(len(legs[j]))])] for j in range(start, len(genera))})
vertdata+=[[j,{legs[j][lcount]:lcount+1 for lcount in range(len(legs[j]))}] for j in range(start,len(genera))]
grpoly=deepcopy(s.poly)
grpoly.rename_legs(renamedic)
grpoly.expand_vertices(start,numvert)
poly*=grpoly
gamma=stgraph(genera,legs,edges)
result.terms.append(decHstratum(gamma,spaces,vertdata,dicv0,dicl0,poly))
return result
def factor_pullback(self,vertices,prodcl):
other=prodtautclass(self.gamma)
defaultterms = [other.terms[0][i] for i in range(self.gamma.num_verts())]
other.terms=[]
for t in prodcl.terms:
newterm=deepcopy(defaultterms)
for j in range(len(vertices)):
newterm[vertices[j]]=t[j]
other.terms.append(newterm)
return self*other
def __neg__(self):
return (-1)*self
def __add__(self,other):
if other==0:
return deepcopy(self)
new=deepcopy(self)
new.terms+=deepcopy(other.terms)
return new.consolidate()
def __radd__(self,other):
if other==0:
return self
else:
return self+other
def __iadd__(self,other):
if other==0:
return self
if not isinstance(other,prodtautclass) or other.gamma != self.gamma:
raise ValueError("summing two prodtautclasses on different graphs!")
self.terms+=other.terms
return self
def __mul__(self,other):
return self.__rmul__(other)
def __imul__(self,other):
if isinstance(other,prodtautclass):
self.terms=(self.__rmul__(other)).terms
return self
else:
for t in self.terms:
t[0]*=other
return self
def __rmul__(self,other):
if isinstance(other,prodtautclass):
if other.gamma != self.gamma:
raise ValueError("product of two prodtautclass on different graphs!")
result = prodtautclass(self.gamma,[])
for T1 in self.terms:
for T2 in other.terms:
vertextautcl=[]
for v in range(self.gamma.num_verts()):
vertextautcl.append((T1[v]*T2[v]).terms)
for choice in itertools.product(*vertextautcl):
result+=prodtautclass(self.gamma,[list(choice)])
return result
else:
new = deepcopy(self)
for t in new.terms:
t[0]=other*t[0]
return new
def dimension_filter(self):
for t in self.terms:
for s in t:
s.dimension_filter()
return self.consolidate()
def consolidate(self):
revrange=list(range(len(self.terms)))
revrange.reverse()
for i in revrange:
for s in self.terms[i]:
if len(s.poly.monom)==0:
self.terms.pop(i)
break
return self
def __repr__(self):
s='Outer graph : ' + repr(self.gamma) +'\n'
for i in range(len(self.terms)):
for j in range(len(self.terms[i])):
s+='Vertex '+repr(j)+' :\n'+repr(self.terms[i][j])+'\n'
s+='\n\n'
return s.rstrip('\n\n')
def factor_reconstruct(self,m, otherfactors):
r"""
Assuming that the prodtautclass is a product v_0 x ... x v_(s-1) of tautclasses v_i on the
vertices, this returns the mth factor v_m assuming that otherfactors=[v_1, ..., v_(m-1), v_(m+1), ... v_(s-1)]
is the list of factors on the vertices not equal to m.
EXAMPLES::
sage: from admcycles.admcycles import StableGraph, prodtautclass, psiclass, kappaclass, fundclass
sage: Gamma = StableGraph([1,2,1],[[1,2],[3,4],[5]],[(2,3),(4,5)])
sage: pt = prodtautclass(Gamma,protaut=[psiclass(1,1,2),kappaclass(2,2,2),fundclass(1,1)])
sage: res = pt.factor_reconstruct(1,[psiclass(1,1,2),fundclass(1,1)])
sage: res
Graph : [2] [[1, 2]] []
Polynomial : 1*(kappa_2^1 )_0
NOTE::
This method works by computing intersection numbers in the other factors. This could be replaced
with computing a basis in the tautological ring of these factors.
In principle, it is also possible to reconstruct all factors v_i (up to scaling) just assuming
that the prodtautclass is a pure tensor (product of pullbacks from factors).
"""
s = self.gamma.num_verts()
if not len(otherfactors) == s-1:
raise ValueError('otherfactors must have exactly one entry for each vertex not equal to m')
otherindices = list(range(s))
otherindices.remove(m)
othertestclass={}
otherintersection=[]
for i in range(s-1):
gv, nv, rv = otherfactors[i].gnr_list()[0]
rvcomplement = 3*gv-3+nv-rv
for tc in tautgens(gv,nv,rvcomplement):
intnum = (tc*otherfactors[i]).evaluate()
if intnum != 0:
othertestclass[otherindices[i]]=tc
otherintersection.append(intnum)
break
result = tautclass([])
for t in self.terms:
result+=prod([(t[oi]*othertestclass[oi]).evaluate() for oi in otherindices])*tautclass([t[m]])
result.simplify()
return 1/prod(otherintersection,QQ(1)) * result
def totensorTautbasis(self,r,vecout=False):
if self.gamma.num_verts() == 1:
g = self.gamma.genera(0)
n = self.gamma.num_legs(0)
result = vector(QQ,len(generating_indices(g,n,r)))
for t in self.terms:
result+=t[0].toTautbasis(g,n,r)
return result
if self.gamma.num_verts() == 2:
g1 = self.gamma.genera(0)
n1 = self.gamma.num_legs(0)
g2 = self.gamma.genera(1)
n2 = self.gamma.num_legs(1)
rmax1 = 3*g1 - 3 + n1
rmax2 = 3*g2 - 3 + n2
rmin=max(0,r-rmax2)
rmax=min(r,rmax1)
result=[matrix(QQ,0,0) for i in range(rmin)]
for r1 in range(rmin, rmax+1):
M=matrix(QQ,len(generating_indices(g1,n1,r1)),len(generating_indices(g2,n2,r-r1)))
for t in self.terms:
vec1=t[0].toTautbasis(g1,n1,r1)
vec2=t[1].toTautbasis(g2,n2,r-r1)
M+=matrix(vec1).transpose()*matrix(vec2)
result.append(M)
result+=[matrix(QQ,0,0) for i in range(rmax+1, r+1)]
if vecout:
return vector([M[i,j] for M in result for i in range(M.nrows()) for j in range(M.ncols())])
else:
return result
if self.gamma.num_verts() > 2:
print('totensorTautbasis not yet implemented on graphs with more than two vertices')
return 0
def converttoTautvect(D,g=None,n=None,r=None):
if isinstance(D,tautclass):
if len(D.terms)==0:
if (g is None or n is None or r is None):
print('Unable to identify g, n, r for empty tautclass')
return 0
else:
return vector(QQ,DR.num_strata(g,r,tuple(range(1, n+1))))
if g is None:
g=D.terms[0].gamma.g()
if n is None:
n=len(D.terms[0].gamma.list_markings())
if r is None:
polydeg=D.terms[0].poly.deg()
if polydeg is not None:
r = D.terms[0].gamma.num_edges() + polydeg
result=converttoTautvect(D.terms[0],g,n,r)
for i in range(1, len(D.terms)):
result+=converttoTautvect(D.terms[i],g,n,r)
return result
if isinstance(D,decstratum):
D.dimension_filter()
if g is None:
g=D.gamma.g()
if n is None:
n=len(D.gamma.list_markings())
if len(D.poly.monom)==0:
if (r is None):
print('Unable to identify r for empty decstratum')
return 0
else:
return vector(QQ,DR.num_strata(g,r,tuple(range(1, n+1))))
if r is None:
polydeg=D.poly.deg()
if polydeg is not None:
r = D.gamma.num_edges() + polydeg
length=DR.num_strata(g,r,tuple(range(1, n+1)))
markings=tuple(range(1, n+1))
result = vector(QQ,length)
graphdegree = D.gamma.num_edges()
for (kappa,psi,coeff) in D.poly:
if graphdegree + sum([sum((j+1)*kvec[j] for j in range(len(kvec))) for kvec in kappa])+sum(psi.values()) == r:
try:
pare=coeff.parent()
except:
pare=QQ
result+=vector(pare,length,{DR.num_of_stratum(Pixtongraph(D.gamma,kappa,psi),g,r,markings):coeff})
return result
def Pixtongraph(G,kappa,psi):
firstcol=[-1]
for v in range(G.num_verts()):
entry = G.genera(v)
for k in range(len(kappa[v])):
entry+=kappa[v][k]*(DR.X)**(k+1)
firstcol.append(entry)
columns=[firstcol]
for l in G.list_markings():
vert=G.vertex(l)
entry = 1
if l in psi:
entry+=psi[l]*DR.X
columns.append([l]+[0 for i in range(vert)]+[entry]+[0 for i in range(G.num_verts()-vert-1)])
for (e0,e1) in G.edges(copy=False):
v0=G.vertex(e0)
v1=G.vertex(e1)
if v0==v1:
entry=2
if (psi.get(e0,0)!=0) and (psi.get(e1,0)==0):
entry+=psi[e0]*DR.X
if (psi.get(e1,0)!=0) and (psi.get(e0,0)==0):
entry+=psi[e1]*DR.X
if (psi.get(e0,0)!=0) and (psi.get(e1,0)!=0):
if psi[e0]>=psi[e1]:
entry+=psi[e0]*DR.X + psi[e1]*(DR.X)**2
else:
entry+=psi[e1]*DR.X + psi[e0]*(DR.X)**2
columns.append([0]+[0 for i in range(v0)]+[entry]+[0 for i in range(G.num_verts()-v0-1)])
else:
col = [0 for i in range(G.num_verts()+1)]
entry = 1
if e0 in psi:
entry+=psi[e0]*DR.X
col[v0+1]=entry
entry = 1
if e1 in psi:
entry+=psi[e1]*DR.X
col[v1+1]=entry
columns.append(col)
M=matrix(columns).transpose()
return DR.Graph(M)
def Tautvecttobasis(v,g,n,r,moduli='st'):
if (2 *g-2 +n<=0) or (r<0) or (r>3 *g-3 +n):
return vector([])
vecs=genstobasis(g,n,r)
res=vector(QQ,len(vecs[0]))
for i in range(len(v)):
if v[i]!=0:
res+=v[i]*vecs[i]
if moduli == 'st':
return res
else:
W = op_subset_space(g,n,r,moduli)
if not res:
return W.zero()
reslen = len(res)
return sum(res[i] * W(vector(QQ,reslen,{i:1})) for i in range(reslen) if not res[i].is_zero())
@cached_function
def genstobasis(g, n, r):
generating_indices(g, n, r, True)
return genstobasis(g, n, r)
def Tautv_to_tautclass(v,g,n,r):
strata = DR.all_strata(g,r, tuple(range(1, n+1)))
stratmod=[]
for i in range(len(v)):
if v[i]==0:
continue
currstrat=Graphtodecstratum(strata[i])
currstrat=v[i]*currstrat
stratmod.append(currstrat)
return tautclass(stratmod)
def Tautvb_to_tautclass(v,g,n,r):
genind=generating_indices(g,n,r)
strata = DR.all_strata(g, r, tuple(range(1, n+1)))
stratmod=[]
for i in range(len(v)):
if v[i]==0:
continue
currstrat=Graphtodecstratum(strata[genind[i]])
currstrat.poly*=v[i]
stratmod.append(currstrat)
return tautclass(stratmod)
class prodHclass(object):
r"""
A sum of gluing pushforwards of pushforwards of fundamental classes of
Hurwitz spaces under products of forgetful morphisms.
This is all relative to a fixed stable graph gamma0.
"""
def __init__(self, gamma0, terms):
self.gamma0 = gamma0.copy(mutable=False)
self.terms = terms
def __neg__(self):
return (-1)*self
def __add__(self,other):
if other==0:
return deepcopy(self)
if isinstance(other,prodHclass) and other.gamma0==self.gamma0:
new=deepcopy(self)
new.terms+=deepcopy(other.terms)
return new.consolidate()
def __radd__(self,other):
if other==0:
return self
else:
return self+other
def __iadd__(self,other):
if other==0:
return self
if isinstance(other,prodHclass) and other.gamma0==self.gamma0:
self.terms+=other.terms
else:
raise ValueError("sum of prodHclasses on different graphs")
return self
def __mul__(self,other):
if isinstance(other,tautclass) or isinstance(other,decstratum):
return self.__rmul__(other)
else:
new=deepcopy(self)
for t in new.terms:
t[0]=other*t[0]
return new
def __rmul__(self,other):
if isinstance(other,tautclass) or isinstance(other,decstratum):
pbother = self.gamma0.boundary_pullback(other)
pbother = pbother.toprodHclass()
result = prodHclass(deepcopy(self.gamma0),[])
for t in pbother.terms:
temppullback = self.gamma0_pullback(t.gamma,t.dicv0,t.dicl0)
for s in temppullback.terms:
s.poly*=t.poly.graphpullback(s.dicv0,s.dicl0)
s.dicv0={v: t.dicv0[s.dicv0[v]] for v in s.dicv0}
s.dicl0={l: s.dicl0[t.dicl0[l]] for l in t.dicl0}
result.terms+=temppullback.terms
return result
else:
new=deepcopy(self)
for t in new.terms:
t[0]=other*t[0]
return new
def evaluate(self):
return sum([t.evaluate() for t in self.terms])
def toprodtautclass(self):
result = prodtautclass(self.gamma0, [])
for t in self.terms:
tempres=decstratum(deepcopy(t.gamma),poly=deepcopy(t.poly))
tempres=tempres.convert_to_prodtautclass()
spacelist={a:[] for a in t.spaces}
for v in range(t.gamma.num_verts()):
spacelist[t.vertdata[v][0]].append(v)
for a in spacelist:
if spacelist[a]==[]:
spacelist.pop(a)
for a in spacelist:
usedmarks=set()
for v in spacelist[a]:
usedmarks.update(set(t.vertdata[v][1].values()))
usedmarks=list(usedmarks)
usedmarks.sort()
rndic={usedmarks[i]:i+1 for i in range(len(usedmarks))}
Hclass=Hidentify(t.spaces[a][0],t.spaces[a][1],markings=usedmarks)
Hclass.rename_legs(rndic,rename=True)
legdics = [{l: rndic[t.vertdata[v][1][l]] for l in t.gamma.legs(v, copy=False)} for v in spacelist[a]]
diagclass = forgetful_diagonal(t.spaces[a][0], len(usedmarks), [t.gamma.legs(v, copy=False) for v in spacelist[a]], legdics, T=Hclass)
tempres=tempres.factor_pullback(spacelist[a],diagclass)
tempres=tempres.partial_pushforward(self.gamma0,t.dicv0,t.dicl0)
result+=tempres
return result
def gamma0_pullback(self,gamma1,dicv=None,dicl=None):
gamma0=self.gamma0
if dicv is None:
if gamma0.num_verts() == 1:
dicv={v:0 for v in range(gamma1.num_verts())}
else:
raise RuntimeError('dicv not uniquely determined')
if dicl is None:
if gamma0.num_edges() == 0:
dicl={l:l for l in gamma0.leglist()}
else:
raise RuntimeError('dicl not uniquely determined')
imdicl=dicl.values()
extraedges=[e for e in gamma1.edges(copy=False) if e[0] not in imdicl]
delta_e = gamma1.num_edges() - gamma0.num_edges()
if delta_e != len(extraedges):
print('Warning: edge numbers')
global exampullb
exampullb=(deepcopy(self),deepcopy(gamma1),deepcopy(dicv),deepcopy(dicl))
raise ValueError('Edge numbers')
edgeorder=list(range(delta_e))
contredges=[extraedges[i] for i in edgeorder]
contrgraph=[gamma1]
actvert=[]
edgegraphs=[]
vnumbers=[]
contrdicts=[]
vimages=[dicv[v] for v in range(gamma1.num_verts())]
count=0
for e in contredges:
gammatild = contrgraph[count].copy()
(av,edgegraph,vnum,diccv) = gammatild.contract_edge(e,adddata=True)
gammatild.set_immutable()
contrgraph.append(gammatild)
actvert.append(av)
edgegraphs.append(edgegraph)
vnumbers.append(vnum)
contrdicts.append(diccv)
if len(vnum)==2:
vimages.pop(vnum[1])
count+=1
contrgraph.reverse()
actvert.reverse()
edgegraphs.reverse()
vnumbers.reverse()
contrdicts.reverse()
result=deepcopy(self)
result.gamma0=contrgraph[0]
dicvisom={dicv[vimages[v]]:v for v in range(gamma0.num_verts())}
diclisom={dicl[l]:l for l in dicl}
for t in result.terms:
t.dicv0={v:dicvisom[t.dicv0[v]] for v in t.dicv0}
t.dicl0={l:t.dicl0[diclisom[l]] for l in diclisom}
for edgenumber in range(delta_e):
newresult=prodHclass(contrgraph[edgenumber+1],[])
gam0=contrgraph[edgenumber]
av=actvert[edgenumber]
diccv=contrdicts[edgenumber]
diccvinv={diccv[v]:v for v in diccv}
vextractdic={v:vnumbers[edgenumber][v] for v in range(len(vnumbers[edgenumber]))}
for t in result.terms:
gamma=t.gamma
actvertices=[v for v in range(gamma.num_verts()) if t.dicv0[v]==av]
(gammaprime,dicvextract,diclextract) =gamma.extract_subgraph(actvertices,outgoing_legs=[t.dicl0[l] for l in gam0.legs(av, copy=False)], rename=False)
dicvextractinv={dicvextract[v]:v for v in dicvextract}
egraph = edgegraphs[edgenumber].copy()
eshift=max(list(t.dicl0.values())+[0])+1
egraph.rename_legs(t.dicl0,shift=eshift)
egraph.set_immutable()
commdeg=common_degenerations(gammaprime,egraph,modiso=True,rename=True)
(e0,e1) = egraph.edges(copy=False)[0]
for (gammadeg,dv1,dl1,dv2,dl2) in commdeg:
if gammadeg.num_edges() == gammaprime.num_edges():
term=deepcopy(t)
numvert = gamma.num_verts()
dl1inverse={dl1[l]:l for l in dl1}
term.poly*=-(psicl(dl1inverse[dl2[e0]],numvert)+psicl(dl1inverse[dl2[e1]],numvert))
dv1inverse={dv1[v]:v for v in dv1}
term.dicv0={v:diccvinv[term.dicv0[v]] for v in term.dicv0}
term.dicv0.update({v:vextractdic[dv2[dv1inverse[dicvextract[v]]]] for v in actvertices})
term.dicl0[e0-eshift]= dl1inverse[dl2[e0]]
term.dicl0[e1-eshift]= dl1inverse[dl2[e1]]
newresult.terms.append(term)
else:
vactiveprime=dv1[gammadeg.vertex(dl2[e0])]
vactive=dicvextractinv[vactiveprime]
(a,spacedicl)=t.vertdata[vactive]
(gsp,Hsp)=t.spaces[a]
numHmarks = trivGgraph(gsp,Hsp).gamma.num_legs(0)
vactiveprimepreim=[gammadeg.vertex(dl2[e0]),gammadeg.vertex(dl2[e1])]
if vactiveprimepreim[0]==vactiveprimepreim[1]:
vactiveprimepreim.pop(1)
(egr,dvex,dlex) = gammadeg.extract_subgraph(vactiveprimepreim,outgoing_legs=[dl1[le] for le in gammaprime.legs(vactiveprime, copy=False)], rename=False, mutable=True)
rndic = {dl1[l]: spacedicl[l] for l in gammaprime.legs(vactiveprime, copy=False)}
egr.rename_legs(rndic,shift=numHmarks+1)
egr.set_immutable()
degenlist = Hbdrystructures(gsp,Hsp,egr)
for (Gr,mult,degendicv,degendicl) in degenlist:
term=deepcopy(t)
e = egr.edges(copy=False)[0]
speciale_im = term.replace_space_by_Gstgraph(a,Gr,specialv=vactive,speciale=(degendicl[e[0]], degendicl[e[1]]))
term.poly*=mult
dl1inverse={dl1[l]:l for l in dl1}
eindex = e.index(dl2[e0]+numHmarks+1)
term.dicl0[e0-eshift]= speciale_im[eindex]
term.dicl0[e1-eshift]= speciale_im[1 -eindex]
term.dicv0=dicv_reconstruct(term.gamma,contrgraph[edgenumber+1],term.dicl0)
newresult.terms.append(term)
result=newresult
return result
def consolidate(self):
return self
def __repr__(self):
s='Outer graph : ' + repr(self.gamma0) +'\n'
for t in self.terms:
s+=repr(t) + '\n\n'
return s.rstrip('\n\n')
def Hbdrystructures(g,H,bdry):
preHbdry, N = preHbdrystructures(g, H)
if bdry.num_verts() == 1:
try:
tempresult=preHbdry[(g)]
except:
return []
(e0,e1) = bdry.edges(copy=False)[0]
result=[]
for (Gr,mult,dicv,dicl) in tempresult:
result.append([Gr,mult,dicv,{e0:dicl[N+1], e1:dicl[N+2]}])
return result
if bdry.num_verts() == 2:
presentmarks=bdry.list_markings()
forgetmarks=[i for i in range(1, N+1) if i not in presentmarks]
possi=[[0,1] for j in forgetmarks]
(e0,e1) = bdry.edges(copy=False)[0]
lgs = bdry.legs(copy=True)
ve0 = bdry.vertex(e0)
lgs[ve0].remove(e0)
lgs[1 -ve0].remove(e1)
result=[]
for distri in itertools.product(*possi):
lgscpy=deepcopy(lgs)
lgscpy[0]+=[forgetmarks[j] for j in range(len(forgetmarks)) if distri[j]==0]
lgscpy[1]+=[forgetmarks[j] for j in range(len(forgetmarks)) if distri[j]==1]
if bdry.genera(0) > bdry.genera(1) or (bdry.genera(0) == bdry.genera(1) and 1 in lgscpy[1]):
vert=1
else:
vert=0
ed=(ve0+vert)%2
try:
tempresult = preHbdry[(bdry.genera(vert), tuple(sorted(lgscpy[vert])))]
for (Gr,mult,dicv,dicl) in tempresult:
result.append((Gr,mult,{j:(dicv[j]+vert)%2 for j in dicv},{e0:dicl[N+1 +ed], e1:dicl[N+2 -ed]}))
except KeyError:
pass
return result
@cached_function
def preHbdrystructures(g, H):
Hbdry=list_Hstrata(g,H,1)
if len(Hbdry)==0:
return ({},len(trivGgraph(g,H).gamma.list_markings()))
N=len(Hbdry[0].gamma.list_markings())
result={}
for Gr in Hbdry:
AutGr=equiGraphIsom(Gr,Gr)
edgs = Gr.gamma.edges()
while edgs:
econtr=edgs[0]
multiplicity=0
for (dicv,dicl) in AutGr:
try:
edgs.remove((dicl[econtr[0]],dicl[econtr[1]]))
multiplicity+=1
except:
pass
contrGr = Gr.gamma.copy(mutable=True)
diccv = {j:j for j in range(contrGr.num_verts())}
for e in Gr.gamma.edges(copy=False):
if e != econtr:
(v0, d1, d2,diccv_new) = contrGr.contract_edge(e,True)
diccv = {j:diccv_new[diccv[j]] for j in diccv}
if contrGr.num_verts() == 1:
if g not in result:
result[(g)]=[]
result[(g)].append([Gr,QQ(multiplicity)/len(AutGr),{j:0 for j in range(Gr.gamma.num_verts())},{N+1: econtr[0], N+2: econtr[1]}])
result[(g)].append([Gr,QQ(multiplicity)/len(AutGr),{j:0 for j in range(Gr.gamma.num_verts())},{N+2: econtr[0], N+1: econtr[1]}])
else:
if contrGr.genera(0) > contrGr.genera(1) or (contrGr.genera(0) == contrGr.genera(1) and 1 in contrGr.legs(1, copy=False)):
switched=1
else:
switched=0
eswitched=(contrGr.vertex(econtr[0])+switched)%2
gprime = contrGr.genera(switched)
markns = contrGr.legs(switched, copy=True)
markns.remove(econtr[eswitched])
markns.sort()
markns=tuple(markns)
try:
result[(gprime,markns)].append([Gr,QQ(multiplicity)/len(AutGr),{v:(diccv[v]+switched)%2 for v in diccv},{N+1: econtr[eswitched], N+2: econtr[1 -eswitched]}])
except KeyError:
result[(gprime,markns)]=[[Gr,QQ(multiplicity)/len(AutGr),{v:(diccv[v]+switched)%2 for v in diccv},{N+1: econtr[eswitched], N+2: econtr[1 -eswitched]}]]
if contrGr.genera(0) == contrGr.genera(1) and N == 0:
result[(gprime,markns)].append([Gr,QQ(multiplicity)/len(AutGr),{v:(diccv[v]+1 -switched)%2 for v in diccv},{N+2: econtr[eswitched], N+1: econtr[1 -eswitched]}])
return (result,N)
class decHstratum(object):
def __init__(self,gamma,spaces,vertdata,dicv0=None,dicl0=None,poly=None):
self.gamma=gamma
self.spaces=spaces
self.vertdata=vertdata
if dicv0 is None:
self.dicv0={v:0 for v in range(gamma.num_verts())}
else:
self.dicv0=dicv0
if dicl0 is None:
self.dicl0={l:l for l in gamma.list_markings()}
else:
self.dicl0=dicl0
if poly is None:
self.poly = onekppoly(gamma.num_verts())
else:
self.poly=poly
def __repr__(self):
return repr((self.gamma,self.spaces,self.vertdata,self.dicv0,self.dicl0,self.poly))
def __neg__(self):
return (-1)*self
def __rmul__(self,other):
new=deepcopy(self)
new.poly*=other
return new
def prodforgetpullback(self,spacelist):
alist=list(spacelist)
decs=decstratum(self.gamma,poly=self.poly)
splitdecs=decs.split()
N={a: self.spaces[a][1].nummarks() for a in spacelist}
forgetlegs = [list(set(range(1, N[self.vertdata[v][0]]+1))-set(self.vertdata[v][1].values())) for v in range(self.gamma.num_verts())]
resultgraph=stgraph([self.spaces[a][0] for a in spacelist],[list(range(1, N[a]+1)) for a in N],[])
trivgraphs = [stgraph([self.gamma.genera(i)],[list(self.vertdata[i][1].values())],[]) for i in range(self.gamma.num_verts())]
splitdecs=[[s[i].rename_legs(self.vertdata[i][1]) for i in range(len(s))] for s in splitdecs]
splitdecs=[[decstratum(trivgraphs[i],poly=s[i]) for i in range(len(s))] for s in splitdecs]
result=prodtautclass(resultgraph,[])
for s in splitdecs:
term=prodtautclass(resultgraph)
for j in range(len(alist)):
t=prod([s[i].forgetful_pullback(forgetlegs[i]) for i in spacelist[alist[j]]])
t.dimension_filter()
t=t.toprodtautclass(self.spaces[alist[j]][0],N[alist[j]])
term=term.factor_pullback([j],t)
result+=term
return result
def evaluate(self):
spacelist={a:[] for a in self.spaces}
for v in range(self.gamma.num_verts()):
spacelist[self.vertdata[v][0]].append(v)
for a in spacelist:
if spacelist[a]==[]:
spacelist.pop(a)
alist=list(spacelist)
pfp=self.prodforgetpullback(spacelist)
Gr=[trivGgraph(*self.spaces[a]) for a in spacelist]
prodGr=[prodHclass(gr.gamma,[gr.to_decHstratum()]) for gr in Gr]
result=0
for t in pfp.terms:
tempresult=1
for i in range(len(alist)):
if t[i].gamma.edges()==[]:
tempresult*=(Hdecstratum(Gr[i],poly=t[i].poly)).quotient_pushforward().evaluate()
else:
tempresult*=(prodGr[i]*t[i]).evaluate()
result+=tempresult
return result
def replace_space_by_Gstgraph(self,a,Gr,specialv=None,speciale=None):
avertices = [v for v in range(self.gamma.num_verts()) if self.vertdata[v][0] == a]
avertices.reverse()
gluein=Gr.to_decHstratum()
maxspacelabel=max(self.spaces)
self.spaces.pop(a)
self.spaces.update({l+maxspacelabel+1: gluein.spaces[l] for l in gluein.spaces})
for v in avertices:
gluegraph = gluein.gamma.copy()
dicl_v=self.vertdata[v][1]
usedlegs=dicl_v.values()
unusedlegs=[l for l in gluegraph.list_markings() if l not in usedlegs]
gluegraph.forget_markings(unusedlegs)
(forgetdicv,forgetdicl,forgetdich)=gluegraph.stabilize()
forgetdicl.update({l:l for l in gluegraph.leglist() if l not in forgetdicl})
dicl_v_inverse={dicl_v[l]:l for l in dicl_v}
shift=max(list(dicl_v)+[0])+1
gluegraph.rename_legs(dicl_v_inverse,shift)
divGr={}
divs={}
dil={}
numvert_old = self.gamma.num_verts()
num_newvert = gluegraph.num_verts()
dic_voutgoing = {l:l for l in self.gamma.legs(v, copy=False)}
self.gamma = self.gamma.copy()
self.gamma.glue_vertex(v,gluegraph,divGr,divs,dil)
self.gamma.set_immutable()
dil.update(dic_voutgoing)
dil_inverse={dil[l]:l for l in dil}
if specialv==v:
speciale_im=(dil[forgetdich.get(speciale[0],speciale[0])+shift],dil[forgetdich.get(speciale[1],speciale[1])+shift])
self.vertdata.pop(v)
for w in range(num_newvert):
(pre_b,pre_diclb)=gluein.vertdata[forgetdicv[w]]
b=pre_b+maxspacelabel+1
diclb={l:pre_diclb[forgetdicl[dicl_v.get(dil_inverse[l],dil_inverse[l]-shift)]] for l in self.gamma.legs(w+numvert_old-1, copy=False)}
self.vertdata.append([b,diclb])
w=self.dicv0.pop(v)
for vnumb in range(v, numvert_old-1):
self.dicv0[vnumb]=self.dicv0[vnumb+1]
for vnumb in range(numvert_old-1, numvert_old - 1 + num_newvert):
self.dicv0[vnumb]=w
newpoly=kppoly([],[])
for (kappa,psi,coeff) in self.poly:
trunckappa=deepcopy(kappa)
kappav=trunckappa.pop(v)
trunckappa+=[[] for i in range(num_newvert)]
trunckppoly=kppoly([(trunckappa,psi)],[coeff])
trunckppoly*=prod([(sum([kappacl(numvert_old-1+k, j+1, numvert_old+num_newvert-1) for k in range(num_newvert)]))**kappav[j] for j in range(len(kappav))])
newpoly+=trunckppoly
self.poly=newpoly
spacesused = {a: False for a in self.spaces}
for a, _ in self.vertdata:
spacesused[a] = True
unusedspaces = [a for a in spacesused if not spacesused[a]]
degree=1
for a in unusedspaces:
trivGraph=trivGgraph(*self.spaces.pop(a))
if trivGraph.dim()==0:
degree*=trivGraph.delta_degree(0)
else:
degree=0
break
self.poly*=degree
if specialv is not None:
return speciale_im
def forgetful_diagonal(g,n,leglists,legdics,T=None):
r=len(leglists)
if T is None:
T=tautclass([decstratum(stgraph([g],[list(range(1, n+1))],[]))])
gamma=stgraph([g for i in range(r)],[list(range(1, n+1)) for i in range(r)],[])
result=prodtautclass(gamma,[[decstratum(stgraph([g],[list(range(1, n+1))],[])) for i in range(r)]])
forgetlegs=[[j for j in range(1, n+1) if j not in legdics[i].values()] for i in range(r)]
fl=[len(forg) for forg in forgetlegs]
rndics=[{legdics[i][leglists[i][j]]: j+1 for j in range(len(leglists[i]))} for i in range(r)]
if r >1:
for rdeg in range(0, 2 *(3 *g-3 +n)+1):
if not cohom_is_taut(g,n,rdeg):
print("In computation of diagonal : H^%s(bar M_%s,%n) not necessarily tautological" % (rdeg,g,n))
Dgamma=stgraph([g,g],[list(range(1, n+1)),list(range(1, n+1))],[])
Delta=prodtautclass(Dgamma,[])
for deg in range(0, (3 *g-3 +n)+1):
gi1=generating_indices(g,n,deg)
gi2=generating_indices(g,n,3 *g-3 +n-deg)
strata1 = DR.all_strata(g,deg,tuple(range(1, n+1)))
gens1=[Graphtodecstratum(strata1[j]) for j in gi1]
strata2 = DR.all_strata(g,3 *g-3 +n-deg,tuple(range(1, n+1)))
gens2=[Graphtodecstratum(strata2[j]) for j in gi2]
invintmat=inverseintmat(g,n,tuple(gi1),tuple(gi2),deg)
for a in range(len(gi1)):
for b in range(len(gi2)):
if invintmat[a,b] != 0:
Delta.terms.append([invintmat[a,b]*gens1[a],gens2[b]])
for i in range(1, r):
result=result.factor_pullback([0,i],Delta)
result=result.factor_pullback([0],T.toprodtautclass(g,n))
for t in result.terms:
for i in range(r):
t[i]=t[i].forgetful_pushforward(forgetlegs[i])
t[i].rename_legs(rndics[i])
result.gamma=stgraph([g for i in range(r)],deepcopy(leglists),[])
return result.consolidate()
@cached_function
def inverseintmat(g,n,gi1,gi2,deg):
if deg <= QQ(3 *g-3 +n)/2:
intmat=matrix(DR.pairing_submatrix(gi1,gi2,g,deg,tuple(range(1, n+1))))
else:
intmat=matrix(DR.pairing_submatrix(gi2,gi1,g,3 *g-3 +n-deg,tuple(range(1, n+1)))).transpose()
return intmat.transpose().inverse()
def inverseintmat2(g,n,gi1,gi2,deg):
tg1=tautgens(g,n,deg)
tg2=tautgens(g,n,3 *g-3 +n-deg)
intmat=matrix([[(tg1[i]*tg2[j]).evaluate() for i in gi1] for j in gi2])
return intmat.transpose().inverse()
""" # remove all terms that must vanish for dimension reasons
def dimension_filter(self):
for i in range(len(self.poly.monom)):
(kappa,psi)=self.poly.monom[i]
for v in range(len(self.gamma.genera)):
# local check: we are not exceeding dimension at any of the vertices
if sum([(k+1)*kappa[v][k] for k in range(len(kappa[v]))])+sum([psi[l] for l in self.gamma.legs(v) if l in psi])>3*self.gamma.genera(v)-3+len(self.gamma.legs(v)):
self.poly.coeff[i]=0
break
# global check: the total codimension of the term of the decstratum is not higher than the total dimension of the ambient space
#if self.poly.deg(i)+len(self.gamma.edges)>3*self.gamma.g()-3+len(self.gamma.list_markings()):
# self.poly.coeff[i]=0
#break
return self.consolidate()
def consolidate(self):
self.poly.consolidate()
return self """
""" # computes integral against the fundamental class of the corresponding moduli space
# will not complain if terms are mixed degree or if some of them do not have the right codimension
def evaluate(self):
answer=0
for (kappa,psi,coeff) in self.poly:
temp=1
for v in range(len(self.gamma.genera)):
psilist=[psi.get(l,0) for l in self.gamma.legs(v)]
kappalist=[]
for j in range(len(kappa[v])):
kappalist+=[j+1 for k in range(kappa[v][j])]
if sum(psilist+kappalist) != 3*self.gamma.genera(v)-3+len(psilist):
temp = 0
break
temp*=DR.socle_formula(self.gamma.genera(v),psilist,kappalist)
answer+=coeff*temp
return answer """
""" def rename_legs(self,dic):
self.gamma.rename_legs(dic)
self.poly.rename_legs(dic)
return self
def __repr__(self):
return 'Graph : ' + repr(self.gamma) +'\n'+ 'Polynomial : ' + repr(self.poly) """
"""
if len(erg.genera)==1:
# loop graph, add all missing markings to it
egr.legs(0)+=forgetmarks
divlist=[egr]
if len(erg.genera)==2:
possi=[[0,1] for j in forgetmarks]
divlist=[]
for distri in itertools.product(*possi):
# distri = (0,0,1,0,1,1) means that forgetmarks number 0,1,3 go to erg.legs(0) and 2,4,5 go to erg.legs(1)
ergcpy=deepcopy(erg)
ergcpy.legs(0)+=[forgetmarks[j] for j in range(len(forgetmarks)) if distri[j]==0]
ergcpy.legs(1)+=[forgetmarks[j] for j in range(len(forgetmarks)) if distri[j]==1]
divlist.append(ergcpy)
"""
def dicv_reconstruct(Gamma, A, dicl):
if A.num_verts() == 1:
return {ver:0 for ver in range(Gamma.num_verts())}
else:
dicv={Gamma.vertex(dicl[h]): A.vertex(h) for h in dicl}
remaining_vertices=set(range(Gamma.num_verts()))-set(dicv)
remaining_edges=set([e for e in Gamma.edges(copy=False) if e[0] not in dicl.values()])
current_vertices=set(dicv)
while remaining_vertices:
newcurrent_vertices=set()
for e in deepcopy(remaining_edges):
if Gamma.vertex(e[0]) in current_vertices:
vnew=Gamma.vertex(e[1])
remaining_edges.remove(e)
if vnew not in current_vertices:
dicv[vnew]=dicv[Gamma.vertex(e[0])]
remaining_vertices.discard(vnew)
newcurrent_vertices.add(vnew)
continue
if Gamma.vertex(e[1]) in current_vertices:
vnew=Gamma.vertex(e[0])
remaining_edges.remove(e)
if vnew not in current_vertices:
dicv[vnew]=dicv[Gamma.vertex(e[1])]
remaining_vertices.discard(vnew)
newcurrent_vertices.add(vnew)
current_vertices=newcurrent_vertices
return dicv
def cohom_is_taut(g, n, r):
if g == 0:
return True
if g == 1 and r % 2 == 0:
return True
if g == 1 and n < 10:
return True
if g == 1 and n >= 11 and r == 11:
return False
if g == 2 and r % 2 == 0 and n < 20:
return True
if g == 2 and n <= 3:
return True
if g == 2 and n == 4:
return True
if g == 3 and n == 0:
return True
if g == 3 and n == 1:
return True
if g == 3 and n == 2:
return True
if g == 4 and n == 0:
return True
if r in [0, 2]:
return True
if r in [1, 3, 5]:
return True
if 2 * (3 * g - 3 + n) - r in [0, 1, 2, 3, 5]:
return True
return None
def FZ_conjecture_holds(g, n, d):
if g == 0:
return True
if g == 1:
return True
PICKLED_FUNCTIONS = [generating_indices, genstobasis]
def save_FZrels():
"""
Saving previously computed Faber-Zagier relations to file new_geninddb.pkl.
TESTS::
sage: from admcycles import *
sage: generating_indices(0,3,0)
[0]
sage: admcycles.genstobasis(0,3,0)
[(1)]
sage: cache1=deepcopy(dict(generating_indices.cache))
sage: cache2=deepcopy(dict(admcycles.genstobasis.cache))
sage: save_FZrels()
sage: generating_indices.clear_cache()
sage: admcycles.genstobasis.clear_cache()
sage: load_FZrels()
sage: cache1==dict(generating_indices.cache)
True
sage: cache2==dict(admcycles.genstobasis.cache)
True
"""
with open('new_geninddb.pkl', 'wb') as file:
dumped = {fun[0]: dict(fun[1].cache)
for fun in enumerate(PICKLED_FUNCTIONS)}
pickle.dump(dumped, file)
def load_FZrels():
"""
Loading values
"""
try:
with open('new_geninddb.pkl', 'rb') as file:
dumped = pickle.load(file)
for k, cache in dumped.items():
PICKLED_FUNCTIONS[k].cache.update(cache)
except IOError:
raise IOError('Could not find file new_geninddb.pkl\nIf you previously saved FZ relations in geninddb.pkl, try old_load_FZrels() followed by save_FZrels() to go to new format.\nType old_load_FZrels? for more info.')
def old_load_FZrels():
"""
Loading values from old format in 'geninddb.pkl'.
If you previously saved FZ relations to this file, you should first call old_load_FZrels(), followed by save_FZrels() to save them to the new location 'new_geninddb.pkl'. From now on, you can load the relations from this new file using load_FZrels().
Once you are certain that the transfer worked, you can delete the file 'geninddb.pkl'.
"""
pkl_file=open('geninddb.pkl','rb')
dumpdic=pickle.load(pkl_file)
for k, cache in dumpdic.items():
if k[0]=='generating_indices':
generating_indices.set_cache(cache, *(k[1]))
if k[0]=='genstobasis':
genstobasis.set_cache(cache, *(k[1]))
pkl_file.close()
def Hdb_lookup(g,dat,markings):
li=dat.l
sort_li=sorted(enumerate(li),key=lambda x:x[1])
ind_reord=[i[0] for i in sort_li]
li_reord=[i[1] for i in sort_li]
dat_reord=HurwitzData(dat.G,li_reord)
mn_list=[]
num_marks=0
Gord=dat.G.order()
for count in range(len(li)):
new_marks=QQ(Gord)/li[count][1]
mn_list.append(list(range(num_marks+1, num_marks+new_marks+1)))
mn_reord=[]
for i in ind_reord:
mn_reord.append(mn_list[i])
legpermute={mn_reord[j]:j+1 for j in range(len(mn_reord))}
Hdb=Hdatabase[(g,dat_reord)]
for marks in Hdb:
if set(markings).issubset(set(marks)):
forgottenmarks=set(marks)-set(markings)
result=deepcopy(Hdb[marks])
result=result.forgetful_pushforward(list(forgottenmarks))
found=True
def fundclass(g,n):
r"""
Return the fundamental class of \Mbar_g,n.
"""
if not all(isinstance(i, (int, Integer)) and i >= 0 for i in (g,n)):
raise ValueError("g,n must be non-negative integers")
if 2*g-2+n <= 0:
raise ValueError("g,n must satisfy 2g-2+n>0")
return trivgraph(g,n).to_tautclass()
def tautgens(g,n,r,decst=False):
r"""
Returns a lists of all tautological classes of degree r on \bar M_{g,n}.
INPUT:
g : integer
Genus g of curves in \bar M_{g,n}.
n : integer
Number of markings n of curves in \bar M_{g,n}.
r : integer
The degree r of of the classes.
decst : boolean
If set to True returns generators as decorated strata, else as tautological classes.
EXAMPLES::
sage: from admcycles import *
sage: tautgens(2,0,2)[1]
Graph : [2] [[]] []
Polynomial : 1*(kappa_1^2 )_0
::
sage: L=tautgens(2,0,2);2*L[3]+L[4]
Graph : [1, 1] [[2], [3]] [(2, 3)]
Polynomial : 2*psi_2^1
<BLANKLINE>
Graph : [1] [[2, 3]] [(2, 3)]
Polynomial : 1*(kappa_1^1 )_0
"""
L = DR.all_strata(g,r,tuple(range(1, n+1)))
if decst:
return [Graphtodecstratum(l) for l in L]
else:
return [tautclass([Graphtodecstratum(l)]) for l in L]
def list_tautgens(g,n,r):
r"""
Lists all tautological classes of degree r on \bar M_{g,n}.
INPUT:
g : integer
Genus g of curves in \bar M_{g,n}.
n : integer
Number of markings n of curves in \bar M_{g,n}.
r : integer
The degree r of of the classes.
EXAMPLES::
sage: from admcycles import *
sage: list_tautgens(2,0,2)
[0] : Graph : [2] [[]] []
Polynomial : 1*(kappa_2^1 )_0
[1] : Graph : [2] [[]] []
Polynomial : 1*(kappa_1^2 )_0
[2] : Graph : [1, 1] [[2], [3]] [(2, 3)]
Polynomial : 1*(kappa_1^1 )_0
[3] : Graph : [1, 1] [[2], [3]] [(2, 3)]
Polynomial : 1*psi_2^1
[4] : Graph : [1] [[2, 3]] [(2, 3)]
Polynomial : 1*(kappa_1^1 )_0
[5] : Graph : [1] [[2, 3]] [(2, 3)]
Polynomial : 1*psi_2^1
[6] : Graph : [0, 1] [[3, 4, 5], [6]] [(3, 4), (5, 6)]
Polynomial : 1*
[7] : Graph : [0] [[3, 4, 5, 6]] [(3, 4), (5, 6)]
Polynomial : 1*
"""
L=tautgens(g,n,r)
for i in range(len(L)):
print('['+repr(i)+'] : '+repr(L[i]))
def kappaclass(a, g=None, n=None):
r"""
Returns the (Arbarello-Cornalba) kappa-class kappa_a on \bar M_{g,n} defined by
kappa_a= pi_*(psi_{n+1}^{a+1})
where pi is the morphism \bar M_{g,n+1} --> \bar M_{g,n}.
INPUT:
a : integer
The degree a of the kappa class.
g : integer
Genus g of curves in \bar M_{g,n}.
n : integer
Number of markings n of curves in \bar M_{g,n}.
EXAMPLES::
sage: from admcycles import *
sage: kappaclass(2,3,1)
Graph : [3] [[1]] []
Polynomial : 1*(kappa_2^1 )_0
When working with fixed g and n for the moduli space \bar M_{g,n},
it is possible to specify the desired value of the global variables g
and n using ``reset_g_n`` to avoid giving them as an argument each time::
sage: reset_g_n(3,2)
sage: kappaclass(1)
Graph : [3] [[1, 2]] []
Polynomial : 1*(kappa_1^1 )_0
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
return tautclass([decstratum(trivgraph(g,n), poly=kappacl(0,a,1,g,n))])
def psiclass(i,g=None,n=None):
r"""
Returns the class psi_i on \bar M_{g,n}.
INPUT:
i : integer
The leg i associated to the psi class.
g : integer
Genus g of curves in \bar M_{g,n}.
n : integer
Number of markings n of curves in \bar M_{g,n}.
EXAMPLES::
sage: from admcycles import *
sage: psiclass(2,2,3)
Graph : [2] [[1, 2, 3]] []
Polynomial : 1*psi_2^1
When working with fixed g and n for the moduli space \bar M_{g,n},
it is possible to specify the desired value of the global variables g
and n using ``reset_g_n`` to avoid giving them as an argument each time::
sage: reset_g_n(3,2)
sage: psiclass(1)
Graph : [3] [[1, 2]] []
Polynomial : 1*psi_1^1
TESTS::
sage: psiclass(3,2,1)
Traceback (most recent call last):
...
ValueError: Index of marking for psiclass smaller than number of markings
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
if i > n:
raise ValueError('Index of marking for psiclass smaller than number of markings')
return tautclass([decstratum(trivgraph(g,n), poly=psicl(i,1))])
def sepbdiv(g1,A,g=None,n=None):
r"""
Returns the pushforward of the fundamental class under the boundary gluing map \bar M_{g1,A} X \bar M_{g-g1,{1,...,n} \ A} --> \bar M_{g,n}.
INPUT:
g1 : integer
The genus g1 of the first vertex.
A: list
The list A of markings on the first vertex.
g : integer
The total genus g of the graph.
n : integer
The total number of markings n of the graph.
EXAMPLES::
sage: from admcycles import *
sage: sepbdiv(1,(1,3,4),2,5)
Graph : [1, 1] [[1, 3, 4, 6], [2, 5, 7]] [(6, 7)]
Polynomial : 1*
When working with fixed g and n for the moduli space \bar M_{g,n},
it is possible to specify the desired value of the global variables g
and n using ``reset_g_n`` to avoid giving them as an argument each time::
sage: reset_g_n(3,3)
sage: sepbdiv(1,(2,))
Graph : [1, 2] [[2, 4], [1, 3, 5]] [(4, 5)]
Polynomial : 1*
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
return tautclass([decstratum(stgraph([g1,g-g1],[list(A)+[n+1], list(set(range(1, n+1)) - set(A))+[n+2]],[(n+1,n+2)]))])
def irrbdiv(g=None, n=None):
r"""
Returns the pushforward of the fundamental class under the irreducible boundary gluing map \bar M_{g-1,n+2} -> \bar M_{g,n}.
INPUT:
g : integer
The total genus g of the graph.
n : integer
The total number of markings n of the graph.
EXAMPLES::
sage: from admcycles import *
sage: irrbdiv(2,5)
Graph : [1] [[1, 2, 3, 4, 5, 6, 7]] [(6, 7)]
Polynomial : 1*
When working with fixed g and n for the moduli space \bar M_{g,n},
it is possible to specify the desired value of the global variables g
and n using ``reset_g_n`` to avoid giving them as an argument each time::
sage: reset_g_n(3,0)
sage: irrbdiv()
Graph : [2] [[1, 2]] [(1, 2)]
Polynomial : 1*
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
return tautclass([decstratum(stgraph([g-1], [list(range(1, n+3))], [(n+1, n+2)]))])
@cached_function
def hodge_chern_char(g, n, d):
bdries=list_strata(g,n,1)
irrbdry=bdries.pop(0)
autos = [bd.automorphism_number() for bd in bdries]
result = tautgens(g,n,0)[0]
if d==0 :
return deepcopy(tautgens(g,n,0)[0])
elif (d%2 == 0) or (d<0):
return tautclass([])
else:
psipsisum_onevert=sum([((-1)**i)*(psicl(n+1, 1)**i)*(psicl(n+2,1)**(d-1-i)) for i in range(d)])
psipsisum_twovert=sum([((-1)**i)*(psicl(n+1, 2)**i)*(psicl(n+2, 2)**(d-1-i)) for i in range(d)])
contrib=kappaclass(d,g,n)-sum([psiclass(i,g,n)**d for i in range(1, n+1)])
contrib+=(QQ(1)/2)*tautclass([decstratum(irrbdry,poly=psipsisum_onevert)])
contrib+=sum([(QQ(1)/autos[ind])*tautclass([decstratum(bdries[ind], poly=psipsisum_twovert)])for ind in range(len(bdries))])
contrib.dimension_filter()
return (bernoulli(d+1)/factorial(d+1)) * contrib
def chern_char_to_poly(chclass,dmax,g,n):
result=deepcopy(tautgens(g,n,0)[0])
argum=sum([factorial(m-1)*chclass(m) for m in range(1, dmax+1)])
expo=deepcopy(argum)
result=result+argum
for m in range(2, dmax+1):
expo*=argum
expo.degree_cap(dmax)
result+=(QQ(1)/factorial(m))*expo
return result
def chern_char_to_class(t,char,g=None,n=None):
r"""
Turns a Chern character into a Chern class in the tautological ring of \Mbar_{g,n}.
INPUT:
t : integer
degree of the output Chern class
char : tautclass or function
Chern character, either represented as a (mixed-degree) tautological class or as
a function m -> ch_m
EXAMPLES:
Note that the output of generalized_hodge_chern takes the form of a chern character::
sage: from admcycles import *
sage: from admcycles.GRRcomp import *
sage: g=2;n=2;l=0;d=[1,-1];a=[[1,[1],-1]]
sage: chern_char_to_class(1,generalized_hodge_chern(l,d,a,1,g,n))
Graph : [2] [[1, 2]] []
Polynomial : 1/12*(kappa_1^1 )_0
<BLANKLINE>
Graph : [2] [[1, 2]] []
Polynomial : (-13/12)*psi_1^1
<BLANKLINE>
Graph : [2] [[1, 2]] []
Polynomial : (-1/12)*psi_2^1
<BLANKLINE>
Graph : [0, 2] [[1, 2, 4], [5]] [(4, 5)]
Polynomial : 1/12*
<BLANKLINE>
Graph : [1, 1] [[4], [1, 2, 5]] [(4, 5)]
Polynomial : 1/12*
<BLANKLINE>
Graph : [1, 1] [[2, 4], [1, 5]] [(4, 5)]
Polynomial : 13/12*
<BLANKLINE>
Graph : [1] [[4, 5, 1, 2]] [(4, 5)]
Polynomial : 1/24*
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
if isinstance(char,tautclass):
arg = [(-1)**(s-1)*factorial(s-1)*char.simplified(r=s) for s in range(1,t+1)]
else:
arg = [(-1)**(s-1)*factorial(s-1)*char(s) for s in range(1,t+1)]
exp = sum(multinomial(s.to_exp())/factorial(len(s))*prod(arg[k-1] for k in s) for s in Partitions(t))
if t==0:
return exp * fundclass(g, n)
return exp.simplify(r=t)
def lambdaclass_old(d,g=None,n=None):
r"""
Old implementation of lambda_d on \bar M_{g,n} defined as the d-th Chern class
lambda_d = c_d(E)
of the Hodge bundle E. The result is represented as a sum of stable graphs with kappa and psi classes.
INPUT:
d : integer
The degree d.
g : integer
Genus g of curves in \bar M_{g,n}.
n : integer
Number of markings n of curves in \bar M_{g,n}.
EXAMPLES::
sage: from admcycles.admcycles import lambdaclass_old
sage: lambdaclass_old(1,2,1)
Graph : [2] [[1]] []
Polynomial : 1/12*(kappa_1^1 )_0
<BLANKLINE>
Graph : [2] [[1]] []
Polynomial : (-1/12)*psi_1^1
<BLANKLINE>
Graph : [1, 1] [[3], [1, 4]] [(3, 4)]
Polynomial : 1/12*
<BLANKLINE>
Graph : [1] [[3, 4, 1]] [(3, 4)]
Polynomial : 1/24*
When working with fixed g and n for the moduli space \bar M_{g,n},
it is possible to specify the desired value of the global variables g
and n using ``reset_g_n`` to avoid giving them as an argument each time::
sage: from admcycles import reset_g_n
sage: reset_g_n(1,1)
sage: lambdaclass_old(1)
Graph : [1] [[1]] []
Polynomial : 1/12*(kappa_1^1 )_0
<BLANKLINE>
Graph : [1] [[1]] []
Polynomial : (-1/12)*psi_1^1
<BLANKLINE>
Graph : [0] [[3, 4, 1]] [(3, 4)]
Polynomial : 1/24*
TESTS::
sage: from admcycles.admcycles import lambdaclass, lambdaclass_old
sage: L1 = lambdaclass_old(3, 3, 0)
sage: L2 = lambdaclass(3, 3, 0)
sage: (L1 - L2).toTautvect().is_zero()
True
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
if d>g:
return tautclass([])
def chcl(m):
return hodge_chern_char(g,n,m)
cherpoly=chern_char_to_poly(chcl,d,g,n)
result=cherpoly.degree_part(d)
result.simplify(g,n,d)
return result
def lambdaclass(d, g=None, n=None, pull=True):
r"""
Returns the tautological class lambda_d on \bar M_{g,n} defined as the d-th Chern class
lambda_d = c_d(E)
of the Hodge bundle E. The result is represented as a sum of stable graphs with kappa and psi classes.
INPUT:
d : integer
The degree d.
g : integer
Genus g of curves in \bar M_{g,n}.
n : integer
Number of markings n of curves in \bar M_{g,n}.
pull : boolean, default = True
Whether lambda_d on \bar M_{g,n} is computed as pullback from \bar M_{g}
EXAMPLES::
sage: from admcycles import *
sage: lambdaclass(1,2,0)
Graph : [2] [[]] []
Polynomial : 1/12*(kappa_1^1 )_0
<BLANKLINE>
Graph : [1, 1] [[2], [3]] [(2, 3)]
Polynomial : 1/24*
<BLANKLINE>
Graph : [1] [[2, 3]] [(2, 3)]
Polynomial : 1/24*
When working with fixed g and n for the moduli space \bar M_{g,n},
it is possible to specify the desired value of the global variables g
and n using ``reset_g_n`` to avoid giving them as an argument each time::
sage: reset_g_n(1,1)
sage: lambdaclass(1)
Graph : [1] [[1]] []
Polynomial : 1/12*(kappa_1^1 )_0
<BLANKLINE>
Graph : [1] [[1]] []
Polynomial : (-1/12)*psi_1^1
<BLANKLINE>
Graph : [0] [[3, 4, 1]] [(3, 4)]
Polynomial : 1/24*
TESTS::
sage: from admcycles import lambdaclass
sage: inputs = [(0,0,4), (1,1,3), (1,2,1), (2,2,1), (3,2,1), (-1,2,1), (2,3,2)]
sage: for d,g,n in inputs:
....: assert (lambdaclass(d, g, n)-lambdaclass(d, g, n, pull=False)).is_zero()
"""
if g is None:
g=globals()['g']
if n is None:
n=globals()['n']
if d > g or d < 0:
return tautclass([])
if n > 0 and pull:
if g == 0:
return fundclass(g,n)
if g == 1:
newmarks = list(range(2, n+1))
return lambdaclass(d, 1, 1, pull=False).forgetful_pullback(newmarks)
newmarks = list(range(1, n+1))
return lambdaclass(d, g, 0, pull=False).forgetful_pullback(newmarks)
def chcl(m):
return hodge_chern_char(g,n,m)
return chern_char_to_class(d,chcl,g,n)
def barH(g,dat,markings=None):
"""Returns \\bar H on genus g with Hurwitz datum dat as a prodHclass on the trivial graph, remembering only the marked points given in markings"""
Hbar = trivGgraph(g,dat)
n = len(Hbar.gamma.list_markings())
if markings is None:
markings = list(range(1, n + 1))
N=len(markings)
msorted=sorted(markings)
markingdictionary={i+1: msorted[i] for i in range(N)}
return prodHclass(stgraph([g],[list(range(1, N+1))],[]),[decHstratum(stgraph([g],[list(range(1 ,N+1))],[]), {0: (g,dat)}, [[0 ,markingdictionary]])])
def Hyperell(g,n=0 ,m=0):
"""Returns the cycle class of the hyperelliptic locus of genus g curves with n marked fixed points and m pairs of conjugate points in \\barM_{g,n+2m}.
TESTS::
sage: from admcycles import *
sage: H=Hyperell(3) # long time
sage: H.toTautbasis() # long time
(3/4, -9/4, -1/8)
"""
if n>2 *g+2:
print('A hyperelliptic curve of genus '+repr(g)+' can only have '+repr(2 *g+2)+' fixed points!')
return 0
if 2 *g-2 +n+2 *m<=0:
print('The moduli space \\barM_{'+repr(g)+','+repr(n+2 *m)+'} does not exist!')
return 0
G=PermutationGroup([(1, 2)])
H=HurData(G,[G[1] for i in range(2 *g+2)]+[G[0] for i in range(m)])
marks=list(range(1, n+1))+list(range(2 *g+2 + 1, 2 *g+2 + 1 +2 *m))
factor=QQ(1)/factorial(2 *g+2 -n)
result= factor*Hidentify(g,H,markings=marks)
rndict={i:i for i in range(1, n+1)}
rndict.update({j:j-(2 *g+2 -n) for j in range(2*g+2+1, 2*g+2+1+2*m)})
result.rename_legs(rndict,True)
return result
def Biell(g,n=0 ,m=0):
r"""
Returns the cycle class of the bielliptic locus of genus ``g`` curves with ``n`` marked fixed points and ``m`` pairs of conjugate points in `\bar M_{g,n+2m}`.
TESTS::
sage: from admcycles import *
sage: B=Biell(2) # long time
sage: B.toTautbasis() # long time
(15/2, -9/4)
"""
if g==0:
print('There are no bielliptic curves of genus 0!')
return 0
if n>2 *g-2:
print('A bielliptic curve of genus '+repr(g)+' can only have '+repr(2 *g-2)+' fixed points!')
return 0
if 2 *g-2 +n+2 *m<=0:
print('The moduli space \\barM_{'+repr(g)+','+repr(n+2 *m)+'} does not exist!')
return 0
G=PermutationGroup([(1, 2)])
H=HurData(G,[G[1] for i in range(2 * g - 2)]+[G[0] for i in range(m)])
marks=list(range(1, n+1))+list(range(2*g-2+1, 2*g-2+1+2*m))
factor= QQ((1,factorial(2 *g-2 -n)))
if g==2 and n==0 and m==0:
factor/=2
result= factor*Hidentify(g,H,markings=marks)
rndict={i:i for i in range(1, n+1)}
rndict.update({j:j-(2 *g-2 -n) for j in range(2*g-2+1, 2*g-2+1+2*m)})
result.rename_legs(rndict,True)
return result
def Hpullpush(g,dat,alpha):
"""Pulls the class alpha to the space \\bar H_{g,dat} via map i forgetting the action, then pushes forward under delta."""
Hb = Htautclass([Hdecstratum(trivGgraph(g,dat),poly=onekppoly(1))])
Hb = Hb * alpha
return Hb.quotient_pushforward()
def FZreconstruct(g,n,r):
genind=generating_indices(g,n,r)
gentob=genstobasis(g,n,r)
numgens=len(gentob)
M=[]
for i in range(numgens):
v=insertvec(gentob[i],numgens,genind)
v[i]-=1
M.append(v)
return matrix(M)
def insertvec(w,length,positions):
v=vector(QQ,length)
for j in range(len(positions)):
v[positions[j]]=w[j]
return v
def pullpushtest(g,dat,r):
r"""Test if for Hurwitz space specified by (g,dat), pulling back codimension r relations under the source map and pushing forward under the target map gives relations.
"""
Gr=trivGgraph(g,dat)
n=Gr.gamma.n()
Grquot=Gr.quotient_graph()
gquot=Grquot.g()
nquot=Grquot.n()
M=FZreconstruct(g,n,r)
N=matrix([Hpullpush(g,dat,alpha).toTautbasis(gquot,nquot,r) for alpha in tautgens(g,n,r)])
return (N.transpose()*M.transpose()).is_zero()
for v in M.rows():
if not v.is_zero():
alpha=Tautv_to_tautclass(v,g,n,r)
pb=Hpullpush(g,dat,alpha)
print(pb.is_zero())
def checkintnum(g,n,r):
markings=tuple(range(1, n+1))
Mpixton=DR.pairing_matrix(g,r,markings)
strata1=[Graphtodecstratum(Grr) for Grr in DR.all_strata(g,r,markings)]
strata2=[Graphtodecstratum(Grr) for Grr in DR.all_strata(g,3 *g-3 +n-r,markings)]
Mself=[[(s1*s2).evaluate() for s2 in strata2] for s1 in strata1]
return Mpixton==Mself
def pullandidentify(g,n,r):
markings=tuple(range(1, n+1))
M=DR.FZ_matrix(g,r,markings)
Mconv=matrix([DR.convert_vector_to_monomial_basis(M.row(i),g,r,markings) for i in range(M.nrows())])
L=list_strata(g,n,1)
strata = DR.all_strata(g, r, markings)
decst=[Graphtodecstratum(Grr) for Grr in strata]
for l in L:
pullbacks=[((l.boundary_pullback(st)).dimension_filter()).totensorTautbasis(r,True) for st in decst]
for i in range(Mconv.nrows()):
vec=0 *pullbacks[0]
for j in range(Mconv.ncols()):
if Mconv[i,j]!=0:
vec+=Mconv[i,j]*pullbacks[j]
if not vec.is_zero():
return False
return True
def pushpullcompat(g,n1,n2,r1):
r2=3 *g-3 +n2-r1
strata_down=[tautclass([Graphtodecstratum(Grr)]) for Grr in DR.all_strata(g,r1,tuple(range(1, n1+1)))]
strata_up=[tautclass([Graphtodecstratum(Grr)]) for Grr in DR.all_strata(g,r2,tuple(range(1, n2+1)))]
fmarkings=list(range(n1+1, n2+1))
for class_down in strata_down:
for class_up in strata_up:
intersection_down = (class_down*class_up.forgetful_pushforward(fmarkings)).evaluate()
intersection_up = (class_down.forgetful_pullback(fmarkings) * class_up).evaluate()
if not intersection_down == intersection_up:
return False
return True
def deltapullpush(g,H,r):
trivGg=trivGgraph(g,H)
trivGclass=Htautclass([Hdecstratum(trivGg)])
ddeg=trivGg.delta_degree(0)
quotgr=trivGg.quotient_graph()
gprime = quotgr.genera(0)
bprime = quotgr.num_legs(0)
gens=tautgens(gprime,bprime,r)
for cl in gens:
clpp=trivGclass.quotient_pullback(cl).quotient_pushforward()
if not (clpp.toTautvect(gprime,bprime,r)==ddeg*cl.toTautvect(gprime,bprime,r)):
return False
return True
def lambdaintnumcheck(g):
intnum=((lambdaclass(g,g,0)*lambdaclass(g-1, g, 0)).simplify()*lambdaclass(g-2 ,g, 0)).evaluate()
result= QQ((-1,2)) / factorial(2 *(g-1)) * bernoulli(2 *(g-1)) / (2*(g-1))*bernoulli(2 *g) / (2 *g)
return intnum==result
@cached_function
def op_subset_space(g,n,r,moduli):
r"""
Returns a vector space W over QQ which is the quotient W = V / U, where
V = space of vectors representing classes in R^r(\Mbar_{g,n}) in a basis
U = subspace of vectors representing classes supported outside the open subset
of \Mbar_{g,n} specified by moduli (= 'sm', 'rt', 'ct' or 'tl')
Thus for v in V, the vector W(v) is the representation of the class represented by v
in a basis of R^r(M^{moduli}_{g,n}).
"""
L = tautgens(g,n,r)
Msm=[(0*L[0]).toTautbasis(g,n,r)]
if moduli == 'st':
def modtest(gr):
return False
if moduli == 'sm':
def modtest(gr):
return bool(gr.num_edges())
if moduli == 'rt':
def modtest(gr):
return g not in gr.genera(copy = False)
if moduli == 'ct':
def modtest(gr):
return gr.num_edges() - gr.num_verts() + 1 != 0
if moduli == 'tl':
def modtest(gr):
return gr.num_edges() - gr.num_loops() - gr.num_verts() + 1 != 0
for t in L:
if modtest(t.terms[0].gamma):
Msm.append(t.toTautbasis())
MsmM=matrix(QQ,Msm)
U=MsmM.row_space()
W=U.ambient_vector_space()/U
return W
r"""
EXAMPLES::
sage: from admcycles import *
sage: from admcycles.admcycles import pullpushtest, deltapullpush, checkintnum, pullandidentify, pushpullcompat, lambdaintnumcheck
sage: G=PermutationGroup([(1,2)])
sage: H=HurData(G,[G[1],G[1]])
sage: pullpushtest(2,H,1)
True
sage: pullpushtest(2,H,2) # long time
True
sage: deltapullpush(2,H,1)
True
sage: deltapullpush(2,H,2) # long time
True
sage: G=PermutationGroup([(1,2,3)])
sage: H=HurData(G,[G[0]])
sage: c=Hidentify(1,H)
sage: c.toTautbasis()
(5, -3, 2, 2, 2)
sage: checkintnum(2,0,1)
True
sage: checkintnum(2,1,2)
True
sage: pullandidentify(2,1,2)
True
sage: pullandidentify(3,0,2)
True
sage: pushpullcompat(2,0,1,2) # long time
True
sage: lambdaintnumcheck(2)
True
sage: lambdaintnumcheck(3) # long time
True
"""