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
        }
Exemple #3
0
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