Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/approximation/steinertree.py: 16%
85 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1from itertools import chain
3import networkx as nx
4from networkx.utils import not_implemented_for, pairwise
6__all__ = ["metric_closure", "steiner_tree"]
9@not_implemented_for("directed")
10@nx._dispatch(edge_attrs="weight")
11def metric_closure(G, weight="weight"):
12 """Return the metric closure of a graph.
14 The metric closure of a graph *G* is the complete graph in which each edge
15 is weighted by the shortest path distance between the nodes in *G* .
17 Parameters
18 ----------
19 G : NetworkX graph
21 Returns
22 -------
23 NetworkX graph
24 Metric closure of the graph `G`.
26 """
27 M = nx.Graph()
29 Gnodes = set(G)
31 # check for connected graph while processing first node
32 all_paths_iter = nx.all_pairs_dijkstra(G, weight=weight)
33 u, (distance, path) = next(all_paths_iter)
34 if Gnodes - set(distance):
35 msg = "G is not a connected graph. metric_closure is not defined."
36 raise nx.NetworkXError(msg)
37 Gnodes.remove(u)
38 for v in Gnodes:
39 M.add_edge(u, v, distance=distance[v], path=path[v])
41 # first node done -- now process the rest
42 for u, (distance, path) in all_paths_iter:
43 Gnodes.remove(u)
44 for v in Gnodes:
45 M.add_edge(u, v, distance=distance[v], path=path[v])
47 return M
50def _mehlhorn_steiner_tree(G, terminal_nodes, weight):
51 paths = nx.multi_source_dijkstra_path(G, terminal_nodes)
53 d_1 = {}
54 s = {}
55 for v in G.nodes():
56 s[v] = paths[v][0]
57 d_1[(v, s[v])] = len(paths[v]) - 1
59 # G1-G4 names match those from the Mehlhorn 1988 paper.
60 G_1_prime = nx.Graph()
61 for u, v, data in G.edges(data=True):
62 su, sv = s[u], s[v]
63 weight_here = d_1[(u, su)] + data.get(weight, 1) + d_1[(v, sv)]
64 if not G_1_prime.has_edge(su, sv):
65 G_1_prime.add_edge(su, sv, weight=weight_here)
66 else:
67 new_weight = min(weight_here, G_1_prime[su][sv][weight])
68 G_1_prime.add_edge(su, sv, weight=new_weight)
70 G_2 = nx.minimum_spanning_edges(G_1_prime, data=True)
72 G_3 = nx.Graph()
73 for u, v, d in G_2:
74 path = nx.shortest_path(G, u, v, weight)
75 for n1, n2 in pairwise(path):
76 G_3.add_edge(n1, n2)
78 G_3_mst = list(nx.minimum_spanning_edges(G_3, data=False))
79 if G.is_multigraph():
80 G_3_mst = (
81 (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in G_3_mst
82 )
83 G_4 = G.edge_subgraph(G_3_mst).copy()
84 _remove_nonterminal_leaves(G_4, terminal_nodes)
85 return G_4.edges()
88def _kou_steiner_tree(G, terminal_nodes, weight):
89 # H is the subgraph induced by terminal_nodes in the metric closure M of G.
90 M = metric_closure(G, weight=weight)
91 H = M.subgraph(terminal_nodes)
93 # Use the 'distance' attribute of each edge provided by M.
94 mst_edges = nx.minimum_spanning_edges(H, weight="distance", data=True)
96 # Create an iterator over each edge in each shortest path; repeats are okay
97 mst_all_edges = chain.from_iterable(pairwise(d["path"]) for u, v, d in mst_edges)
98 if G.is_multigraph():
99 mst_all_edges = (
100 (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight]))
101 for u, v in mst_all_edges
102 )
104 # Find the MST again, over this new set of edges
105 G_S = G.edge_subgraph(mst_all_edges)
106 T_S = nx.minimum_spanning_edges(G_S, weight="weight", data=False)
108 # Leaf nodes that are not terminal might still remain; remove them here
109 T_H = G.edge_subgraph(T_S).copy()
110 _remove_nonterminal_leaves(T_H, terminal_nodes)
112 return T_H.edges()
115def _remove_nonterminal_leaves(G, terminals):
116 terminals_set = set(terminals)
117 for n in list(G.nodes):
118 if n not in terminals_set and G.degree(n) == 1:
119 G.remove_node(n)
122ALGORITHMS = {
123 "kou": _kou_steiner_tree,
124 "mehlhorn": _mehlhorn_steiner_tree,
125}
128@not_implemented_for("directed")
129@nx._dispatch(edge_attrs="weight")
130def steiner_tree(G, terminal_nodes, weight="weight", method=None):
131 r"""Return an approximation to the minimum Steiner tree of a graph.
133 The minimum Steiner tree of `G` w.r.t a set of `terminal_nodes` (also *S*)
134 is a tree within `G` that spans those nodes and has minimum size (sum of
135 edge weights) among all such trees.
137 The approximation algorithm is specified with the `method` keyword
138 argument. All three available algorithms produce a tree whose weight is
139 within a ``(2 - (2 / l))`` factor of the weight of the optimal Steiner tree,
140 where ``l`` is the minimum number of leaf nodes across all possible Steiner
141 trees.
143 * ``"kou"`` [2]_ (runtime $O(|S| |V|^2)$) computes the minimum spanning tree of
144 the subgraph of the metric closure of *G* induced by the terminal nodes,
145 where the metric closure of *G* is the complete graph in which each edge is
146 weighted by the shortest path distance between the nodes in *G*.
148 * ``"mehlhorn"`` [3]_ (runtime $O(|E|+|V|\log|V|)$) modifies Kou et al.'s
149 algorithm, beginning by finding the closest terminal node for each
150 non-terminal. This data is used to create a complete graph containing only
151 the terminal nodes, in which edge is weighted with the shortest path
152 distance between them. The algorithm then proceeds in the same way as Kou
153 et al..
155 Parameters
156 ----------
157 G : NetworkX graph
159 terminal_nodes : list
160 A list of terminal nodes for which minimum steiner tree is
161 to be found.
163 weight : string (default = 'weight')
164 Use the edge attribute specified by this string as the edge weight.
165 Any edge attribute not present defaults to 1.
167 method : string, optional (default = 'kou')
168 The algorithm to use to approximate the Steiner tree.
169 Supported options: 'kou', 'mehlhorn'.
170 Other inputs produce a ValueError.
172 Returns
173 -------
174 NetworkX graph
175 Approximation to the minimum steiner tree of `G` induced by
176 `terminal_nodes` .
178 Notes
179 -----
180 For multigraphs, the edge between two nodes with minimum weight is the
181 edge put into the Steiner tree.
184 References
185 ----------
186 .. [1] Steiner_tree_problem on Wikipedia.
187 https://en.wikipedia.org/wiki/Steiner_tree_problem
188 .. [2] Kou, L., G. Markowsky, and L. Berman. 1981.
189 ‘A Fast Algorithm for Steiner Trees’.
190 Acta Informatica 15 (2): 141–45.
191 https://doi.org/10.1007/BF00288961.
192 .. [3] Mehlhorn, Kurt. 1988.
193 ‘A Faster Approximation Algorithm for the Steiner Problem in Graphs’.
194 Information Processing Letters 27 (3): 125–28.
195 https://doi.org/10.1016/0020-0190(88)90066-X.
196 """
197 if method is None:
198 import warnings
200 msg = (
201 "steiner_tree will change default method from 'kou' to 'mehlhorn' "
202 "in version 3.2.\nSet the `method` kwarg to remove this warning."
203 )
204 warnings.warn(msg, FutureWarning, stacklevel=4)
205 method = "kou"
207 try:
208 algo = ALGORITHMS[method]
209 except KeyError as e:
210 msg = f"{method} is not a valid choice for an algorithm."
211 raise ValueError(msg) from e
213 edges = algo(G, terminal_nodes, weight)
214 # For multigraph we should add the minimal weight edge keys
215 if G.is_multigraph():
216 edges = (
217 (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in edges
218 )
219 T = G.edge_subgraph(edges)
220 return T