-
-
Save jg-you/ff9ba9bfac1f24ecc2a8f31cc89c5067 to your computer and use it in GitHub Desktop.
| # -*- coding: utf-8 -*- | |
| """ | |
| Wrapper around dreadnaut that computes the orbits of a graph. | |
| NOTE: Must have installed `dreandaut`. The location of the binary can be passed | |
| as an argument to `compute_automorphisms`. | |
| Author: Jean-Gabriel Young <[email protected]> | |
| """ | |
| import subprocess | |
| import networkx as nx | |
| from os import remove | |
| def _build_dreadnaut_file(g): | |
| """Prepare file to pass to dreadnaut. | |
| Warning | |
| ------- | |
| Assumes that the nodes are represented by the 0 indexed integers. | |
| """ | |
| # dreadnaut options | |
| file_content = ["As"] # sparse mode | |
| file_content.append("-a") # do not print out automorphisms | |
| file_content.append("-m") # do not print out level markers | |
| # specify graph structure | |
| file_content.append("n=" + str(g.number_of_nodes()) + " g") | |
| for v in sorted(g.nodes()): | |
| line = " " + str(v) + " : " | |
| for nb in g.neighbors(v): | |
| if v < nb: | |
| line += str(nb) + " " | |
| line += ";" | |
| file_content.append(line) | |
| # add nauty command | |
| file_content.append(".") | |
| file_content.append("x") | |
| file_content.append("o") | |
| return file_content | |
| def compute_automorphisms(g, tmp_path="/tmp/dreadnaut.txt", dreadnaut_call="dreadnaut"): | |
| # get dreadnaut command file | |
| file_content = _build_dreadnaut_file(g) | |
| # write to tmp_path | |
| with open(tmp_path, 'w') as f: | |
| print("\n".join(file_content), file=f) | |
| # call dreadnaut | |
| proc = subprocess.run([dreadnaut_call], | |
| input=b"< " + tmp_path.encode(), | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.DEVNULL) | |
| [info, _, orbits] = proc.stdout.decode().strip().split("\n", 2) | |
| # ~~~~~~~~~~~~~~ | |
| # Extract high level info from captured output | |
| # ~~~~~~~~~~~~~~ | |
| num_orbits = int(info.split(" ")[0]) | |
| num_gen = int(info.split(" ")[3]) | |
| # ~~~~~~~~~~~~~~ | |
| # Extract orbits | |
| # ~~~~~~~~~~~~~~ | |
| # This big list comprehension splits all orbits into their own sublist, and | |
| # each of these orbits into individual components (as string). | |
| # There is still some post-processing to do since some of them are in the | |
| # compact notation X:X+n when the n+1 nodes of the orbits are contiguous. | |
| X = [_.strip().split(" (")[0].split(" ") | |
| for _ in orbits.replace("\n ",'').strip().split(";")[:-1]] | |
| for i, orbit in enumerate(X): | |
| final_orbit = [] | |
| for elem in orbit: | |
| if ":" in elem: | |
| _ = elem.split(":") | |
| final_orbit += range(int(_[0]), int(_[1]) + 1) | |
| else: | |
| final_orbit += [int(elem)] | |
| X[i] = final_orbit | |
| # garbage collection | |
| remove(tmp_path) | |
| return num_orbits, num_gen, X | |
| if __name__ == '__main__': | |
| import matplotlib.pyplot as plt | |
| # declare networkx graph | |
| g = nx.barbell_graph(5, 2) | |
| # orbits and generators of the graph | |
| num_orbits, num_gen, X = compute_automorphisms(g) | |
| print("Graph:\t\t", "num_orbits=" +str(num_orbits), "num_gen=" +str(num_gen)) | |
| # Plot | |
| colors = [None for i in range(g.number_of_nodes())] | |
| for idx, orbit in enumerate(X): | |
| for v in orbit: | |
| colors[v] = idx | |
| nx.draw(g, node_color=colors, linewidths=2, width=2, edge_color='gray', edgecolors='k') | |
| plt.show() |
Nice program! In line 83 you should insert
g = nx.barbell_graph(5, 2)
as you had in previous versions.
Hey thanks! You are right, fixed 👍
I believe there is a bug in how the dreadnaut file is generated. Consider two 6-node complete bipartite graphs K_{3,3} where each vertex in the first graph is connected to a vertex in the second graph:
g = nx.complete_bipartite_graph(3,3)
g2 = nx.relabel_nodes(nx.complete_bipartite_graph(3,3), mapping = {i:i+6 for i in range(6)})
g.add_edges_from(g2.edges())
g.add_edges_from([(i,i+6) for i in range(6)])It is easy to verify that this graph is vertex-transitive. Your gist produces incorrect orbits for this graph. Suggested fix: in line 27 iterate over nodes in the order of increasing label
for v in g.nodes():should be
for v in sorted(g.nodes()):This is because the dreadnaut command ; increases the current index.
Thanks, the code assumed an implicit sort of the nodes IDs, your fix make it more general.
Have you thought about just using https://github.com/pdobsan/pynauty ?
Output for the above:
The tested graph is the (5,2) barbell shown below:
It is easy to confirm visually that there are indeed 3 orbits.