Beispiel #1
0
    def reevaluate_forward(self, to_process, arrow):
        # print("Re-evaluate forward with " + str(to_process) + " " + str(arrow))
        for node in to_process:
            if self.mode == "heuristic":
                nzero_effect_nodes = self.effect_edges_graph.get(node)
                # print("Re-evaluate forward. Currently on node: " + str(node))
                # print("nzero-effect-nodes: " + str(nzero_effect_nodes))
            elif self.mode == "covernoncolliders":
                g = set()
                for n in graph_util.adjacent_nodes(self.graph, node):
                    for m in graph_util.adjacent_nodes(self.graph, n):
                        if graph_util.adjacent(self.graph, n, m):
                            continue

                        if graph_util.is_def_collider(self.graph, m, n, node):
                            continue

                        g.update(m)

                nzero_effect_nodes = list(g)
            if nzero_effect_nodes is not None:
                for w in nzero_effect_nodes:
                    if w == node:
                        continue
                    if not graph_util.adjacent(self.graph, node, w):
                        self.clear_arrow(w, node)
                        self.calculate_arrows_forward(w, node)
Beispiel #2
0
    def initialize_two_step_edges(self, nodes):
        for node in nodes:

            g = set()

            for n in graph_util.adjacent_nodes(self.graph, node):
                for m in graph_util.adjacent_nodes(self.graph, n):

                    if node == m:
                        continue

                    if graph_util.adjacent(self.graph, node, m):
                        continue

                    if graph_util.is_def_collider(self.graph, m, n, node):
                        continue

                    g.update(m)

            for x in g:
                assert (x is not node)
                if self.knowledge is not None:
                    if self.knowledge.is_forbidden(
                            node, x) or self.knowledge.is_forbidden(x, node):
                        continue
                    # again, what's the point?
                    if not self.valid_set_by_knowledge(node, set()):
                        continue

                # TODO: Adjacencies

                if (x, node) in self.removed_edges:
                    continue

                self.calculate_arrows_forward(x, node)
Beispiel #3
0
    def insert(self, x, y, T, bump):
        """ T is a subset of the neighbors of Y that are not adjacent to
        (connected by a directed or undirected edge) to X, this should
        connect X -> Y and for t \in T, direct T -> Y if it's not already
        directed

        Definition 12

        """

        if True:
            self.tot += 1
            print(
                str(self.tot) + ". Doing an actual insertion with " + str(x) +
                " -> " + str(y) + " with T: " + str(T) + " and bump: " +
                str(bump))

        if graph_util.adjacent(self.graph, x, y):
            return False

        # Adds directed edge
        self.graph.add_edge(x, y)

        for node in T:
            graph_util.undir_to_dir(self.graph, node, y)

        return True
    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)
Beispiel #5
0
    def bes(self):
        """BES removes edges from the graph generated by FGES, as added edges can now have negative bump in light
        of the additions to the graph after those edges were added."""

        while len(self.sorted_arrows) > 0:
            if self.checkpoint_frequency > 0 and (
                    time.time() -
                    self.last_checkpoint) > self.checkpoint_frequency:
                self.create_checkpoint()
                self.last_checkpoint = time.time()

            arrow = self.sorted_arrows.pop(0)
            x = arrow.a
            y = arrow.b

            if (not (arrow.na_y_x == graph_util.get_na_y_x(self.graph, x, y))) or \
                    (not graph_util.adjacent(self.graph, x, y)) or (graph_util.has_dir_edge(self.graph, y, x)):
                continue

            if not self.valid_delete(x, y, arrow.h_or_t, arrow.na_y_x):
                continue

            H = arrow.h_or_t
            bump = arrow.bump

            self.delete(x, y, H)

            meek_rules = MeekRules(knowledge=self.knowledge)
            meek_rules.orient_implied_subset(self.graph, set([x, y]))

            self.total_score += bump
            self.clear_arrow(x, y)
            if self.verbose:
                print("BES: Removed arrow " + str(x) + " -> " + str(y) +
                      " with bump -" + str(bump))
            visited = self.reapply_orientation(x, y, H)

            to_process = set()

            for node in visited:
                neighbors = graph_util.neighbors(self.graph, node)
                str_neighbors = self.stored_neighbors[node]

                if str_neighbors != neighbors:
                    to_process.update([node])

            to_process.add(x)
            to_process.add(y)
            to_process.update(graph_util.get_common_adjacents(
                self.graph, x, y))

            # TODO: Store graph
            self.reevaluate_backward(to_process)
    def r1_helper(self, node_a, node_b, node_c, graph):
        if ((not graph_util.adjacent(graph, node_a, node_c))
                and (graph_util.has_dir_edge(graph, node_a, node_b) or
                     (node_a, node_b) in self.oriented)
                and graph_util.has_undir_edge(graph, node_b, node_c)):
            if not graph_util.is_unshielded_non_collider(
                    graph, node_a, node_b, node_c):
                return

            if self.is_arrowpoint_allowed(node_b, node_c):
                # print("R1: " + str(node_b) + " " + str(node_c))
                if (node_a, node_c) not in self.oriented and (node_c, node_a) not in self.oriented and \
                        (node_b, node_c) not in self.oriented and (node_c, node_b) not in self.oriented:
                    self.direct(node_b, node_c, graph)
Beispiel #7
0
    def fes(self):
        """The basic workflow of FGES is to first consider add all edges with positive bump, as defined
        by the SEMBicScore, to a sorted list (sorted by bump).
        Edges are popped off this list and added to the graph, after which point the Meek rules are utilized to
        orient edges in the graph that can be oriented. Then, all relevant bumps are recomputed and
        the list is resorted. This process is repeated until there remain no edges to add with positive bump."""
        # print("Running FES.`.")
        # print("Length of sorted arrows", len(self.sorted_arrows))
        # print(self.arrow_dict)
        while len(self.sorted_arrows) > 0:
            if self.checkpoint_frequency > 0 and (
                    time.time() -
                    self.last_checkpoint) > self.checkpoint_frequency:
                self.create_checkpoint()
                self.last_checkpoint = time.time()
            max_bump_arrow = self.sorted_arrows.pop(
                0)  # Pops the highest bump edge off the sorted list
            x = max_bump_arrow.a
            y = max_bump_arrow.b
            # print("Popped arrow: " + str(x) + " -> " + str(y))

            if graph_util.adjacent(self.graph, x, y):
                continue

            na_y_x = graph_util.get_na_y_x(self.graph, x, y)

            # TODO: max degree checks
            # print(na_y_x)

            if max_bump_arrow.na_y_x != na_y_x:
                continue

            # print("Past crucial step")

            if not graph_util.get_t_neighbors(self.graph, x, y).issuperset(
                    max_bump_arrow.h_or_t):
                continue

            if not self.valid_insert(x, y, max_bump_arrow.h_or_t, na_y_x):
                # print("Not valid insert")
                continue

            T = max_bump_arrow.h_or_t
            bump = max_bump_arrow.bump

            # TODO: Insert should return a bool that we check here
            inserted = self.insert(
                x, y, T, bump)  # Insert highest bump edge into the graph
            if not inserted:
                continue

            self.total_score += bump
            # print("Edge set before reapplying orientation: " + str(self.graph.edges()))
            visited_nodes = self.reapply_orientation(
                x, y, None)  # Orient edges appropriately following insertion
            # print("Edge set after reapplying orientation: " + str(self.graph.edges()))
            to_process = set({})

            # check whether the (undirected) neighbors of each node in
            # visited_nodes changed compared to stored neighbors
            for node in visited_nodes:
                # gets undirected neighbors
                new_neighbors = graph_util.neighbors(self.graph, node)
                stored_neighbors = self.stored_neighbors.get(node)
                if stored_neighbors != new_neighbors:
                    to_process.add(node)  # Reevaluate neighbor nodes

            to_process.add(x)  # Reevaluate edges relating to node x
            to_process.add(y)  # Reevaluate edges relating to node y

            self.reevaluate_forward(to_process,
                                    max_bump_arrow)  # Do actual reevaluation