def test_topological_generations(): G = nx.DiGraph( {1: [2, 3], 2: [4, 5], 3: [7], 4: [], 5: [6, 7], 6: [], 7: []} ).reverse() # order within each generation is inconsequential generations = [sorted(gen) for gen in nx.topological_generations(G)] expected = [[4, 6, 7], [3, 5], [2], [1]] assert generations == expected MG = nx.MultiDiGraph(G.edges) MG.add_edge(2, 1) generations = [sorted(gen) for gen in nx.topological_generations(MG)] assert generations == expected
def find_groups( pjs: Sequence[PredictionJobDataClass], randomize_groups: bool = False ) -> Tuple[nx.DiGraph, List[List[PredictionJobDataClass]]]: """Find a sequence of prediction job groups respecting dependencies. Compute groups of prediction jobs such that the prediction jobs in a group depend of at least one prediction job in the previous group and does not depend on a prediction job in the following groups. This means that all the prediction jobs in a group can be run in parallel and that if groups are treated in the given order, the dependencies of a prediction job have already been treated when the prediction job is run. Args: pjs (Iterable[PredictionJobDataClass]): The sequence of prediction jobs randomize_groups (bool, optional): whether subgroups should be randomized. Returns: nx.Digraph: the dependency graph List[List[PredictionJobDataClass]]: The list of prediction job groups """ nodes, edges = build_graph_structure(pjs) graph = build_nx_graph(nodes, edges) groups = list(nx.topological_generations(graph)) if randomize_groups: for group in groups: random.shuffle(group) # Convert groups of pj ids to groups of pjs pj_id_map = {pj["id"]: i for i, pj in enumerate(pjs)} pj_groups = [[pjs[pj_id_map[pj_id]] for pj_id in group] for group in groups] return graph, pj_groups
def read_packages(variables_fp): with open(variables_fp) as fh: variables = yaml.load(fh, Loader=yaml.CLoader) G = nx.DiGraph() for project in variables['projects']: G.add_node(project['name']) for project in variables['projects']: for dep in project.get('deps', []): G.add_edge(project['name'], dep) return list(reversed(list(nx.topological_generations(G))))
def get_execution_layers(self) -> List[Set[HierarchyPath]]: """Get step execution layers, with steps represnted by paths. An execution layer is a set of steps that can be executed in parallel. The graph's execution layers are an ordered list of these layers such that: * For a given layer, every step in the layer may be executed as soon as all steps in preceding layers have been executed. * Every step is in as early a layer as possible. In other words, the execution layers are the topological generations of the graph, prepended by any sequential steps, each in its own layer and in the order in which they were added. Returns: An ordered list of the execution layers, with each step represented by its path. """ layers = nx.topological_generations(self._graph) to_return = [set([step]) for step in self._sequential_steps] to_return += [set(layer) for layer in layers] return to_return
def test_topological_generations_cycle(): G = nx.DiGraph([[2, 1], [3, 1], [1, 2]]) with pytest.raises(nx.NetworkXUnfeasible): list(nx.topological_generations(G))
def test_topological_generations_empty(): G = nx.DiGraph() assert list(nx.topological_generations(G)) == []
def topological_sort(G): """Returns a generator of nodes in topologically sorted order. A topological sort is a nonunique permutation of the nodes of a directed graph such that an edge from u to v implies that u appears before v in the topological sort order. This ordering is valid only if the graph has no directed cycles. Parameters ---------- G : NetworkX digraph A directed acyclic graph (DAG) Yields ------ nodes Yields the nodes in topological sorted order. Raises ------ NetworkXError Topological sort is defined for directed graphs only. If the graph `G` is undirected, a :exc:`NetworkXError` is raised. NetworkXUnfeasible If `G` is not a directed acyclic graph (DAG) no topological sort exists and a :exc:`NetworkXUnfeasible` exception is raised. This can also be raised if `G` is changed while the returned iterator is being processed RuntimeError If `G` is changed while the returned iterator is being processed. Examples -------- To get the reverse order of the topological sort: >>> DG = nx.DiGraph([(1, 2), (2, 3)]) >>> list(reversed(list(nx.topological_sort(DG)))) [3, 2, 1] If your DiGraph naturally has the edges representing tasks/inputs and nodes representing people/processes that initiate tasks, then topological_sort is not quite what you need. You will have to change the tasks to nodes with dependence reflected by edges. The result is a kind of topological sort of the edges. This can be done with :func:`networkx.line_graph` as follows: >>> list(nx.topological_sort(nx.line_graph(DG))) [(1, 2), (2, 3)] Notes ----- This algorithm is based on a description and proof in "Introduction to Algorithms: A Creative Approach" [1]_ . See also -------- is_directed_acyclic_graph, lexicographical_topological_sort References ---------- .. [1] Manber, U. (1989). *Introduction to Algorithms - A Creative Approach.* Addison-Wesley. """ for generation in nx.topological_generations(G): yield from generation
def plot(self, output: str): """ Visualize a Bayesian Network. Result will be saved in parent directory in folder visualization_result. output: str name of output file """ if not output.endswith('.html'): logger_network.error("This version allows only html format.") return None G = nx.DiGraph() nodes = [node.name for node in self.nodes] G.add_nodes_from(nodes) G.add_edges_from(self.edges) network = Network(height="800px", width="100%", notebook=True, directed=nx.is_directed(G), layout='hierarchical') nodes_sorted = np.array(list(nx.topological_generations(G)), dtype=object) # Qualitative class of colormaps q_classes = [ 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c' ] hex_colors = [] for cls in q_classes: rgb_colors = plt.get_cmap(cls).colors hex_colors.extend([ matplotlib.colors.rgb2hex(rgb_color) for rgb_color in rgb_colors ]) hex_colors = np.array(hex_colors) # Number_of_colors in matplotlib in Qualitative class = 144 class_number = len(set([node.type for node in self.nodes])) hex_colors_indexes = [ random.randint(0, len(hex_colors) - 1) for _ in range(class_number) ] hex_colors_picked = hex_colors[hex_colors_indexes] class2color = { cls: color for cls, color in zip(set([node.type for node in self.nodes]), hex_colors_picked) } name2class = {node.name: node.type for node in self.nodes} for level in range(len(nodes_sorted)): for node_i in range(len(nodes_sorted[level])): name = nodes_sorted[level][node_i] cls = name2class[name] color = class2color[cls] network.add_node(name, label=name, color=color, size=45, level=level, font={'size': 36}, title=f'Узел байесовской сети {name} ({cls})') for edge in G.edges: network.add_edge(edge[0], edge[1]) network.hrepulsion(node_distance=300, central_gravity=0.5) if not (os.path.exists('visualization_result')): os.mkdir("visualization_result") return network.show(f'visualization_result/' + output)