def undirect_unforced_edges_func(self, node, graph):
        """Removes directed edges that are not forced by an unshielded collider about node"""
        node_parents = graph_util.get_parents(graph, node)
        parents_to_undirect = set(node_parents)

        # Find any unshielded colliders in node_parents, and orient them
        for (p1, p2) in itertools.combinations(node_parents, 2):
            if not graph_util.adjacent(graph, p1, p2):
                # Have an unshielded collider p1 -> node <- p2, which forces orientation
                self.oriented.update([(p1, node), (p2, node)])
                parents_to_undirect.difference_update([p1, p2])

        did_unorient = False

        for parent in parents_to_undirect:
            if self.knowledge is not None:
                must_orient = self.knowledge.is_required(parent, node) or \
                              self.knowledge.is_forbidden(node, parent)
            else:
                must_orient = False
            if not (parent, node) in self.oriented and not must_orient:
                # Undirect parent -> node
                graph_util.remove_dir_edge(graph, parent, node)
                graph_util.add_undir_edge(graph, parent, node)
                self.visited.add(node)
                self.visited.add(parent)
                # print(f"unorienting {parent} -> {node}")
                did_unorient = True

        if did_unorient:
            for adjacent in graph_util.adjacent_nodes(graph, node):
                self.direct_stack.append(adjacent)

            self.direct_stack.append(node)
    def direct(self, node_1, node_2, graph):
        # print("Int Directing " + str(node_1) + " " + str(node_2))
        if self.knowledge is not None:
            if self.knowledge.is_forbidden(node_1, node_2):
                return

        graph_util.remove_dir_edge(graph, node_1, node_2)
        graph_util.remove_dir_edge(graph, node_2, node_1)
        graph_util.add_dir_edge(graph, node_1, node_2)
        self.visited.update([node_1, node_2])
        # node_1 -> node_2 edge
        if (node_1, node_2) not in self.oriented and \
                (node_2, node_1) not in self.oriented:
            self.oriented.add((node_1, node_2))
            self.direct_stack.append(node_2)
Beispiel #3
0
    def add_required_edges(self):
        """Tetrad implementation is really confusing and seems to be
        mostly checks to ensure required edges don't form a cycle"""
        if self.knowledge is None:
            return

        for edge in self.knowledge.required_edges:
            # Make sure the required edges aren't a cycle
            if not edge[0] in graph_util.get_ancestors(self.graph, edge[1]):
                graph_util.remove_dir_edge(self.graph, edge[1], edge[0])
                graph_util.add_dir_edge(self.graph, edge[0], edge[1])
                #   if self.verbose:
                #    print(f"Adding edge from knowledge: {edge[0]} -> {edge[1]}")

        for edge in self.knowledge.required_connections:
            graph_util.add_undir_edge(self.graph, edge[1], edge[0])
Beispiel #4
0
    def delete(self, x, y, H):
        # Remove any edge between x and y
        graph_util.remove_dir_edge(self.graph, x, y)
        graph_util.remove_dir_edge(self.graph, y, x)

        # H is the set of neighbors of y that are adjacent to x
        for node in H:
            if (graph_util.has_dir_edge(self.graph, node, y)
                    or graph_util.has_dir_edge(self.graph, node, x)):
                continue

            # Direct the edge y --- node as y --> node
            graph_util.undir_to_dir(self.graph, y, node)

            # If x --- node is undirected, direct it as x --> node
            if graph_util.has_undir_edge(self.graph, x, node):
                graph_util.undir_to_dir(self.graph, x, node)

        self.removed_edges.add((x, y))