https://github.com/uberparagon/mgn
Tip revision: 87eacb93177c9d41edb525bb71ae03ae45f18d14 authored by Drew Johnson on 20 March 2020, 16:48:33 UTC
added a ps and ps_ member to strataalgebra
added a ps and ps_ member to strataalgebra
Tip revision: 87eacb9
stratagraph.py
from __future__ import print_function, absolute_import
import itertools
from sage.all import PolynomialRing, var, factorial, Matrix, ZZ, cached_function, copy, Permutations, SetPartitions, prod, SR, sage, Integer, operator
class StrataGraph(object):
R = PolynomialRing(ZZ,"X",1,order='lex')
Xvar = R.gen()
def __init__(self,M=None,genus_list=None):
#print genus_list
if M:
self.M = copy(M)
elif genus_list:
self.M = Matrix(StrataGraph.R,len(genus_list)+1,1,[-1]+genus_list)
else:
self.M = Matrix(StrataGraph.R,1,1,-1)
def __repr__(self):
"""
Drew added this.
"""
name = self.nice_name()
if name == None:
return repr(self.nice_matrix())
else:
return name
def num_vertices(self):
return self.M.nrows() - 1
def num_edges(self):
return self.M.ncols() - 1
def h1(self):
return self.M.ncols()-self.M.nrows()+1
def add_vertex(self,g):
self.M = self.M.stack(Matrix(1,self.M.ncols()))
self.M[-1,0] = g
def add_edge(self,i1,i2,marking=0):
self.M = self.M.augment(Matrix(self.M.nrows(),1))
self.M[0,-1] = marking
if i1 > 0:
self.M[i1,-1] += 1
if i2 > 0:
self.M[i2,-1] += 1
def del_vertex(self,i):
self.M = self.M[:i].stack(self.M[(i+1):])
def del_edge(self,i):
self.M = self.M[0:,:i].augment(self.M[0:,(i+1):])
def compute_degree_vec(self):
self.degree_vec = [sum(self.M[i,j][0] for j in range(1,self.M.ncols())) for i in range(1,self.M.nrows())]
def degree(self,i):
return self.degree_vec[i-1]
def split_vertex(self,i,row1,row2):
self.M = self.M.stack(Matrix(2,self.M.ncols(),row1+row2))
self.add_edge(self.M.nrows()-2, self.M.nrows()-1)
self.del_vertex(i)
def compute_parity_vec_original(self):
X = StrataGraph.Xvar
self.parity_vec = [(ZZ(1+self.M[i,0][0]+sum(self.M[i,k][1] for k in range(1,self.M.ncols()))+sum(self.M[i,k][2] for k in range(1,self.M.ncols()))+self.M[i,0].derivative(X).substitute(X=1)) % 2) for i in range(1,self.M.nrows())]
def compute_parity_vec(self):
self.parity_vec = [(ZZ(1+self.M[i,0][0]+sum(self.M[i,k][1] for k in range(1,self.M.ncols()))+sum(self.M[i,k][2] for k in range(1,self.M.ncols()))+self.last_parity_summand(i)) % 2) for i in range(1,self.M.nrows())]
def last_parity_summand(self,i):
return sum(( expon[0] * coef for expon, coef in self.M[i,0].dict().items() ))
def parity(self,i):
return self.parity_vec[i-1]
def replace_vertex_with_graph(self,i,G):
X = StrataGraph.Xvar
nv = self.num_vertices()
ne = self.num_edges()
#i should have degree d, there should be no classes near i, and G should have markings 1,...,d and genus equal to the genus of i
hedge_list = []
for k in range(1,self.M.ncols()):
for j in range(ZZ(self.M[i,k])):
hedge_list.append(k)
self.del_vertex(i)
for j in range(G.num_edges() - len(hedge_list)):
self.add_edge(0,0)
for j in range(G.num_vertices()):
self.add_vertex(G.M[j+1,0])
col = ne+1
for k in range(1,G.M.ncols()):
if G.M[0,k] > 0:
mark = ZZ(G.M[0,k])
for j in range(G.num_vertices()):
if self.M[nv+j,hedge_list[mark-1]] == 0:
self.M[nv+j,hedge_list[mark-1]] = G.M[j+1,k]
elif G.M[j+1,k] != 0:
a = self.M[nv+j,hedge_list[mark-1]][1]
b = G.M[j+1,k][1]
self.M[nv+j,hedge_list[mark-1]] = 2 + max(a,b)*X + min(a,b)*X**2
else:
for j in range(G.num_vertices()):
self.M[nv+j,col] = G.M[j+1,k]
col += 1
def compute_invariant(self):
self.compute_parity_vec()
self.compute_degree_vec()
nr,nc = self.M.nrows(),self.M.ncols()
self.invariant = [[self.M[i,0], [], [], [[] for j in range(1,nr)]] for i in range(1,nr)]
for k in range(1,nc):
L = [i for i in range(1,nr) if self.M[i,k] != 0]
if len(L) == 1:
if self.M[0,k] != 0:
self.invariant[L[0]-1][2].append([self.M[0,k],self.M[L[0],k]])
else:
self.invariant[L[0]-1][1].append(self.M[L[0],k])
else:
self.invariant[L[0]-1][3][L[1]-1].append([self.M[L[0],k],self.M[L[1],k]])
self.invariant[L[1]-1][3][L[0]-1].append([self.M[L[1],k],self.M[L[0],k]])
for i in range(1,nr):
self.invariant[i-1][3] = [term for term in self.invariant[i-1][3] if len(term) > 0]
for term in self.invariant[i-1][3]:
term.sort()
self.invariant[i-1][3].sort()
self.invariant[i-1][2].sort()
self.invariant[i-1][1].sort()
vertex_invariants = [[i,self.invariant[i-1]] for i in range(1,nr)]
self.invariant.sort()
vertex_invariants.sort(key=lambda x: x[1])
self.vertex_groupings = []
for i in range(nr-1):
if i == 0 or vertex_invariants[i][1] != vertex_invariants[i-1][1]:
self.vertex_groupings.append([])
self.vertex_groupings[-1].append(vertex_invariants[i][0])
#Drew added this
self.invariant = tupleit(self.invariant)
self.hash = hash(self.invariant)
def __eq__(self,other):
"""
Drew added this.
"""
return graph_isomorphic(self, other)
def __hash__(self):
"""
Drew added this.
Note that it returns a value stored when "compute_invariant" is called.
"""
try:
return self.hash
except:
self.compute_invariant()
return self.hash
def codim(self):
codim = 0
for v in range(1, self.num_vertices()+1):
for expon, coef in self.M[v,0].dict().items():
codim += expon[0]*coef
for e in range(1, self.num_edges()+1):
for expon, coef in self.M[v,e].dict().items():
if expon[0] > 0:
codim += coef
for e in range(1, self.num_edges()+1):
if self.M[0,e] == 0:
codim +=1
return codim
def codim_undecorated(self):
codim = 0
for e in range(1, self.num_edges()+1):
if self.M[0,e] == 0:
codim +=1
return codim
print("not implemented!!!!!!")
return 1
def kappa_on_v(self,v):
"""
Drew added this.
"""
#print "kappa",self.M[v,0].dict()
for ex, coef in self.M[v,0].dict().items():
if ex[0] != 0:
yield ex[0], coef
def psi_no_loop_on_v(self,v):
for edge in range(1,self.num_edges()+1):
if self.M[v,edge][0] == 1:
psi_expon = self.M[v,edge][1]
if psi_expon > 0:
yield edge, psi_expon
def psi_loop_on_v(self,v):
for edge in range(1,self.num_edges()+1):
if self.M[v,edge][0] == 2:
psi_expon1 = self.M[v,edge][1]
psi_expon2 = self.M[v,edge][2]
if psi_expon1 > 0:
yield edge, psi_expon1, psi_expon2
def moduli_dim_v(self,v):
"""
Drew added this.
"""
return 3*self.M[v,0][0]-3 + sum(( self.M[v,j][0] for j in range(1, self.num_edges()+1 ) ))
def num_loops(self):
count = 0
for edge in range(1, self.num_edges()+1):
for v in range(1, self.num_vertices()+1):
if self.M[v,edge][0] == 2:
count+=1
break
return count
def num_undecorated_loops(self):
count = 0
for edge in range(1, self.num_edges()+1):
for v in range(1, self.num_vertices()+1):
if self.M[v,edge] == 2:
count+=1
break
return count
def num_full_edges(self):
count = 0
for edge in range(1, self.num_edges()+1):
if sum(( self.M[v,edge][0] for v in range(1, self.num_vertices()+1 ))) == 2:
count += 1
return count
def forget_kappas(self):
M = copy(self.M)
for v in range(1, self.num_vertices()+1):
M[v,0] = M[v,0][0]
return StrataGraph(M)
def forget_decorations(self):
M = Matrix(StrataGraph.R,[[self.M[r,c][0] for c in range(self.M.ncols())] for r in range(self.M.nrows())] )
Gnew = StrataGraph(M)
#Gnew.compute_invariant()
return Gnew
def is_loop(self,edge):
for v in range(1, self.num_vertices()+1):
if self.M[v,edge][0] == 2:
return True
if self.M[v,edge][0] == 1:
return False
raise Exception("Unexpected!")
ps_name = "ps"
ps2_name = "ps_"
def nice_matrix(self):
Mnice = Matrix(SR, self.M.nrows(), self.M.ncols())
for edge in range(1, self.num_edges()+1):
Mnice[0,edge] = self.M[0,edge]
for v in range(1, self.num_vertices()+1):
kappas = 1
for expon, coef in self.M[v,0].dict().items():
if expon[0]==0:
Mnice[v,0] += coef
else:
kappas *= var("ka{0}".format(expon[0]))**coef
if kappas != 1:
Mnice[v,0] += kappas
for edge in range(1, self.num_edges()+1):
psis = 1
for expon, coef in self.M[v,edge].dict().items():
if expon[0]==0:
Mnice[v,edge] += coef
elif expon[0]==1:
psis *= var(StrataGraph.ps_name)**coef
elif expon[0]==2:
psis *= var(StrataGraph.ps2_name)**coef
if psis != 1:
Mnice[v,edge] += psis
return Mnice
@staticmethod
def from_nice_matrix(lists):
Mx = Matrix(StrataGraph.R, len(lists), len(lists[0]))
Mx[0,0]=-1
Mx[0,:] = Matrix([lists[0]])
for v in range(1, len(lists)):
if lists[v][0] in ZZ:
Mx[v,0]=lists[v][0]
continue
if lists[v][0].operator() == sage.symbolic.operators.add_vararg:
operands = lists[v][0].operands()
if len(operands) != 2:
raise Exception("Input error!")
genus = operands[1] #the genus
kappas = operands[0]
else:
kappas = lists[v][0]
genus = 0
if kappas.operator() == sage.symbolic.operators.mul_vararg:
for operand in kappas.operands():
Mx[v,0] += StrataGraph._kappaSR_monom_to_X(operand)
Mx[v,0] += genus
else:
Mx[v,0] = StrataGraph._kappaSR_monom_to_X(kappas) + genus
X = StrataGraph.Xvar
for v in range(1, len(lists)):
for edge in range(1, len(lists[0])):
if lists[v][edge] in ZZ:
Mx[v,edge] = lists[v][edge]
else:
for operand in lists[v][edge].operands():
if operand in ZZ:
Mx[v,edge] += operand
elif operand.operator() is None:
#it is a ps or ps2
Mx[v,edge] += X
elif operand.operator() == operator.pow:
#it is ps^n or ps2^n
Mx[v,edge] += X * operand.operands()[1]
elif operand.operator() == sage.symbolic.operators.mul_vararg:
#it is a ps^n*ps2^m
op1,op2 = operand.operands()
if op1.operator() is None:
exp1 = 1
else:
exp1 = op1.operand()[1]
if op2.operator() == None:
exp2 = 1
else:
exp2 = op2.operand()[1]
if exp1 >= exp2:
Mx[v,edge] += exp1*X + exp2*X**2
else:
Mx[v,edge] += exp1*X**2 + exp2*X
return StrataGraph(Mx)
@staticmethod
def _kappaSR_monom_to_X(expr):
X = StrataGraph.Xvar
if expr in ZZ:
return expr
elif expr.operator() == None:
ka_subscript = Integer(str(expr)[2:])
return X ** ka_subscript
elif expr.operator() == operator.pow:
ops = expr.operands()
expon = ops[1]
ka = ops[0]
ka_subscript = Integer(str(ka)[2:])
return expon * X ** ka_subscript
def nice_name(self):
"""
:return:
"""
if self.num_vertices() == 1:
num_loops = self.num_loops()
if num_loops >1:
return None
elif num_loops == 1:
if self.codim() == 1:
return "D_irr"
else:
return None
#else, there are no loops
var_strs = []
for expon, coef in self.M[1,0].dict().items():
if expon[0] == 0 or coef == 0:
continue
if coef == 1:
var_strs.append("ka{0}".format(expon[0]))
else:
var_strs.append("ka{0}^{1}".format(expon[0],coef))
for he in range(1,self.num_edges()+1): #should only be half edges now
if self.M[1,he][1] > 1:
var_strs.append("ps{0}^{1}".format(self.M[0,he],self.M[1,he][1]))
elif self.M[1,he][1] == 1:
var_strs.append("ps{0}".format(self.M[0,he]))
if len(var_strs) > 0:
return "*".join(var_strs)
else:
return "one"
if self.num_vertices() == 2 and self.num_full_edges() == 1 and self.codim() == 1:
#it is a boundary divisor
v1_marks = [self.M[0,j] for j in range(1, self.num_edges()+1) if self.M[1,j] == 1 and self.M[0,j] != 0]
v1_marks.sort()
v2_marks = [self.M[0,j] for j in range(1, self.num_edges()+1) if self.M[2,j] == 1 and self.M[0,j] != 0]
v2_marks.sort()
if v1_marks < v2_marks:
g = self.M[1,0]
elif v1_marks == v2_marks:
if self.M[1,0] <= self.M[2,0]:
g = self.M[1,0]
else:
g = self.M[2,0]
else:
g = self.M[2,0]
#temp = v1_marks
v1_marks = v2_marks
#v2_marks = temp
if len(v1_marks) == 0:
return "Dg{0}".format(g)
else:
return "Dg{0}m".format(g) + "_".join([str(m) for m in v1_marks])
def tupleit(t):
"""
Drew added this.
Changes a nested list into a nested tuple.
Needed so that we can hash the invariant.
"""
return tuple(map(tupleit, t)) if isinstance(t, list) else t
def graph_isomorphic(G1,G2):
if "invariant" not in G1.__dict__.keys():
G1.compute_invariant()
if "invariant" not in G2.__dict__.keys():
G2.compute_invariant()
if G1.invariant != G2.invariant:
return False
else:
return isomorphic(G1.M,G2.M,G1.vertex_groupings,G2.vertex_groupings)
def isomorphic(M1,M2,group1,group2):
nr,nc = M1.nrows(),M1.ncols()
PermList = [Permutations(list(range(len(group)))) for group in group1]
for sigma_data in itertools.product(*PermList):
sigma = [0 for i in range(nr-1)]
for i in range(len(group1)):
for j in range(len(group1[i])):
sigma[group1[i][j]-1] = group2[i][sigma_data[i][j]]
good = True
for i in range(1,nr):
ii = sigma[i-1]
for j in range(1,i):
jj = sigma[j-1]
L1 = []
for k in range(1,nc):
if M1[i,k] != 0 and M1[j,k] != 0:
L1.append([M1[i,k],M1[j,k]])
L1.sort()
L2 = []
for k in range(1,nc):
if M2[ii,k] != 0 and M2[jj,k] != 0:
L2.append([M2[ii,k],M2[jj,k]])
L2.sort()
if L1 != L2:
good = False
break
if good == False:
break
if good:
return True
return False
@cached_function
def graph_count_automorphisms(G,vertex_orbits=False):
return count_automorphisms(G.M,G.vertex_groupings,vertex_orbits)
def count_automorphisms(M,grouping,vertex_orbits=False):
nr,nc = M.nrows(),M.ncols()
count = 0
PermList = [Permutations(list(range(len(group)))) for group in grouping]
if vertex_orbits:
isom_list = []
for sigma_data in itertools.product(*PermList):
sigma = [0 for i in range(nr-1)]
for i in range(len(grouping)):
for j in range(len(grouping[i])):
sigma[grouping[i][j]-1] = grouping[i][sigma_data[i][j]]
good = True
for i in range(1,nr):
ii = sigma[i-1]
for j in range(1,i):
jj = sigma[j-1]
L1 = []
for k in range(1,nc):
if M[i,k] != 0 and M[j,k] != 0:
L1.append([M[i,k],M[j,k]])
L1.sort()
L2 = []
for k in range(1,nc):
if M[ii,k] != 0 and M[jj,k] != 0:
L2.append([M[ii,k],M[jj,k]])
L2.sort()
if L1 != L2:
good = False
break
if good == False:
break
if good:
count += 1
if vertex_orbits:
isom_list.append(sigma)
if vertex_orbits:
orbit_list = []
vertices_used = []
while len(vertices_used) < nr-1:
i = [ii for ii in range(1,nr) if ii not in vertices_used][0]
orbit = []
for sigma in isom_list:
if sigma[i-1] not in orbit:
orbit.append(sigma[i-1])
vertices_used.append(sigma[i-1])
orbit.sort()
orbit_list.append(orbit)
return orbit_list
for i in range(1,nr):
for k in range(1,nc):
if M[i,k][0] == 2 and M[i,k][1] == M[i,k][2]:
count *= 2
L = []
for k in range(1,nc):
if M[i,k] != 0:
if sum(1 for j in range(1,nr) if M[j,k] != 0) == 1:
L.append([M[0,k],M[i,k]])
count *= aut(L)
for j in range(1,i):
L = []
for k in range(1,nc):
if M[i,k] != 0 and M[j,k] != 0:
L.append([M[i,k],M[j,k]])
count *= aut(L)
return count
def aut(L):
if len(L) == 0:
return 1
L.sort()
total = 1
n = 1
last = L[0]
for l in L[1:]:
if l == last:
n += 1
else:
n = 1
total *= n
last = l
return total
def PsiKappaVars(A, kappa_only = False):
kappa_names = ["kappa{0}_{1}".format(a,v) for v in range(1,A.num_vertices()+1) for a in range(1,A.moduli_dim_v(v)+1)]
if kappa_only:
PsiKappaRing = PolynomialRing(ZZ, kappa_names, len(kappa_names) )
else:
psi_names = ["psi{0}_{1}".format(e,v) for v in range(1,A.num_vertices()+1) for e in range(1,A.num_edges()+1) if A.M[v,e][0] > 0]
psi_names += ["psi{0}_{1}_2".format(e,v) for v in range(1,A.num_vertices()+1) for e in range(1,A.num_edges()+1) if A.M[v,e][0] == 2]
PsiKappaRing = PolynomialRing(ZZ, kappa_names +psi_names)
kappa = {(a,v) : PsiKappaRing.gens_dict()["kappa{0}_{1}".format(a,v)] for v in range(1,A.num_vertices()+1) for a in range(1,A.moduli_dim_v(v)+1) }
#print PsiKappaRing
if kappa_only:
return PsiKappaRing, kappa
else:
psi = {(e,v) : PsiKappaRing.gens_dict()["psi{0}_{1}".format(e,v)] for v in range(1,A.num_vertices()+1) for e in range(1,A.num_edges()+1) if A.M[v,e][0] > 0 }
psi2 = {(e,v) : PsiKappaRing.gens_dict()["psi{0}_{1}_2".format(e,v)] for v in range(1,A.num_vertices()+1) for e in range(1,A.num_edges()+1) if A.M[v,e][0] == 2 }
return PsiKappaRing, kappa, psi, psi2
def X_to_kappa(f,kappa):
"""
f -- A polynomial in X. The constant term is ignored.
kappa -- A function so that kappa(a) is kappa_a.
Returns::
A polynomial in kappas, converting ...
"""
psi_list = []
for expon,coef in f.dict().items():
#print expon[0], coef
if expon[0] !=0:
psi_list += [expon[0]]*coef
#print psi_list
result = 0
for p in SetPartitions(list(range(len(psi_list)))):
#print p
#print [ kappa( sum((psi_list[i] for i in s)) ) for s in p]
result += prod((factorial(len(s) - 1) for s in p)) * prod(( kappa( sum((psi_list[i] for i in s)) ) for s in p ))
return result