def calculate_arrows_backward(self, a, b): """Finds all edges with negative bump""" if self.knowledge is not None and not self.knowledge.no_edge_required( a, b): return na_y_x = graph_util.get_na_y_x(self.graph, a, b) _na_y_x = list(na_y_x) _depth = len(_na_y_x) for i in range(_depth + 1): choices = itertools.combinations(range(0, _depth), i) for choice in choices: diff = set([_na_y_x[k] for k in choice]) h = set(_na_y_x) h = h - diff if self.knowledge is not None and not self.valid_set_by_knowledge( b, h): continue bump = self.delete_eval(a, b, diff, na_y_x) if bump > 0: if self.verbose: print("Evaluated removal of an arrow " + str(a) + " -> " + str(b) + " with bump: " + str(bump)) self.add_arrow(a, b, na_y_x, h, bump)
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 calculate_arrows_forward(self, a, b): # print("Calculate Arrows Forward: " + str(a) + " " + str(b)) if b not in self.effect_edges_graph[a] and self.mode == "heuristic": print("Returning early...") return if self.knowledge is not None and self.knowledge.is_forbidden(a, b): return # print("Get neighbors for " + str(b) + " returns " + str(graph_util.neighbors(self.graph, b))) self.stored_neighbors[b] = graph_util.neighbors(self.graph, b) na_y_x = graph_util.get_na_y_x(self.graph, a, b) _na_y_x = list(na_y_x) if not graph_util.is_clique(self.graph, na_y_x): return t_neighbors = list(graph_util.get_t_neighbors(self.graph, a, b)) # print("tneighbors for " + str(a) + ", " + str(b) + " returns " + str(t_neighbors)) len_T = len(t_neighbors) def outer_loop(): previous_cliques = set() # set of sets of nodes previous_cliques.add(frozenset()) new_cliques = set() # set of sets of nodes for i in range(len_T + 1): choices = itertools.combinations(range(len_T), i) choices2 = itertools.combinations(range(len_T), i) # print("All choices: ", list(choices2), " TNeighbors: ", t_neighbors) for choice in choices: T = frozenset([t_neighbors[k] for k in choice]) # print("Choice:", T) union = set(na_y_x) union.update(T) found_a_previous_clique = False for clique in previous_cliques: # basically if clique is a subset of union if union >= clique: found_a_previous_clique = True break if not found_a_previous_clique: # Break out of the outer for loop return if not graph_util.is_clique(self.graph, union): continue new_cliques.add(frozenset(union)) bump = self.insert_eval(a, b, T, na_y_x) # print("Evaluated arrow " + str(a) + " -> " + str(b) + " with T: " + str(T) + " and bump: " + str(bump)); if bump > 0: self.add_arrow(a, b, na_y_x, T, bump) previous_cliques = new_cliques new_cliques = set() outer_loop()
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