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)
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])
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))