From f04847f9de48a2035c2926c0d5c36729f32be3ba Mon Sep 17 00:00:00 2001 From: Steven Holtzen Date: Sun, 24 Feb 2019 15:10:55 -0800 Subject: [PATCH] naive way lol --- my_graphs.py | 73 +++++++++++++++++++ orbitgen.py | 202 +++++++++++++++++++++++++++++---------------------- test.py | 16 +++- 3 files changed, 201 insertions(+), 90 deletions(-) create mode 100644 my_graphs.py diff --git a/my_graphs.py b/my_graphs.py new file mode 100644 index 0000000..4a8b485 --- /dev/null +++ b/my_graphs.py @@ -0,0 +1,73 @@ +from sage.all import * +import itertools + +def findsubsets(S,m): + return set(itertools.combinations(S, m)) + +### generates a friends and smokers graph with n people +def gen_friends_smokers(n): + g = Graph(sparse=True) + # make n smoker vertices + smokers = [x for x in range(0,n)] + # connect all the smokers + smokeredges = findsubsets(smokers, 2) + # make friends + friends = [] + friendedges = [] + count = n + for (s1,s2) in findsubsets(smokers, 2): + friends += [count] + friendedges += [(s1, count), (s2, count)] + count += 1 + + g.add_vertices(smokers) + g.add_vertices(friends) + g.add_edges(friendedges) + g.add_edges(smokeredges) + return g + +# generates a graph which is fully-connected in m and connected across n +def gen_pigeonhole(n,m): + g = Graph() + v = [] + e = [] + # generate vertices + for x in range(0,n): + for y in range(0,m): + v += [x*m + y] + + # generate edges + # generate fully connected graph in n + for x in range(0,n): + for y in findsubsets(range(0, m), 2): + e += [(x*m + y[0], x*m + y[1])] + + # connect between n and m + for x in range(0,n): + for y in range (0,m): + e += [(x*m + y, ((x + 1) % n)*m + y)] + + g.add_vertices(v) + g.add_edges(e) + return g + +# generates a complete graph with n vertices with some extra nodes on each +# vertex +def gen_complete_extra(n): + g = Graph() + v = [] + e = [] + # generate complete graph + for x in range(0,n): + v += [x] + for x in findsubsets(range(0, n), 2): + e += [(x[0], x[1])] + + # now generate an extra vertex for each point and add it + for x in range(0,n): + v += [n + x] + e += [(x, n + x)] + + g.add_vertices(v) + g.add_edges(e) + return g diff --git a/orbitgen.py b/orbitgen.py index 7961a0f..a0bf9c0 100644 --- a/orbitgen.py +++ b/orbitgen.py @@ -1,10 +1,8 @@ from sage.all import * +from my_graphs import * import cProfile, pstats, StringIO -import itertools from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic - -def findsubsets(S,m): - return set(itertools.combinations(S, m)) +from collections import deque ### foldrep: applies f to each canonical representative, maintains an ### accumulator @@ -14,16 +12,13 @@ def findsubsets(S,m): ### colors[1]: False vertices ### colors[3] is reserved for the algorithm ### fold: apply a fold method to each orbit -def foldrep(graph, colors, acc, f, level=0, debug=False): +def foldrep(graph, colors, acc, f): # TODO: reduce number of isomorphism calls if possible acc = f(acc, graph, colors) if len(colors[0]) >= (len(graph.vertices()) / 2): return acc - A, orbits = graph.automorphism_group(partition=colors, orbits=True, algorithm="bliss") - # print("%sorbits: %s" % ("\t"*level, orbits)) + A, orbits = graph.automorphism_group(partition=colors, orbits=True) for o in orbits: - # print("") - # print("%scurrent orbit: %s" % ("\t"*level, o)) e = o[0] # pick the first element in the orbit arbitrarily # check if this orbit is already true if e in colors[0]: @@ -34,14 +29,14 @@ def foldrep(graph, colors, acc, f, level=0, debug=False): Gpcolor = [colors[0] + [e], [c for c in colors[1] if c != e]] Gprime, c = graph.canonical_label(partition=Gpcolor, - certificate=True, algorithm="bliss") + certificate=True) - # print("%smap: %s" % ("\t"*level, c)) # c is a map from old vertices to new vertices; we need to invert it inv_c = {v: k for k, v in c.iteritems()} - # find lexically first vertex whose value is true in the canonical Gprime; - # default to vertex 0 + # compute the canonical deletion of Gprime + # find lexically first vertex whose value is true in the canonical + # Gprime; default to vertex 0. first_t = None for v in Gprime.vertices(sort=True): if inv_c[v] in Gpcolor[0]: @@ -53,14 +48,54 @@ def foldrep(graph, colors, acc, f, level=0, debug=False): canoncolor = [[x for x in Gpcolor[0] if x != first_t], Gpcolor[1], [first_t]] - # print("%scanon color: %s, Dcolor: %s" % ("\t"*level, canoncolor, Dcolor)) - Dcanon = graph.canonical_label(partition=canoncolor, algorithm="bliss") - assert(set(Dcolor[0] + Dcolor[2]) == set(canoncolor[0] + canoncolor[2]) == set(Gpcolor[0])) + Dcanon = graph.canonical_label(partition=canoncolor) if Dcanon == D: - # print("%saccepted %s" % ("\t"*level,Gpcolor)) - acc = foldrep(graph, Gpcolor, acc, f, level=level+1) - # else: - # print("%srejected coloring: %s" % ("\t"*level, Gpcolor)) + acc = foldrep(graph, Gpcolor, acc, f) + return acc + +def bfs_foldrep(graph, colors, acc, f): + # queue of colorings yet to be considered + queue = deque([colors]) + # set of representative graph colorings + reps = set() + while len(queue) != 0: + c = queue.popleft() + # print("----") + # print("Current color: %s" % c) + gcanon, cert = graph.canonical_label(partition=c, certificate=True) + G_immut = Graph(gcanon, immutable=True) + + # convert the colors to their coloring in the canonical graph + c_canon = [[], []] + for c1 in c[0]: + c_canon[0].append(cert[c1]) + for c1 in c[1]: + c_canon[1].append(cert[c1]) + + c_canon[0].sort() + c_canon[1].sort() + + if (G_immut, (tuple(c_canon[0]), tuple(c_canon[1]))) in reps: + continue + acc = f(acc, graph, c) + reps.add((G_immut, (tuple(c_canon[0]), tuple(c_canon[1])))) + # print("added rep: %s" % reps) + + # print(c) + if len(c_canon[0]) + 1 <= len(graph.vertices()) / 2.0: + # if we can add more colors, try to + A, orbits = graph.automorphism_group(partition=c, orbits=True) + # print("orbits: %s" % orbits) + # expand this node and add it to the queue + for o in orbits: + # print("Considering orbit %s" % o) + e = o[0] # pick the first element in the orbit arbitrarily + # check if this orbit is already true + if e in c[0]: + continue + # we know this is an uncolored vertex + newcolor = [c[0] + [e], [x for x in c[1] if x != e]] + queue.append(newcolor) return acc ### genrep: generates a representative of each orbit class of a graph @@ -68,74 +103,56 @@ def genrep(G): colors = [[], [i for i in G.vertices()]] # folding function def add_vertex(acc, g, color): - # check if inversion is isomorphic to the current graph if len(color[0]) < len(g.vertices()) / 2.0: return acc + [color] + [color[::-1]] - # use orbit-stabilizer to see if this is a unique orbit or not - # A, orbits = g.automorphism_group(partition=color, orbits=True) - # check to make sure each orbit has at most a single color - # intersect = False - # print("colors: %s, orbits: %s" % (color, orbits)) - # for o in orbits: - # if len(set(o).intersection(set(color[0])).intersection(set(color[1]))) > 0: - # intersect = True - # break - - # if not intersect: - # print("non-overlapping orbits: %s, colors: %s" % (orbits, color)) - # return acc + [color] + [color[::-1]] - # else: - # # inversion are isomorphic, return one return acc + [color] return foldrep(G, colors, [], add_vertex) -### generates a friends and smokers graph with n people -def gen_friends_smokers(n): - g = Graph(sparse=True) - # make n smoker vertices - smokers = [x for x in range(0,n)] - # connect all the smokers - smokeredges = findsubsets(smokers, 2) - # make friends - friends = [] - friendedges = [] - count = n - for (s1,s2) in findsubsets(smokers, 2): - friends += [count] - friendedges += [(s1, count), (s2, count)] - count += 1 - - g.add_vertices(smokers) - g.add_vertices(friends) - g.add_edges(friendedges) - g.add_edges(smokeredges) - return g - -# generates a graph which is fully-connected in m and connected across n -def gen_pigeonhole(n,m): - g = Graph() - v = [] - e = [] - # generate vertices - for x in range(0,n): - for y in range(0,m): - v += [x*m + y] - - # generate edges - # generate fully connected graph in n - for x in range(0,n): - for y in findsubsets(range(0, m), 2): - e += [(x*m + y[0], x*m + y[1])] - - # connect between n and m - for x in range(0,n): - for y in range (0,m): - e += [(x*m + y, ((x + 1) % n)*m + y)] - - g.add_vertices(v) - g.add_edges(e) - return g +def genrep_bfs(G): + colors = [[], [i for i in G.vertices()]] + # folding function + def add_vertex(acc, g, color): + if len(color[0]) < len(g.vertices()) / 2.0: + return acc + [color] + [color[::-1]] + + return acc + [color] + return bfs_foldrep(G, colors, [], add_vertex) + + +### partition_function +### computes the partition of a fully symmetric MLN the specified parameter +### +### potential: a function which maps variable truth assignments to +### probabilities. This function *must* be invariant wrt. the automorphism +### group of the graph. +def partition(G, potential): + colors = [[], [i for i in G.vertices()]] + # folding function + def sum_prob(acc, g, color): + # TODO: avoid recomputing these two automorphism groups + g_order = g.automorphism_group().order() + aut_order = g.automorphism_group(partition=color).order() + orbit_sz = g_order / aut_order # yay orbit stabilizer theorem! + if len(color[0]) < len(g.vertices()) / 2.0: + return acc + (orbit_sz * potential(color)) + (orbit_sz * potential(color[::-1])) + + return acc + (orbit_sz * potential(color)) + return foldrep(G, colors, 0.0, sum_prob) + +def bruteforce_partition(G, potential): + # evaluate the potential on every assignment to variables + def recurse(c1, c2): + print(c1) + if len(c1) == 0: + return 0 + tot = potential([c1, c2]) + for x in c1: + new_c1 = c1[:] + new_c1.remove(x) + tot += recurse(new_c1, c2 + [x]) + return tot + return recurse(G.vertices(), []) # given a graph g, count the number of distinct 2-colorings using Polya's # theorem. @@ -144,16 +161,25 @@ def gen_pigeonhole(n,m): def count_num_distinct(g): return g.automorphism_group().cycle_index().expand(2)((1,1)) -def main(): + +def compute_partition(): + def partfun(assgn): + return len(assgn[0]) + G = gen_complete_extra(3) + print("partition: %d" % (partition(G, partfun))) + print("brute forced: %d" % (bruteforce_partition(G, partfun))) + +def find_representatives(): # n=3 # pr = cProfile.Profile() # pr.enable() - # G = gen_friends_smokers(3) - G = graphs.CycleGraph(7) - # G = graphs.CompleteGraph(10) + # G = graphs.CompleteGraph(4) + G = gen_friends_smokers(10) + # G = graphs.CycleGraph(20) + # G = gen_complete_extra(3) num_vert = len(G.vertices()) - r = genrep(G) + r = genrep_bfs(G) for x in r: print(x) print("Found configurations: %d" % len(r)) @@ -168,4 +194,4 @@ def main(): if __name__ == "__main__": - main() + find_representatives() diff --git a/test.py b/test.py index ad58c7e..36ca8b1 100644 --- a/test.py +++ b/test.py @@ -1,11 +1,12 @@ from sage.all import * import orbitgen +from my_graphs import * import unittest def count_num_generated(G): colors = [[], [i for i in G.vertices()]] num_vert = len(G.vertices()) - l = orbitgen.genrep(G) + l = orbitgen.genrep_bfs(G) return len(l) class TestGen(unittest.TestCase): @@ -45,9 +46,20 @@ def test_star(self): count_num_generated(G)) def test_friends_smokers(self): - G = orbitgen.gen_friends_smokers(3) + G = gen_friends_smokers(3) self.assertEqual(orbitgen.count_num_distinct(G), count_num_generated(G)) + def test_friends_smokers(self): + G = gen_friends_smokers(4) + self.assertEqual(orbitgen.count_num_distinct(G), + count_num_generated(G)) + + def test_aug_complete(self): + G = gen_complete_extra(8) + self.assertEqual(orbitgen.count_num_distinct(G), + count_num_generated(G)) + + if __name__ == '__main__': unittest.main()