def find_rep_nodes(nx_g): """ Takes a NetworkX graph and finds groups of nodes that are equivalent up to automorphism """ # Creates PyNauty graph and passes it to PyNauty to get orbits partition = 'member' if nx_g.__dict__.get('power', 1) > 1 else None pyn_g, node_map = convert_nx_to_pyn(nx_g, partition=partition) _, _, _, orbits, _ = pyn.autgrp(pyn_g) # Finds node equivalency dictionary node_equivs = defaultdict(list) for node, equiv in enumerate(orbits): node_equivs[node_map[equiv]].append(node_map[node]) # If multigraph, returns orbits of nodes in first layer if nx_g.__dict__.get('dimension', 2) > 2: node_equivs = { u: [v for l_v, v in equivs if l_v == 0] for (l_u, u), equivs in list(node_equivs.items()) if l_u == 0 } else: node_equivs = { node: equivs for node, equivs in node_equivs.items() if nx_g.degree(node) > 1 } return node_equivs
def bindByOrbits(graph1, graph2): bondedGraphs = [] orbits1 = autgrp(graph1)[3] orbits2 = autgrp(graph2)[3] verticesByOrbit1 = getVerticesByOrbit(orbits1) verticesByOrbit2 = getVerticesByOrbit(orbits2) for vertex1 in verticesByOrbit1.values(): for vertex2 in verticesByOrbit2.values(): g1VertexDegree = graph1.getVertexDegree(vertex1) g2VertexDegree = graph2.getVertexDegree(vertex2) if g1VertexDegree != g2VertexDegree: continue g1 = deepcopy(graph1) g2 = deepcopy(graph2) g1.setBindVertex(vertex1) g2.setBindVertex(vertex2) bondedGraphs.append(bindGraph(g1, g2)) return bondedGraphs
def apply_to_all_ged_k(G, k, f, statistic=np.mean): """ f is the function to apply. Must take (G,aut) """ results = [] edges = [(u, v) for u, v in G.edges()] for edges_to_remove in combinations(edges, k): G1 = copy.deepcopy(G) for e in edges_to_remove: assert (len(e) == 2) G1.remove_edge(*e) g = pynauty.Graph(number_of_vertices=G1.number_of_nodes(), directed=nx.is_directed(G1), adjacency_dict=get_adjacency_dict(G1)) aut = pynauty.autgrp(g) results.append(f(G1, aut)) return statistic(results)
def compute_orbits(node_list, A): ''' WARN: node_list & A should be a connected component. Don't input multiple connected components. args: node_list e.g. ['C', 'N', 'N', 'C', 'C', 'O', 'P', 'O', 'C'] A adj mat n x n np array. 0 edge absent. >0 edge present. Edge colors & edge weights are ignored. returns: orbit labels. 1 label per atom. number of orbit partitions. scalar. ''' A = adj_mat_2_adj_dict(A) vertex_coloring = node_list_2_vertex_coloring(node_list) G = pynauty.Graph(number_of_vertices=len(node_list), directed=False, adjacency_dict=A, vertex_coloring=vertex_coloring) automorphism_group = pynauty.autgrp(G) #return -> (generators, grpsize1, grpsize2, orbits, numorbits) # print('automorphism_group', automorphism_group) n_orbits = automorphism_group[-1] # e.g. 3 orbits = automorphism_group[-2] # e.g. [0 1 2 2 1 0] return orbits, n_orbits
print(args.files) for fname in progressbar.progressbar(glob.glob(args.files)): # TODO: check if the row is already there. If it is, check what fields it already has name = Path(fname).name if name in precomputed_names: # if it's already there, update s = df.loc[name] existing_columns = set(s.where(s.notna()).dropna().index) columns_to_compute = expected_features - existing_columns elist = s['elist'] G = nx.OrderedGraph() G.add_edges_from(elist) g = pynauty.Graph(number_of_vertices=G.number_of_nodes(), directed=nx.is_directed(G), adjacency_dict=get_adjacency_dict(G)) aut = pynauty.autgrp(g) for feature_name in columns_to_compute: df.at[name, feature_name] = float( feature_getter_dispatcher(feature_name, G, aut)) else: # if it's not there yet, build a dictionary with all the stuff elist, p, res = pickle.load(open(fname, "rb")) d = { 'name': name, 'p': float(p), 'elist': copy.deepcopy(elist), 'res': copy.deepcopy(res) } G = nx.OrderedGraph() G.add_edges_from(elist) g = pynauty.Graph(number_of_vertices=G.number_of_nodes(),
def calculate_platform_symmetries(cfg): """Calculate the Automorphism Group of a Platform Graph This task expects three hydra parameters to be available. **Hydra Parameters**: * **platform:** the input platform. The task expects a configuration dict that can be instantiated to a :class:`~mocasin.common.platform.Platform` object. * **out:** the output file (extension will be added) * **mpsym:** a boolean value selecting mpsym as backend (and JSON as output) Otherwise it outputs plaintext from the python implementation. """ platform = hydra.utils.instantiate(cfg["platform"]) log.info("start converting platform to edge graph for automorphisms.") plat_graph = platform.to_adjacency_dict(include_proc_type_labels=True) use_mpsym = cfg["mpsym"] ( adjacency_dict, num_vertices, coloring, nodes_correspondence, ) = aut.to_labeled_edge_graph(plat_graph) log.info("done converting platform to edge graph for automorphisms.") # print(nodes_correspondence) # print(coloring) # print(len(coloring)) # print(str(edge_graph)) log.info( "start calculating the automorphism group of the (edge) graph with " + str(num_vertices) + " nodes using nauty.") nautygraph = pynauty.Graph(num_vertices, True, adjacency_dict, coloring) autgrp_edges = pynauty.autgrp(nautygraph) log.info( "done calculating the automorphism group of the (edge) graph using nauty." ) log.info("start coverting automorhpism of edges to nodes.") autgrp, new_nodes_correspondence = aut.edge_to_node_autgrp( autgrp_edges[0], nodes_correspondence) permutations_lists = map(aut.list_to_tuple_permutation, autgrp) # permutations = map(perm.Permutation,permutations_lists) # permgrp = perm.PermutationGroup(list(permutations)) # print(permgrp.point_orbit(0)) log.info("done coverting automorhpism of edges to nodes.") log.info("start writing to file.") if use_mpsym: try: mpsym except NameError: log.error( "Configured for mpsym output but could not load mpsym. Fallback to python implementation" ) use_mpsym = False if use_mpsym: out_filename = str(cfg["out_file"]) mpsym_autgrp = mpsym.ArchGraphAutomorphisms( [mpsym.Perm(g) for g in autgrp]) json_out = mpsym_autgrp.to_json() with open(out_filename, "w") as f: f.write(json_out) else: out_filename = cfg["out_file"] with open(out_filename, "w") as f: f.write("Platform Graph:") f.write(str(plat_graph)) # f.write("Edge Group with ~" + str(autgrp_edges[1]) + " * 10^" + str(autgrp_edges[2]) + " elements.\n") f.write("Symmetry group generators:") f.write(str(list(permutations_lists))) f.write("\nCorrespondence:") f.write(str(new_nodes_correspondence)) log.info("done writing to file.")
adjacency_dict[variable_index].append(intermediate_vertex_index) num_vertices += 1 # So, in building the adjacency dict, we want to colour constraints in one colour (maybe this changes in the future). # We then want to partition the variables by their upper bound, lower bound and their value (if any) in the objective function # Inefficient to itereate through yet again but want it to be as obvious as possible what's going on # I think that we may also need to consider constraint colourings! variable_colouring_dict = defaultdict(set) for n, variable in model.vd.items(): variable_index = graph_indices[variable.name] obj_coef = obj_var_coefs[ variable.name] if variable.name in obj_var_coefs else 0 variable_colouring_dict[(variable.lb, variable.ub, obj_coef)].add(variable_index) # Let's look at some constraint colourings constraint_colouring_dict = defaultdict(set) for n, constraint in model.c.items(): constraint_index = graph_indices[constraint.name] constraint_colouring_dict[(constraint.lb, constraint.ub, constraint.rhs)].add(constraint_index) vertex_colourings = [] vertex_colourings.extend(variable_colouring_dict.values()) vertex_colourings.extend(constraint_colouring_dict.values()) # We haven't yet done the vertex_colourings yet but let's just see what we get for now graph = pynauty.Graph(num_vertices, False, adjacency_dict, vertex_colourings) aut = pynauty.autgrp(graph) print(aut[3])
# [[name, Graph, numorbit, grpsize, generators]] # # numorbit, grpsize, generators was calculated by dreadnut # from data_graphs import graphs if __name__ == '__main__': print('Testing pynauty.autgrp()') print('Python version: ' + sys.version) print('Starting ...') passed = 0 failed = 0 for gname, g, numorbit, grpsize, gens in graphs: print('%-17s ...' % gname, end=' ') sys.stdout.flush() generators, order, o2, orbits, orbit_no = autgrp(g) if generators == gens and orbit_no == numorbit and order == grpsize: print('OK') passed += 1 else: print('failed') failed +=1 print('... done.') if failed > 0: print('passed = %d failed = %d' % (passed, failed)) else: print('All tests passed.')
def __init__( self, graph, platform, channels=False, periodic_boundary_conditions=False, norm_p=2, canonical_operations=True, disable_mpsym=False, disable_symmetries_test=False, ): self._topologyGraph = platform.to_adjacency_dict( include_proc_type_labels=True) self.graph = graph self.platform = platform self._d = len(graph.processes()) init_app_ncs(self, graph) self._arch_nc_inv = {} self.channels = channels self.boundary_conditions = periodic_boundary_conditions self.p = norm_p com_mapper = ComFullMapper(graph, platform) self.list_mapper = ProcPartialMapper(graph, platform, com_mapper) self.canonical_operations = canonical_operations n = len(self.platform.processors()) correct = None if disable_mpsym: self.sym_library = False else: try: mpsym except NameError: self.sym_library = False else: self.sym_library = True if hasattr(platform, "ag"): self._ag = platform.ag log.info( "Symmetries initialized with mpsym: Platform Generator." ) elif hasattr(platform, "ag_json"): if exists(platform.ag_json): self._ag = mpsym.ArchGraphSystem.from_json_file( platform.ag_json) if disable_symmetries_test: log.warning( "Using symmetries JSON without testing.") correct = True else: try: correct = checkSymmetries( platform.to_adjacency_dict(), self._ag.automorphisms(), ) except Exception as e: log.warning( "An unknown error occurred while reading " "the embedding JSON file. Did you provide " "the correct file for the given platform? " f"({e})") correct = False if not correct: log.warning( "Symmetries json does not fit platform.") del self._ag else: log.info( "Symmetries initialized with mpsym: JSON file." ) else: log.warning( "Invalid symmetries JSON path (file does not exist)." ) if not hasattr(self, "_ag"): # only calculate this if not already present log.info("No pre-comupted mpsym symmetry group available." " Initalizing architecture graph...") ( adjacency_dict, num_vertices, coloring, self._arch_nc, ) = to_labeled_edge_graph(self._topologyGraph) nautygraph = pynauty.Graph(num_vertices, True, adjacency_dict, coloring) log.info("Architecture graph initialized. Calculating " "automorphism group using Nauty...") autgrp_edges = pynauty.autgrp(nautygraph) autgrp, _ = edge_to_node_autgrp(autgrp_edges[0], self._arch_nc) self._ag = mpsym.ArchGraphAutomorphisms( [mpsym.Perm(g) for g in autgrp]) for node in self._arch_nc: self._arch_nc_inv[self._arch_nc[node]] = node # TODO: ensure that nodes_correspondence fits simpleVec if not self.sym_library: log.info( "Using python symmetries: Initalizing architecture graph...") ( adjacency_dict, num_vertices, coloring, self._arch_nc, ) = to_labeled_edge_graph(self._topologyGraph) nautygraph = pynauty.Graph(num_vertices, True, adjacency_dict, coloring) log.info("Architecture graph initialized. Calculating " "automorphism group using Nauty...") autgrp_edges = pynauty.autgrp(nautygraph) autgrp, _ = edge_to_node_autgrp(autgrp_edges[0], self._arch_nc) permutations_lists = map(list_to_tuple_permutation, autgrp) permutations = [ Permutation.fromLists(p, n=n) for p in permutations_lists ] self._G = PermutationGroup(permutations) log.info("Initialized automorphism group with internal symmetries")
def find_symmetries(model): num_vertices = 0 obj_var_coefs = {} # Going to make this really easy initially by iterating through once initially and building up an index map graph_indices = {} variable_names = {} def add_vertex(name, num): graph_indices[name] = num variable_names[num] = name return num + 1 for n, constraint in model.c.items(): num_vertices = add_vertex(constraint.name, num_vertices) #graph_indices[constraint.name] = num_vertices #num_vertices += 1 for n, variable in model.vd.items(): num_vertices = add_vertex(variable.name, num_vertices) #graph_indices[variable.name] = num_vertices #num_vertices += 1 # The number of vertices before the inclusion of intermediate vertices num_model_vertices = num_vertices # I think that first, we go through the objective function build a map from variable->onj_coeff standard_objective = generate_standard_repn(model.o) for i, (variable, coefficient) in enumerate( zip(standard_objective.linear_vars, standard_objective.linear_coefs)): obj_var_coefs[variable.name] = coefficient # Should probably look up a slightly nicer way to do this? adjacency_dict = defaultdict(list) for name, constraint in model.c.items(): # variables that share a coefficient within a constraint can coalesce their intermediate vertices coalesce_dict = {} repn = generate_standard_repn(constraint.body) constraint_index = graph_indices[constraint.name] for coefficient, variable in zip(repn.linear_coefs, repn.linear_vars): # So, first we want an intermediate vertex variable_index = graph_indices[variable.name] if coefficient == 1: # For now, if the coefficient is one, we will not use an intermediate vertex adjacency_dict[constraint_index].append(variable_index) elif coefficient in coalesce_dict: intermediate_vertex_index = coalesce_dict[coefficient] # The intermediate vertex is already connected to the constraint vertex, so here we just need to connect it to this variable adjacency_dict[variable_index].append( intermediate_vertex_index) else: # We do not yet have an intermediate vertex for this coefficient and vertex, let's make one intermediate_vertex_index = num_vertices coalesce_dict[coefficient] = intermediate_vertex_index adjacency_dict[constraint_index].append( intermediate_vertex_index) adjacency_dict[variable_index].append( intermediate_vertex_index) num_vertices += 1 # So, in building the adjacency dict, we want to colour constraints in one colour (maybe this changes in the future). # We then want to partition the variables by their upper bound, lower bound and their value (if any) in the objective function # Inefficient to itereate through yet again but want it to be as obvious as possible what's going on # I think that we may also need to consider constraint colourings! variable_colouring_dict = defaultdict(set) for n, variable in model.vd.items(): variable_index = graph_indices[variable.name] obj_coef = obj_var_coefs[ variable.name] if variable.name in obj_var_coefs else 0 variable_colouring_dict[(variable.lb, variable.ub, obj_coef)].add(variable_index) # Let's look at some constraint colourings constraint_colouring_dict = defaultdict(set) for n, constraint in model.c.items(): constraint_index = graph_indices[constraint.name] constraint_colouring_dict[(constraint.lb, constraint.ub, constraint.rhs)].add(constraint_index) vertex_colourings = [] vertex_colourings.extend(variable_colouring_dict.values()) vertex_colourings.extend(constraint_colouring_dict.values()) # We haven't yet done the vertex_colourings yet but let's just see what we get for now graph = pynauty.Graph(num_vertices, False, adjacency_dict, vertex_colourings) aut = pynauty.autgrp(graph) symmetry_groups = defaultdict(set) for index, min_vertex in enumerate(aut[3][:num_model_vertices]): # I think that we're doing it slightly wrong here, we should use the constraint names here symmetry_groups[min_vertex].add(variable_names[index]) return set(map(lambda s: frozenset(s), symmetry_groups.values()))
1: [0, 7], 2: [3, 1], 3: [2, 5], 4: [2, 5], 5: [4, 6], 6: [0, 7], 7: [4, 6], } if __name__ == "__main__": print(list_to_tuple_permutation([0, 1, 2, 4, 3, 5])) # [[3, 4]] print(list_to_tuple_permutation([5, 4, 3, 2, 1, 0])) # [[0, 5], [1, 4], [2, 3]] print(list_to_tuple_permutation([1, 2, 3, 4, 5, 0])) # [[0, 1, 2, 3, 4, 5]] square_aut_grp = pynauty.autgrp(pynauty.Graph(8, True, edge_graph_square)) print(str(list(map(list_to_tuple_permutation, square_aut_grp[0])))) def platform_graph_to_num_dict(platform_graph): core_to_int = {} for i, core in enumerate(platform_graph): core_to_int[core] = i res = {} for core in platform_graph: vals = [] for val in platform_graph[core]: vals.append(core_to_int[val[0]]) res[core_to_int[core]] = set(vals) return res