def test_insertable1(): c = CliqueTree() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (3, 5), (6, 4), (1, 3), (1, 5), (1, 4), (5, 7), (2, 5), (1, 6), (2, 4), (1, 7), (4, 7), (2, 7), (3, 7), (2, 6), (3, 6)] solutions = [ frozenset([]), frozenset([(1, 3)]), frozenset([(1, 3), (2, 4)]), frozenset([(1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (5, 7), (1, 3), (2, 4), (3, 5)]), frozenset([(1, 3), (4, 6), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (1, 3), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (5, 7), (1, 4), (1, 5), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (5, 7), (3, 6), (1, 4), (4, 7)]), frozenset([(4, 7), (5, 7), (1, 6), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (1, 6), (2, 4), (3, 6), (4, 7)]), frozenset([(4, 7), (1, 6), (2, 4), (3, 6)]), frozenset([(4, 7), (2, 4), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6)]), frozenset([(2, 7), (3, 7), (2, 6), (3, 6)]), frozenset([(3, 7), (2, 6)]), frozenset([(2, 6), (3, 6)]), frozenset([(3, 6)]), frozenset([]) ] for edge, insertable in zip(edges, solutions): c.add_edge(*edge) assert c.insertable == insertable
def test_update(): c = CliqueTree() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (3, 5), (6, 4), (1, 3), (1, 5), (1, 4), (5, 7), (2, 5), (1, 6), (2, 4), (1, 7), (4, 7), (2, 7), (3, 7), (2, 6), (3, 6)] solutions = [frozenset([]), frozenset([(1, 3)]), frozenset([(1, 3), (2, 4)]), frozenset([(1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (5, 7), (1, 3), (2, 4), (3, 5)]), frozenset([(1, 3), (4, 6), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (1, 3), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (5, 7), (1, 4), (1, 5), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (5, 7), (3, 6), (1, 4), (4, 7)]), frozenset([(4, 7), (5, 7), (1, 6), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (1, 6), (2, 4), (3, 6), (4, 7)]), frozenset([(4, 7), (1, 6), (2, 4), (3, 6)]), frozenset([(4, 7), (2, 4), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6)]), frozenset([(2, 7), (3, 7), (2, 6), (3, 6)]), frozenset([(3, 7), (2, 6)]), frozenset([(2, 6), (3, 6)]), frozenset([(3, 6)]), frozenset([])] for edge, insertable in zip(edges[:10], solutions[:10]): c.add_edge(*edge) c.add_edge(*edges[10], update_insertable=False) assert len(c.insertable) == 0 c.update_insertable(7, stop_at=1) assert len(c.insertable) == 1 assert len(c.insertable.intersection(solutions[10])) == 1
def test_insertable1(): c = CliqueTree() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (3, 5), (6, 4), (1, 3), (1, 5), (1, 4), (5, 7), (2, 5), (1, 6), (2, 4), (1, 7), (4, 7), (2, 7), (3, 7), (2, 6), (3, 6)] solutions = [frozenset([]), frozenset([(1, 3)]), frozenset([(1, 3), (2, 4)]), frozenset([(1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (5, 7), (1, 3), (2, 4), (3, 5)]), frozenset([(1, 3), (4, 6), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (1, 3), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (5, 7), (1, 4), (1, 5), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (5, 7), (3, 6), (1, 4), (4, 7)]), frozenset([(4, 7), (5, 7), (1, 6), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (1, 6), (2, 4), (3, 6), (4, 7)]), frozenset([(4, 7), (1, 6), (2, 4), (3, 6)]), frozenset([(4, 7), (2, 4), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6)]), frozenset([(2, 7), (3, 7), (2, 6), (3, 6)]), frozenset([(3, 7), (2, 6)]), frozenset([(2, 6), (3, 6)]), frozenset([(3, 6)]), frozenset([])] for edge, insertable in zip(edges, solutions): c.add_edge(*edge) assert c.insertable == insertable
def test_update(): c = CliqueTree() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (3, 5), (6, 4), (1, 3), (1, 5), (1, 4), (5, 7), (2, 5), (1, 6), (2, 4), (1, 7), (4, 7), (2, 7), (3, 7), (2, 6), (3, 6)] solutions = [ frozenset([]), frozenset([(1, 3)]), frozenset([(1, 3), (2, 4)]), frozenset([(1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (1, 3), (2, 4), (3, 5)]), frozenset([(4, 6), (5, 7), (1, 3), (2, 4), (3, 5)]), frozenset([(1, 3), (4, 6), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (1, 3), (5, 7), (3, 6), (2, 5), (2, 4)]), frozenset([(4, 7), (5, 7), (1, 4), (1, 5), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (5, 7), (3, 6), (1, 4), (4, 7)]), frozenset([(4, 7), (5, 7), (1, 6), (3, 6), (2, 5), (2, 4)]), frozenset([(2, 5), (1, 6), (2, 4), (3, 6), (4, 7)]), frozenset([(4, 7), (1, 6), (2, 4), (3, 6)]), frozenset([(4, 7), (2, 4), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6), (1, 7)]), frozenset([(4, 7), (2, 6), (3, 6)]), frozenset([(2, 7), (3, 7), (2, 6), (3, 6)]), frozenset([(3, 7), (2, 6)]), frozenset([(2, 6), (3, 6)]), frozenset([(3, 6)]), frozenset([]) ] for edge, insertable in zip(edges[:10], solutions[:10]): c.add_edge(*edge) c.add_edge(*edges[10], update_insertable=False) assert len(c.insertable) == 0 c.update_insertable(7, stop_at=1) assert len(c.insertable) == 1 assert len(c.insertable.intersection(solutions[10])) == 1
def greedy_learn(self, k_max=np.inf, time_limit=np.inf): """An algorithm for learning kDGs using a greedy hill-climbing approach. The code is influenced by the design of the hill climbing approach used in the pgmpy package (https://pgmpy.org/_modules/pgmpy/estimators/HillClimbSearch.html). However, we only consider edge additions which maintain chordality. The learned models are placed in the undirected and directed attributes. :param k_max (int): The maximal clique size of the graph to learn :param time_limit: The time limit of the search """ # build the mst spanning tree initial_graph = CliqueTree() start = time.time() # Generate MST if nx.classes.function.is_empty(self.maxtree): self.mst() for edge in list(self.get_model_mst().edges()): initial_graph.add_edge(edge[0], edge[1]) # the best bn learned so far is the MST self.to_bn(use_mst=True) best_bn = self.get_model_directed() # Flag which controls when the algorithm finishes add_edge = True if k_max > 2: while add_edge: running_time = time.time() - start best_score_delta = (0, None) if running_time > time_limit: break current_bn = best_bn.copy() add_edge = False # make a list of all possible edges that could be added to the graph diff = initial_graph.insertable for edge in diff: running_time = time.time() - start if running_time > time_limit: break # add an edge to the graph initial_graph.add_edge(edge[0], edge[1]) cliques = initial_graph.nodes_in_clique # check if the graph is chordal and has the right clique number if DecomposableModel.get_clique_num(cliques) <= k_max: # get the score of the new graph greedy_bn, greedy_score_delta = DecomposableModel.add_edge_to_bn(edge, current_bn.copy(), self.bdeu) # is the score better than previous graphs? if greedy_score_delta > best_score_delta[0]: # If we add can add an edge, the algorithm should continue looking for more edges add_edge = True best_score_delta = (greedy_score_delta, edge) best_bn = greedy_bn # after we check the candidate edge, remove it from the graph and keep looking initial_graph.remove_edge(edge[0], edge[1]) # make the new initial graph the best graph from the previous search if add_edge: initial_graph.add_edge(best_score_delta[1][0], best_score_delta[1][1]) self.undirected = initial_graph.G self.directed = best_bn