def test_merge_nodes(): Graph = graph_from_edges(("A", "B", "C"), ("B", "D")) mGraph = merge_nodes(Graph, {"B": "A"}) assert set(mGraph.nodes) == {"A", "C", "D"} assert set(mGraph.edges) == {("A", "C"), ("A", "D")} # check Graph wasn't modified assert set(Graph.nodes) == {"A", "B", "C", "D"} mGraph = merge_nodes(Graph, {"C": "B", "D": "B"}) assert set(mGraph.nodes) == {"A", "B"} assert set(mGraph.edges) == {('A', 'B')}
def _rec_convert_graph_to_code(Graph, all_models_params, models_dico, model_name_mapping=None, composition_already_done=None): """ recursive function used to convert a Graph into a json code See convert_graph_to_code """ if composition_already_done is None: composition_already_done = set() if len(Graph.nodes) == 1: node = list(Graph.nodes)[0] return models_dico[node] node = _find_first_composition_node(Graph, composition_already_done) if node is not None: successors = list(Graph.successors(node)) assert len(successors) > 0 else: successors = [] if node is None or len(successors) == 0: ### ** It's means I'll return a GraphPipeline ** ### # 2 cases : # * nodes is None : meaning there is no composition node if len(successors) > 0: raise ValueError( "a composition node should have at most one successor '%s'" % str(node)) # assert len(successors) > 0 # it shouldn't append ... # 1) either it an original node => composition node => no successor isn't possible # 2) the node was already handled => should have been in the list edges = gh.edges_from_graph(Graph) if model_name_mapping is None: model_name_mapping = _create_name_mapping(list(Graph.nodes)) # each node in graph will be mapped to a name within the GraphPipeline models = {model_name_mapping[n]: models_dico[n] for n in Graph.nodes} edges = [ tuple((model_name_mapping[e] for e in edge)) for edge in edges ] return (SpecialModels.GraphPipeline, { "models": models, "edges": edges }) composition_already_done.add(node) # to prevent looping on the same node all_sub_branch_nodes = {} all_terminal_nodes = [] for successor in successors: sub_branch_nodes = list( gh.subbranch_search(starting_node=successor, Graph=Graph, visited={node})) all_sub_branch_nodes[successor] = sub_branch_nodes assert successor in sub_branch_nodes sub_Graph = Graph.subgraph(sub_branch_nodes) all_terminal_nodes += gh.get_terminal_nodes(sub_Graph) models_dico[successor] = _rec_convert_graph_to_code( sub_Graph, all_models_params=all_models_params, models_dico=models_dico, model_name_mapping=model_name_mapping, composition_already_done=composition_already_done, ) # Check all_s = [ frozenset(Graph.successors(t_node)) for t_node in all_terminal_nodes ] if len(set(all_s)) != 1: # By convention, if we look at the nodes AFTER the composition # (ie : the successors of the terminal nodes of the part of the graph that will be merged by the composition) # Those nodes should have the same list of successors. Those successors will be the successors of the merged node raise ValueError( "The successor at the end of the composition node %s are not always the same" % str(node)) if len(successors) == 1: # Only one sucessor of composition node models_dico[node] = (_klass_from_node(node), models_dico[successors[0]], all_models_params[node]) elif len(successors) > 1: models_dico[node] = ( _klass_from_node(node), [models_dico[successor] for successor in successors], all_models_params[node], ) else: raise NotImplementedError("can't go there") # Now I need to merge 'node' with all the sub-branches nodes_mapping = {} for successor, sub_branch_nodes in all_sub_branch_nodes.items(): for n in sub_branch_nodes: nodes_mapping[n] = node Gmerged = gh.merge_nodes(Graph, nodes_mapping=nodes_mapping) # All the node in successor will be 'fused' with 'node' ... # Recurse now, that the composition node is taken care of return _rec_convert_graph_to_code( Gmerged, all_models_params=all_models_params, models_dico=models_dico, model_name_mapping=model_name_mapping, composition_already_done=composition_already_done, )