def edges_to_directed_tree(g, root, edges): t = Graph(directed=False) for _ in range(g.num_vertices()): t.add_vertex() for u, v in edges: t.add_edge(u, v) vis = EdgeCollectorVisitor() bfs_search(t, source=root, visitor=vis) t.clear_edges() t.set_directed(True) for u, v in vis.edges: t.add_edge(u, v) return filter_nodes_by_edges(t, edges)
class BoardGraphGraphtool(BoardGraphBase): def __init__(self, number_of_vertices, graph_type): super().__init__(number_of_vertices, graph_type) # Graph tool creates directed multigraph by default. self._graph = Graph() self._graph.add_vertex(number_of_vertices) self._graph.vertex_properties["cell"] = self._graph.new_vertex_property( "object", number_of_vertices * [BoardCell()] ) self._graph.edge_properties["direction" ] = self._graph.new_edge_property("object") self._graph.edge_properties["weight" ] = self._graph.new_edge_property("int") def __getitem__(self, position): return self._graph.vp.cell[self._graph.vertex(position)] def __setitem__(self, position, board_cell): self._graph.vp.cell[self._graph.vertex(position)] = board_cell def __contains__(self, position): return position in range(0, self.vertices_count()) def vertices_count(self): return self._graph.num_vertices() def edges_count(self): return self._graph.num_edges() def has_edge(self, source_vertice, target_vertice, direction): for e in self._graph.vertex(source_vertice).out_edges(): if ( int(e.target()) == target_vertice and self._graph.ep.direction[e] == direction ): return True return False def out_edges_count(self, source_vertice, target_vertice): return len([ 1 for e in self._graph.vertex(source_vertice).out_edges() if int(e.target()) == target_vertice ]) def reconfigure_edges(self, width, height, tessellation): """ Uses tessellation object to create all edges in graph. """ self._graph.clear_edges() for source_vertice in self._graph.vertices(): for direction in tessellation.legal_directions: neighbor_vertice = tessellation.neighbor_position( int(source_vertice), direction, board_width=width, board_height=height ) if neighbor_vertice is not None: e = self._graph.add_edge( source_vertice, neighbor_vertice, add_missing=False ) self._graph.ep.direction[e] = direction # TODO: Faster version? # def reconfigure_edges(self, width, height, tessellation): # """ # Uses tessellation object to create all edges in graph. # """ # self._graph.clear_edges() # edges_to_add = [] # directions_to_add = dict() # for source_vertice in self._graph.vertices(): # for direction in tessellation.legal_directions: # neighbor_vertice = tessellation.neighbor_position( # int(source_vertice), direction, # board_width=width, board_height=height # ) # if neighbor_vertice is not None: # edge = (int(source_vertice), neighbor_vertice,) # edges_to_add.append(edge) # if edge not in directions_to_add: # directions_to_add[edge] = deque() # directions_to_add[edge].append(direction) # self._graph.add_edge_list(edges_to_add) if edges_to_add else None # for e in edges_to_add: # e_descriptors = self._graph.edge( # s = self._graph.vertex(e[0]), # t = self._graph.vertex(e[1]), # all_edges = True # ) # for e_descriptor in e_descriptors: # if len(directions_to_add[e]) > 0: # self._graph.ep.direction[e_descriptor] = directions_to_add[e][0] # directions_to_add[e].popleft() def calculate_edge_weights(self): for e in self._graph.edges(): self._graph.ep.weight[e] = self.out_edge_weight(int(e.target())) def neighbor(self, from_position, direction): try: for e in self._graph.vertex(from_position).out_edges(): if self._graph.ep.direction[e] == direction: return int(e.target()) except ValueError as e: raise IndexError(e.args) return None def wall_neighbors(self, from_position): return [ int(n) for n in self._graph.vertex(from_position).out_neighbours() if self[int(n)].is_wall ] def all_neighbors(self, from_position): return [ int(n) for n in self._graph.vertex(from_position).out_neighbours() ] def shortest_path(self, start_position, end_position): try: return [ int(v) for v in shortest_path( g=self._graph, source=self._graph.vertex(start_position), target=self._graph.vertex(end_position), )[0] ] except ValueError: return [] def dijkstra_path(self, start_position, end_position): try: self.calculate_edge_weights() return [ int(v) for v in shortest_path( g=self._graph, source=self._graph.vertex(start_position), target=self._graph.vertex(end_position), weights=self._graph.ep.weight, )[0] ] except ValueError: return [] def position_path_to_direction_path(self, position_path): retv = [] src_vertice_index = 0 for target_vertice in position_path[1:]: source_vertice = position_path[src_vertice_index] src_vertice_index += 1 for out_edge in self._graph.vertex(source_vertice).out_edges(): if int(out_edge.target()) == target_vertice: retv.append(self._graph.ep.direction[out_edge]) return { 'source_position': position_path[0] if position_path else None, 'path': retv }
class graphRL(gym.Env): """ will have fixed action space, but not all actions are valid within each state step function should have a function that tests if the chosen action is valid the observation returned will be the graph_tool graph, but the state will just be the adjacency matrix (? maybe, currently have obs space as the matrix) maybe step function just alters the given graph """ metadata = {'render.modes': ['human', 'graph', 'interactive']} def __init__(self, network_size=10, input_nodes=3): self.network_size = network_size self.input_nodes = input_nodes self.graph = Graph() self.graph.set_fast_edge_removal(True) self.graph.add_vertex(self.network_size) self.action_space = spaces.Tuple((spaces.Discrete(self.network_size), spaces.Discrete(self.network_size))) self.observation_space = spaces.MultiDiscrete( np.full((self.network_size, self.network_size), 2)) self.time_step = 0 self.observation = adjacency(self.graph).toarray().astype(int) self.seed_value = self.seed() self.true_graph = self.create_true_graph() self.reset() def create_true_graph(self): final_workflow = Graph() final_workflow.add_vertex(self.network_size) i = 0 while max([ shortest_distance(final_workflow, x, (self.network_size - 1)) for x in final_workflow.get_vertices() ]) > 100: i += 1 if i > 10000: raise RuntimeError('generating graph took too long') valid_source_nodes = [ index for index, in_degree in enumerate( final_workflow.get_in_degrees( final_workflow.get_vertices())) if ((in_degree > 0 or index < self.input_nodes) and index < (self.network_size - 1)) ] valid_to_nodes = [ index for index in final_workflow.get_vertices() if (index >= self.input_nodes) ] new_edge = final_workflow.add_edge( self.np_random.choice(valid_source_nodes), self.np_random.choice(valid_to_nodes)) if not is_DAG(final_workflow): final_workflow.remove_edge(new_edge) observation = adjacency(final_workflow).toarray().astype(int) if not self.observation_space.contains(observation): final_workflow.remove_edge(new_edge) return final_workflow def render(self, mode='human'): if mode == 'graph': # return graphtools graph object return self.graph elif mode == 'interactive': interactive_window(self.graph) elif mode == 'human': filename = "./renders/render" + str(self.time_step) + ".png" graph_draw(self.graph, vertex_text=self.graph.vertex_index, vertex_font_size=18, output_size=(1000, 1000), output=filename) def render_truth(self, mode='human'): if mode == 'graph': # return graphtools graph object return self.true_graph elif mode == 'interactive': interactive_window(self.true_graph) elif mode == 'human': filename = "./renders/TrueGraphSeed" + str( self.seed_value) + ".png" graph_draw(self.true_graph, vertex_text=self.true_graph.vertex_index, vertex_font_size=18, output_size=(1000, 1000), output=filename) def seed(self, seed=None): self.np_random, seed = seeding.np_random(seed) return [seed] def step(self, action): done = 0 reward = 0 assert self.action_space.contains(action) valid_source_nodes = [ index for index, in_degree in enumerate( self.graph.get_in_degrees(self.graph.get_vertices())) if ((in_degree > 0 or index < self.input_nodes) and index < (self.network_size - 1)) ] if action[0] not in valid_source_nodes: raise ValueError('this action does not have a valid from node') new_edge = self.graph.add_edge(action[0], action[1]) if not is_DAG(self.graph): self.graph.remove_edge(new_edge) raise ValueError('this action violates the DAG property') self.observation = adjacency(self.graph).toarray().astype(int) if not self.observation_space.contains(self.observation): print(self.observation) self.graph.remove_edge(new_edge) self.observation = adjacency(self.graph).toarray().astype(int) raise ValueError('this action makes a duplicate edge') if isomorphism(self.graph, self.true_graph): reward = 1 done = 1 self.time_step += 1 return self.observation, reward, done, {"time_step": self.time_step} def reset(self): self.graph.clear_edges() self.time_step = 0 self.observation = adjacency(self.graph).toarray() return self.observation