1"""
2Flow Hierarchy.
3"""
4
5import networkx as nx
6
7__all__ = ["flow_hierarchy"]
8
9
10@nx._dispatchable(edge_attrs="weight")
11def flow_hierarchy(G, weight=None):
12 """Returns the flow hierarchy of a directed network.
13
14 Flow hierarchy is defined as the fraction of edges not participating
15 in cycles in a directed graph [1]_.
16
17 Parameters
18 ----------
19 G : DiGraph or MultiDiGraph
20 A directed graph
21
22 weight : string, optional (default=None)
23 Attribute to use for edge weights. If None the weight defaults to 1.
24
25 Returns
26 -------
27 h : float
28 Flow hierarchy value
29
30 Raises
31 ------
32 NetworkXError
33 If `G` is not a directed graph or if `G` has no edges.
34
35 Notes
36 -----
37 The algorithm described in [1]_ computes the flow hierarchy through
38 exponentiation of the adjacency matrix. This function implements an
39 alternative approach that finds strongly connected components.
40 An edge is in a cycle if and only if it is in a strongly connected
41 component, which can be found in $O(m)$ time using Tarjan's algorithm.
42
43 References
44 ----------
45 .. [1] Luo, J.; Magee, C.L. (2011),
46 Detecting evolving patterns of self-organizing networks by flow
47 hierarchy measurement, Complexity, Volume 16 Issue 6 53-61.
48 DOI: 10.1002/cplx.20368
49 http://web.mit.edu/~cmagee/www/documents/28-DetectingEvolvingPatterns_FlowHierarchy.pdf
50 """
51 # corner case: G has no edges
52 if nx.is_empty(G):
53 raise nx.NetworkXError("flow_hierarchy not applicable to empty graphs")
54 if not G.is_directed():
55 raise nx.NetworkXError("G must be a digraph in flow_hierarchy")
56 scc = nx.strongly_connected_components(G)
57 return 1 - sum(G.subgraph(c).size(weight) for c in scc) / G.size(weight)