networkx 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +18 -11
  3. data/CONTRIBUTING.md +2 -2
  4. data/Guardfile +1 -1
  5. data/ISSUE_TEMPLATE.md +15 -0
  6. data/PULL_REQUEST_TEMPLATE.md +12 -0
  7. data/README.md +4 -4
  8. data/RELEASE_POLICY.md +20 -0
  9. data/lib/networkx.rb +37 -1
  10. data/lib/networkx/auxillary_functions/cliques.rb +65 -0
  11. data/lib/networkx/auxillary_functions/cycles.rb +104 -0
  12. data/lib/networkx/auxillary_functions/dag.rb +54 -0
  13. data/lib/networkx/auxillary_functions/eccentricity.rb +36 -0
  14. data/lib/networkx/auxillary_functions/mis.rb +23 -0
  15. data/lib/networkx/auxillary_functions/mst.rb +35 -0
  16. data/lib/networkx/auxillary_functions/union_find.rb +24 -0
  17. data/lib/networkx/auxillary_functions/vitality.rb +13 -0
  18. data/lib/networkx/auxillary_functions/wiener.rb +13 -0
  19. data/lib/networkx/converters/to_csv.rb +47 -0
  20. data/lib/networkx/converters/to_json.rb +39 -0
  21. data/lib/networkx/digraph.rb +228 -0
  22. data/lib/networkx/flow/capacityscaling.rb +255 -0
  23. data/lib/networkx/flow/edmondskarp.rb +113 -0
  24. data/lib/networkx/flow/preflowpush.rb +252 -0
  25. data/lib/networkx/flow/shortestaugmentingpath.rb +157 -0
  26. data/lib/networkx/flow/utils.rb +160 -0
  27. data/lib/networkx/graph.rb +341 -0
  28. data/lib/networkx/link_analysis/hits.rb +59 -0
  29. data/lib/networkx/link_analysis/pagerank.rb +47 -0
  30. data/lib/networkx/multidigraph.rb +240 -0
  31. data/lib/networkx/multigraph.rb +171 -0
  32. data/lib/networkx/operators/all.rb +61 -0
  33. data/lib/networkx/operators/binary.rb +244 -0
  34. data/lib/networkx/operators/product.rb +204 -0
  35. data/lib/networkx/operators/unary.rb +17 -0
  36. data/lib/networkx/shortest_path/astar.rb +71 -0
  37. data/lib/networkx/shortest_path/dense.rb +31 -0
  38. data/lib/networkx/shortest_path/unweighted.rb +139 -0
  39. data/lib/networkx/shortest_path/weighted.rb +408 -0
  40. data/lib/networkx/to_matrix.rb +52 -0
  41. data/lib/networkx/traversals/bfs.rb +58 -0
  42. data/lib/networkx/traversals/dfs.rb +79 -0
  43. data/lib/networkx/traversals/edge_dfs.rb +90 -0
  44. data/lib/networkx/version.rb +1 -1
  45. data/networkx.gemspec +4 -1
  46. metadata +70 -4
@@ -0,0 +1,17 @@
1
+ module NetworkX
2
+ # Performs the complement operation on the graph
3
+ #
4
+ # @param [Graph, DiGraph, MultiGraph, MultiDiGraph] graph
5
+ #
6
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] the complement of the graph
7
+ def self.complement(graph)
8
+ result = Marshal.load(Marshal.dump(graph))
9
+ result.clear
10
+
11
+ result.add_nodes(graph.nodes.map { |u, attrs| [u, attrs] })
12
+ graph.adj.each do |u, u_edges|
13
+ graph.nodes.each { |v, attrs| result.add_edge(u, v, attrs) if !u_edges.key?(v) && u != v }
14
+ end
15
+ result
16
+ end
17
+ end
@@ -0,0 +1,71 @@
1
+ module NetworkX
2
+ # TODO: Reduce method complexity and method length
3
+
4
+ # Returns path using astar algorithm between source and target
5
+ #
6
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
7
+ # @param source [Object] a node to start astar from
8
+ # @param target [Object] a node to end astar
9
+ # @param heuristic [] a lambda to compute heuristic b/w two nodes
10
+ #
11
+ # @return [Array<Object>] an array of nodes forming a path between source
12
+ # and target
13
+ def self.astar_path(graph, source, target, heuristic=nil)
14
+ warn 'A* is not implemented for MultiGraph and MultiDiGraph'
15
+ raise ArgumentError, 'Either source or target is not in graph' unless graph.node?(source) && graph.node?(target)
16
+ count = ->(i) { i + 1 }
17
+ i = -1
18
+ heuristic ||= (->(_u, _v) { 0 })
19
+ heap = Heap.new { |x, y| x[0] < y[0] || (x[0] == y[0] && x[1] < y[1]) }
20
+ heap << [0, count.call(i), source, 0, nil]
21
+ enqueued, explored = {}, {}
22
+
23
+ until heap.empty?
24
+ _, _, curnode, dist, parent = heap.pop
25
+ if curnode == target
26
+ path = [curnode]
27
+ node = parent
28
+ until node.nil?
29
+ path << node
30
+ node = explored[node]
31
+ end
32
+ path.reverse
33
+ return path
34
+ end
35
+
36
+ next if explored.key?(curnode)
37
+
38
+ explored[curnode] = parent
39
+
40
+ graph.adj[curnode].each do |u, attrs|
41
+ next if explored.key?(u)
42
+ ncost = dist + (attrs[:weight] || 1)
43
+ if enqueued.key?(u)
44
+ qcost, = enqueued[u]
45
+ next if qcost <= ncost
46
+ else
47
+ h = heuristic.call(u, target)
48
+ enqueued[u] = ncost, h
49
+ heap << [ncost + h, count.call(i), u, ncost, curnode]
50
+ end
51
+ end
52
+ end
53
+ raise ArgumentError, 'Target not reachable!'
54
+ end
55
+
56
+ # Returns astar path length b/w source and target
57
+ #
58
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
59
+ # @param source [Object] a node to start astar from
60
+ # @param target [Object] a node to end astar
61
+ # @param heuristic [] a lambda to compute heuristic b/w two nodes
62
+ #
63
+ # @return [Numeric] the length of the path
64
+ def self.astar_path_length(graph, source, target, heuristic=nil)
65
+ raise ArgumentError, 'Either source or target is not in graph' unless graph.node?(source) && graph.node?(target)
66
+ path = astar_path(graph, source, target, heuristic)
67
+ path_length = 0
68
+ (1..(path.length - 1)).each { |i| path_length += (graph.adj[path[i - 1]][path[i]][:weight] || 1) }
69
+ path_length
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ module NetworkX
2
+ # TODO: Reduce method length and method complexity
3
+
4
+ # Returns the all pair distance between all the nodes
5
+ #
6
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
7
+ #
8
+ # @return [Hash{ Object => { Object => { Numeric }}}] a hash containing distances
9
+ # b/w all pairs of nodes
10
+ def self.floyd_warshall(graph)
11
+ a, index = to_matrix(graph, Float::INFINITY, 'min')
12
+ nodelen = graph.nodes.length
13
+ (0..(nodelen - 1)).each { |i| a[i, i] = 0 }
14
+ (0..(nodelen - 1)).each do |k|
15
+ (0..(nodelen - 1)).each do |i|
16
+ (0..(nodelen - 1)).each do |j|
17
+ a[i, j] = [a[i, j], a[i, k] + a[k, j]].min
18
+ end
19
+ end
20
+ end
21
+
22
+ as_hash = {}
23
+ (0..(nodelen - 1)).each do |i|
24
+ (0..(nodelen - 1)).each do |j|
25
+ as_hash[index[i]] = {} unless as_hash.key?(index[i])
26
+ as_hash[index[i]][index[j]] = a[i, j]
27
+ end
28
+ end
29
+ as_hash
30
+ end
31
+ end
@@ -0,0 +1,139 @@
1
+ module NetworkX
2
+ # TODO: Reduce method length
3
+
4
+ # Helper function for single source shortest path length
5
+ def self.help_single_shortest_path_length(adj, firstlevel, cutoff)
6
+ iterator = Enumerator.new do |e|
7
+ seen = {}
8
+ level = 0
9
+ nextlevel = firstlevel
10
+
11
+ while !nextlevel.empty? && cutoff >= level
12
+ thislevel = nextlevel
13
+ nextlevel = {}
14
+ thislevel.each do |u, _attrs|
15
+ next if seen.key?(u)
16
+ seen[u] = level
17
+ nextlevel.merge!(adj[u])
18
+ e.yield [u, level]
19
+ end
20
+ level += 1
21
+ end
22
+ seen.clear
23
+ end
24
+ iterator
25
+ end
26
+
27
+ # Computes shortest path values to all nodes from a given node
28
+ #
29
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
30
+ # @param source [Object] source to compute path length from
31
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
32
+ #
33
+ # @return [Array<Object, Numeric>] path lengths for all nodes
34
+ def self.single_source_shortest_path_length(graph, source, cutoff=nil)
35
+ raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)
36
+ cutoff = Float::INFINITY if cutoff.nil?
37
+ nextlevel = {source => 1}
38
+ help_single_shortest_path_length(graph.adj, nextlevel, cutoff).take(graph.nodes.length)
39
+ end
40
+
41
+ # Computes shortest path values to all nodes from all nodes
42
+ #
43
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
44
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
45
+ #
46
+ # @return [Array<Object, Array<Object, Numeric>>] path lengths for all nodes from all nodes
47
+ def self.all_pairs_shortest_path_length(graph, cutoff=nil)
48
+ shortest_paths = []
49
+ graph.nodes.each_key { |n| shortest_paths << [n, single_source_shortest_path_length(graph, n, cutoff)] }
50
+ shortest_paths
51
+ end
52
+
53
+ # TODO: Reduce method length
54
+
55
+ # Helper function for finding single source shortest path
56
+ def self.help_single_shortest_path(adj, firstlevel, paths, cutoff)
57
+ level = 0
58
+ nextlevel = firstlevel
59
+ while !nextlevel.empty? && cutoff > level
60
+ thislevel = nextlevel
61
+ nextlevel = {}
62
+ thislevel.each_key do |u|
63
+ adj[u].each_key do |v|
64
+ unless paths.key?(v)
65
+ paths[v] = paths[u] + [v]
66
+ nextlevel[v] = 1
67
+ end
68
+ end
69
+ end
70
+ level += 1
71
+ end
72
+ paths
73
+ end
74
+
75
+ # Computes single source shortest path from a node to every other node
76
+ #
77
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
78
+ # @param source [Object] source from which shortest paths are needed
79
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
80
+ #
81
+ # @return [Array<Object, Array<Object, Array<Object>>>] path lengths for all nodes from all nodes
82
+ def self.single_source_shortest_path(graph, source, cutoff=nil)
83
+ raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)
84
+ cutoff = Float::INFINITY if cutoff.nil?
85
+ nextlevel = {source => 1}
86
+ paths = {source => [source]}
87
+ help_single_shortest_path(graph.adj, nextlevel, paths, cutoff)
88
+ end
89
+
90
+ # Computes shortest paths to all nodes from all nodes
91
+ #
92
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
93
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
94
+ #
95
+ # @return [Array<Object, Hash {Object => Array<Object> }>] paths for all nodes from all nodes
96
+ def self.all_pairs_shortest_path(graph, cutoff=nil)
97
+ shortest_paths = []
98
+ graph.nodes.each_key { |n| shortest_paths << [n, single_source_shortest_path(graph, n, cutoff)] }
99
+ shortest_paths
100
+ end
101
+
102
+ # TODO: Reduce method length and method complexity
103
+
104
+ # Computes shortest paths to all nodes from all nodes
105
+ #
106
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
107
+ # @param source [Object] source for which predecessors are needed
108
+ # @param cutoff [Numeric, nil] cutoff for finding predecessor
109
+ # @param return_seen [Boolean] if true, returns seen dict
110
+ #
111
+ # @return [Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>,
112
+ # Hash{ Object => Array<Object> }]
113
+ # predecessors of a given node and/or seen dict
114
+ def self.predecessor(graph, source, cutoff=nil, return_seen=false)
115
+ raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)
116
+ level = 0
117
+ nextlevel = [source]
118
+ seen = {source => level}
119
+ pred = {source => []}
120
+ until nextlevel.empty?
121
+ level += 1
122
+ thislevel = nextlevel
123
+ nextlevel = []
124
+ thislevel.each do |u|
125
+ graph.adj[u].each_key do |v|
126
+ if !seen.key?(v)
127
+ pred[v] = [u]
128
+ seen[v] = level
129
+ nextlevel << v
130
+ elsif seen[v] == level
131
+ pred[v] << u
132
+ end
133
+ end
134
+ end
135
+ break if cutoff.nil? && cutoff <= level
136
+ end
137
+ return_seen ? [pred, seen] : pred
138
+ end
139
+ end
@@ -0,0 +1,408 @@
1
+ # TODO: Reduce module length
2
+
3
+ module NetworkX
4
+ # Helper function to extract weight from a adjecency hash
5
+ def self.get_weight(graph)
6
+ weight_get = lambda do |_, _, attrs|
7
+ return attrs[:weight] || 1 unless graph.multigraph?
8
+ attrs.group_by { |_k, vals| vals[:weight] || 1 }.keys.max
9
+ end
10
+ weight_get
11
+ end
12
+
13
+ # TODO: Reduce method length and method complexity
14
+
15
+ # Helper function for multisource dijkstra
16
+ def self.help_multisource_dijkstra(graph, sources, weight, pred=nil, paths=nil, cutoff=nil, target=nil)
17
+ count = ->(i) { i + 1 }
18
+ i = -1
19
+ dist = {}
20
+ seen = {}
21
+ fringe = Heap.new { |x, y| x[0] < y[0] || (x[0] == y[0] && x[1] < y[1]) }
22
+ sources.each do |s|
23
+ seen[s] = 0
24
+ fringe << [0, count.call(i), s]
25
+ end
26
+
27
+ until fringe.empty?
28
+ d, _, v = fringe.pop
29
+ next if dist.key?(v)
30
+ dist[v] = d
31
+ break if v == target
32
+ graph.adj[v].each do |u, attrs|
33
+ cost = weight.call(v, u, attrs)
34
+ next if cost.nil?
35
+ vu_dist = dist[v] + cost
36
+ next if !cutoff.nil? && vu_dist > cutoff
37
+ if dist.key?(u)
38
+ raise ValueError, 'Contradictory weights found!' if vu_dist < dist[u]
39
+ elsif !seen.key?(u) || vu_dist < seen[u]
40
+ seen[u] = vu_dist
41
+ fringe << [vu_dist, count.call(i), u]
42
+ paths[u] = paths[v] + [u] unless paths.nil?
43
+ pred[u] = [v] unless pred.nil?
44
+ elsif vu_dist == seen[u]
45
+ pred[u] << v unless pred.nil?
46
+ end
47
+ end
48
+ end
49
+ dist
50
+ end
51
+
52
+ # Helper function for single source dijkstra
53
+ def self.help_dijkstra(graph, source, weight, pred=nil, paths=nil, cutoff=nil, target=nil)
54
+ help_multisource_dijkstra(graph, [source], weight, pred, paths, cutoff, target)
55
+ end
56
+
57
+ # Computes shortest paths and path lengths to a target from one of the nodes
58
+ #
59
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
60
+ # @param sources [Array<Object>] Array of sources
61
+ # @param target [Object, nil] target node for the dijkstra algorithm
62
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
63
+ #
64
+ # @return [Numeric, Array<Object>] path lengths for all nodes
65
+ def self.multisource_dijkstra(graph, sources, target=nil, cutoff=nil)
66
+ raise ValueError, 'Sources cannot be empty' if sources.empty?
67
+ return [0, [target]] if sources.include?(target)
68
+ paths = {}
69
+ weight = get_weight(graph)
70
+ sources.each { |source| paths[source] = [source] }
71
+ dist = help_multisource_dijkstra(graph, sources, weight, nil, paths, cutoff, target)
72
+ return [dist, paths] if target.nil?
73
+ raise KeyError, "No path to #{target}!" unless dist.key?(target)
74
+ [dist[target], paths[target]]
75
+ end
76
+
77
+ # Computes shortest path lengths to any from the given nodes
78
+ #
79
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
80
+ # @param sources [Array<Object>] Array of sources
81
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
82
+ #
83
+ # @return [Hash{ Object => Numeric }] path lengths for any nodes from given nodes
84
+ def self.multisource_dijkstra_path_length(graph, sources, cutoff=nil)
85
+ raise ValueError, 'Sources cannot be empty' if sources.empty?
86
+ weight = get_weight(graph)
87
+ help_multisource_dijkstra(graph, sources, weight, nil, nil, cutoff)
88
+ end
89
+
90
+ # Computes shortest paths to any from the given nodes
91
+ #
92
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
93
+ # @param sources [Array<Object>] Array of sources
94
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
95
+ #
96
+ # @return [Hash{ Object => Array<Object> }] paths for any nodes from given nodes
97
+ def self.multisource_dijkstra_path(graph, sources, cutoff=nil)
98
+ _, path = multisource_dijkstra(graph, sources, nil, cutoff)
99
+ path
100
+ end
101
+
102
+ # Computes shortest paths and path distances to all nodes/target from the given node
103
+ #
104
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
105
+ # @param source [Object] source
106
+ # @param target [Object] target
107
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
108
+ #
109
+ # @return [Hash{ Object => Array<Object> }, Array<Object>] paths for all nodes/target node from given node
110
+ def self.singlesource_dijkstra(graph, source, target=nil, cutoff=nil)
111
+ multisource_dijkstra(graph, [source], target, cutoff)
112
+ end
113
+
114
+ # Computes shortest path lengths to all nodes from the given node
115
+ #
116
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
117
+ # @param source [Object] source
118
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
119
+ #
120
+ # @return [Hash{ Object => Numeric }] path lengths for all nodes from given node
121
+ def self.singlesource_dijkstra_path_length(graph, source, cutoff=nil)
122
+ multisource_dijkstra_path_length(graph, [source], cutoff)
123
+ end
124
+
125
+ # Computes shortest paths to all nodes from the given node
126
+ #
127
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
128
+ # @param source [Object] source
129
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
130
+ #
131
+ # @return [Hash{ Object => Array<Object> }] paths for all nodes from given node
132
+ def self.singlesource_dijkstra_path(graph, source, cutoff=nil)
133
+ multisource_dijkstra_path(graph, [source], cutoff)
134
+ end
135
+
136
+ # Computes shortest path length to target from the given node
137
+ #
138
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
139
+ # @param source [Object] source
140
+ # @param target [Object] target
141
+ #
142
+ # @return [Numeric] path length for target node from given node
143
+ def self.dijkstra_path_length(graph, source, target)
144
+ return 0 if source == target
145
+ weight = get_weight(graph)
146
+ length = help_dijkstra(graph, source, weight, nil, nil, nil, target)
147
+ raise KeyError, 'Node not reachable!' unless length.key?(target)
148
+ length[target]
149
+ end
150
+
151
+ # Computes shortest path to target from the given node
152
+ #
153
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
154
+ # @param source [Object] source
155
+ # @param target [Object] target
156
+ #
157
+ # @return [Numeric] path for target node from given node
158
+ def self.dijkstra_path(graph, source, target)
159
+ _, path = singlesource_dijkstra(graph, source, target)
160
+ path
161
+ end
162
+
163
+ # Computes weighted shortest path length and predecessors
164
+ #
165
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
166
+ # @param source [Object] source
167
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
168
+ #
169
+ # @return [<Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>]
170
+ # predcessor hash and distance hash
171
+ def self.dijkstra_predecessor_distance(graph, source, cutoff=nil)
172
+ weight = get_weight(graph)
173
+ pred = {source => []}
174
+ [pred, help_dijkstra(graph, source, weight, pred, nil, cutoff)]
175
+ end
176
+
177
+ # Finds shortest weighted paths and lengths between all nodes
178
+ #
179
+ # @param graph [Graph, DiGrhelp_dijkaph, MultiGraph, MultiDiGraph] a graph
180
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
181
+ #
182
+ # @return [Array<Object, Array<Hash{ Object => Numeric }, Hash{ Object => Array<Object> }>>]
183
+ # paths and path lengths between all nodes
184
+ def self.all_pairs_dijkstra(graph, cutoff=nil)
185
+ path = []
186
+ graph.nodes.each_key { |n| path << [n, singlesource_dijkstra(graph, n, nil, cutoff)] }
187
+ path
188
+ end
189
+
190
+ # Finds shortest weighted path length between all nodes
191
+ #
192
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
193
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
194
+ #
195
+ # @return [Array<Object, Hash{ Object => Numeric }>] path lengths between all nodes
196
+ def self.all_pairs_dijkstra_path_length(graph, cutoff=nil)
197
+ path_lengths = []
198
+ graph.nodes.each_key { |n| path_lengths << [n, singlesource_dijkstra_path_length(graph, n, cutoff)] }
199
+ path_lengths
200
+ end
201
+
202
+ # Finds shortest weighted paths between all nodes
203
+ #
204
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
205
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
206
+ #
207
+ # @return [Array<Object, Hash{ Object => Array<Object> }>] path lengths between all nodes
208
+ def self.all_pairs_dijkstra_path(graph, cutoff=nil)
209
+ paths = []
210
+ graph.nodes.each_key { |n| paths << singlesource_dijkstra_path(graph, n, cutoff) }
211
+ paths
212
+ end
213
+
214
+ # TODO: Reduce method length and method complexity
215
+
216
+ # Helper function for bellman ford
217
+ def self.help_bellman_ford(graph, sources, weight, pred=nil, paths=nil, dist=nil, cutoff=nil, target=nil)
218
+ pred = sources.product([[]]).to_h if pred.nil?
219
+ dist = sources.product([0]).to_h if dist.nil?
220
+
221
+ inf, n, count, q, in_q = Float::INFINITY, graph.nodes.length, {}, sources.clone, Set.new(sources)
222
+ until q.empty?
223
+ u = q.shift
224
+ in_q.delete(u)
225
+ skip = false
226
+ pred[u].each { |k| skip = true if in_q.include?(k) }
227
+ next if skip
228
+ dist_u = dist[u]
229
+ graph.adj[u].each do |v, e|
230
+ dist_v = dist_u + weight.call(u, v, e)
231
+ next if !cutoff.nil? && dist_v > cutoff
232
+ next if !target.nil? && dist_v > (dist[target] || inf)
233
+ if dist_v < (dist[v] || inf)
234
+ unless in_q.include?(v)
235
+ q << v
236
+ in_q.add(v)
237
+ count_v = (count[v] || 0) + 1
238
+ raise ArgumentError, 'Negative edge cycle detected!' if count_v == n
239
+ count[v] = count_v
240
+ end
241
+ dist[v] = dist_v
242
+ pred[v] = [u]
243
+ elsif dist.key?(v) && dist_v == dist[v]
244
+ pred[v] << u
245
+ end
246
+ end
247
+ end
248
+ unless paths.nil?
249
+ dsts = target.nil? ? pred : [target]
250
+ dsts.each_key do |dst|
251
+ path, cur = [dst], dst
252
+ until pred[cur][0].nil?
253
+ cur = pred[cur][0]
254
+ path << cur
255
+ end
256
+ path.reverse
257
+ paths[dst] = path
258
+ end
259
+ end
260
+ dist
261
+ end
262
+
263
+ # Finds shortest weighted path lengths and predecessors on shortest paths
264
+ #
265
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
266
+ # @param source [Object] source
267
+ # @param target [Object, nil] target
268
+ # @param cutoff [Numeric, nil] cutoff for the dijkstra algorithm
269
+ #
270
+ # @return [Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>] predecessors and distances
271
+ def self.bellmanford_predecesor_distance(graph, source, target=nil, cutoff=nil)
272
+ raise ArgumentError, 'Node not found!' unless graph.node?(source)
273
+ weight = get_weight(graph)
274
+ # TODO: Detection of selfloop edges
275
+ dist = {source => 0}
276
+ pred = {source => []}
277
+ return [pred, dist] if graph.nodes.length == 1
278
+ dist = help_bellman_ford(graph, [source], weight, pred, nil, dist, cutoff, target)
279
+ [pred, dist]
280
+ end
281
+
282
+ def self.singlesource_bellmanford(graph, source, target=nil, cutoff=nil)
283
+ return [0, [source]] if source == target
284
+ weight = get_weight(graph)
285
+ paths = {source => [source]}
286
+ dist = help_bellman_ford(graph, [source], weight, nil, paths, nil, cutoff, target)
287
+ return [dist, paths] if target.nil?
288
+ raise ArgumentError, 'Node not reachable!' unless dist.key?(target)
289
+ [dist[target], paths[target]]
290
+ end
291
+
292
+ # Length of shortest path from source to target using Bellman Ford algorithm
293
+ #
294
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
295
+ # @param source [Object] source
296
+ # @param target [Object] target
297
+ #
298
+ # @return [Numeric] distance between source and target
299
+ def self.bellmanford_path_length(graph, source, target)
300
+ return 0 if source == target
301
+ weight = get_weight(graph)
302
+ length = help_bellman_ford(graph, [source], weight, nil, nil, nil, nil, target=target)
303
+ raise ArgumentError, 'Node not reachable!' unless length.key?(target)
304
+ length[target]
305
+ end
306
+
307
+ # Shortest path from source to target using Bellman Ford algorithm
308
+ #
309
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
310
+ # @param source [Object] source
311
+ # @param target [Object] target
312
+ #
313
+ # @return [Array<Object>] path from source to target
314
+ def self.bellmanford_path(graph, source, target)
315
+ _, path = singlesource_bellmanford(graph, source, target)
316
+ path
317
+ end
318
+
319
+ # Shortest path from source to all nodes using Bellman Ford algorithm
320
+ #
321
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
322
+ # @param source [Object] source
323
+ # @param cutoff [Numeric, nil] cutoff for Bellman Ford algorithm
324
+ #
325
+ # @return [Hash{ Object => Array<Object> }] path from source to all nodes
326
+ def self.singlesource_bellmanford_path(graph, source, cutoff=nil)
327
+ _, path = singlesource_bellmanford(graph, source, cutoff)
328
+ path
329
+ end
330
+
331
+ # Shortest path length from source to all nodes using Bellman Ford algorithm
332
+ #
333
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
334
+ # @param source [Object] source
335
+ # @param cutoff [Numeric, nil] cutoff for Bellman Ford algorithm
336
+ #
337
+ # @return [Hash{ Object => Numeric }] path lengths from source to all nodes
338
+ def self.singlesource_bellmanford_path_length(graph, source, cutoff=nil)
339
+ weight = get_weight(graph)
340
+ help_bellman_ford(graph, [source], weight, nil, nil, nil, cutoff)
341
+ end
342
+
343
+ # Shortest path lengths between all nodes using Bellman Ford algorithm
344
+ #
345
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
346
+ # @param cutoff [Numeric, nil] cutoff for Bellman Ford algorithm
347
+ #
348
+ # @return [Array<Object, Hash{ Object => Numeric }>] path lengths from source to all nodes
349
+ def self.allpairs_bellmanford_path_length(graph, cutoff=nil)
350
+ path_lengths = []
351
+ graph.nodes.each_key { |n| path_lengths << [n, singlesource_bellmanford_path_length(graph, n, cutoff)] }
352
+ path_lengths
353
+ end
354
+
355
+ # Shortest paths between all nodes using Bellman Ford algorithm
356
+ #
357
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
358
+ # @param cutoff [Numeric, nil] cutoff for Bellman Ford algorithm
359
+ #
360
+ # @return [Array<Object, Hash{ Object => Array<Object> }>] path lengths from source to all nodes
361
+ def self.allpairs_bellmanford_path(graph, cutoff=nil)
362
+ paths = []
363
+ graph.nodes.each_key { |n| paths << [n, singlesource_bellmanford_path(graph, n, cutoff)] }
364
+ paths
365
+ end
366
+
367
+ # Helper function to get sources
368
+ def self.get_sources(graph)
369
+ graph.nodes.collect { |k, _v| k }
370
+ end
371
+
372
+ # Helper function to get distances
373
+ def self.dist_path_lambda(_graph, _new_weight)
374
+ lambda do |graph, v, new_weight|
375
+ paths = {v => [v]}
376
+ _ = help_dijkstra(graph, v, new_weight, nil, paths)
377
+ paths
378
+ end
379
+ end
380
+
381
+ # Helper function to set path lengths for Johnson algorithm
382
+ def self.set_path_lengths_johnson(graph, dist_path, new_weight)
383
+ path_lengths = []
384
+ graph.nodes.each_key { |n| path_lengths << [n, dist_path.call(graph, n, new_weight)] }
385
+ path_lengths
386
+ end
387
+
388
+ # TODO: Reduce method length and method complexity
389
+
390
+ # Returns shortest path between all pairs of nodes
391
+ #
392
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
393
+ #
394
+ # @return [Array<Object, Hash { Object => Array<Object> }] shortest paths between all pairs of nodes
395
+ def self.johnson(graph)
396
+ dist, pred = {}, {}
397
+ sources = get_sources(graph)
398
+ graph.nodes.each_key do |n|
399
+ dist[n], pred[n] = 0, []
400
+ end
401
+ weight = get_weight(graph)
402
+ dist_bellman = help_bellman_ford(graph, sources, weight, pred, nil, dist=dist)
403
+ new_weight = ->(u, v, d) { weight.call(u, v, d) + dist_bellman[u] - dist_bellman[v] }
404
+ dist_path = dist_path_lambda(graph, new_weight)
405
+ path_lengths = set_path_lengths_johnson(graph, dist_path, new_weight)
406
+ path_lengths
407
+ end
408
+ end