networkx 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +21 -11
  4. data/.github/ISSUE_TEMPLATE.md +15 -0
  5. data/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. data/.github/workflows/ci.yml +17 -0
  7. data/.github/workflows/doc.yml +23 -0
  8. data/.github/workflows/gem-push.yml +45 -0
  9. data/.rspec +0 -1
  10. data/.rubocop.yml +56 -63
  11. data/.yardopts +0 -1
  12. data/README.md +27 -27
  13. data/Rakefile +2 -3
  14. data/lib/networkx/auxillary_functions/cliques.rb +62 -0
  15. data/lib/networkx/auxillary_functions/cycles.rb +114 -0
  16. data/lib/networkx/auxillary_functions/dag.rb +59 -0
  17. data/lib/networkx/auxillary_functions/eccentricity.rb +37 -0
  18. data/lib/networkx/auxillary_functions/mis.rb +23 -0
  19. data/lib/networkx/auxillary_functions/mst.rb +33 -0
  20. data/lib/networkx/auxillary_functions/union_find.rb +104 -0
  21. data/lib/networkx/auxillary_functions/vitality.rb +13 -0
  22. data/lib/networkx/auxillary_functions/wiener.rb +13 -0
  23. data/lib/networkx/converters/to_csv.rb +45 -0
  24. data/lib/networkx/converters/to_json.rb +37 -0
  25. data/lib/networkx/digraph.rb +234 -0
  26. data/lib/networkx/flow/capacityscaling.rb +249 -0
  27. data/lib/networkx/flow/edmondskarp.rb +115 -0
  28. data/lib/networkx/flow/preflowpush.rb +249 -0
  29. data/lib/networkx/flow/shortestaugmentingpath.rb +154 -0
  30. data/lib/networkx/flow/utils.rb +139 -0
  31. data/lib/networkx/graph.rb +448 -0
  32. data/lib/networkx/link_analysis/hits.rb +59 -0
  33. data/lib/networkx/link_analysis/pagerank.rb +89 -0
  34. data/lib/networkx/multidigraph.rb +249 -0
  35. data/lib/networkx/multigraph.rb +199 -0
  36. data/lib/networkx/operators/all.rb +65 -0
  37. data/lib/networkx/operators/binary.rb +222 -0
  38. data/lib/networkx/operators/product.rb +201 -0
  39. data/lib/networkx/operators/unary.rb +17 -0
  40. data/lib/networkx/others/bridges.rb +30 -0
  41. data/lib/networkx/others/generators.rb +237 -0
  42. data/lib/networkx/others/grid_2d_graph.rb +38 -0
  43. data/lib/networkx/others/info.rb +11 -0
  44. data/lib/networkx/others/number_connected_components.rb +17 -0
  45. data/lib/networkx/others/reads.rb +52 -0
  46. data/lib/networkx/shortest_path/astar.rb +73 -0
  47. data/lib/networkx/shortest_path/dense.rb +29 -0
  48. data/lib/networkx/shortest_path/unweighted.rb +136 -0
  49. data/lib/networkx/shortest_path/weighted.rb +417 -0
  50. data/lib/networkx/to_matrix.rb +51 -0
  51. data/lib/networkx/traversals/bfs.rb +110 -0
  52. data/lib/networkx/traversals/dfs.rb +135 -0
  53. data/lib/networkx/traversals/edge_dfs.rb +114 -0
  54. data/lib/networkx/version.rb +1 -1
  55. data/lib/networkx.rb +43 -1
  56. data/networkx.gemspec +14 -12
  57. metadata +118 -62
  58. data/.rspec_formatter.rb +0 -24
  59. data/.travis.yml +0 -18
  60. data/Guardfile +0 -7
@@ -0,0 +1,38 @@
1
+ require_relative '../graph'
2
+
3
+ class Array
4
+ include Comparable
5
+ end
6
+
7
+ # Reference: https://networkx.org/documentation/stable/_modules/networkx/generators/lattice.html#grid_2d_graph
8
+ module NetworkX
9
+ # @param m [Integer] the number of rows
10
+ # @param n [Integer] the number of columns
11
+ # @param create_using [Class] graph class. this is optional. default is `NetworkX::Graph`.
12
+ def self.grid_2d_graph(m, n, periodic: false, create_using: NetworkX::Graph)
13
+ warn('sorry, periodic is not done yet') if periodic
14
+
15
+ m.is_a?(Integer) or raise(ArgumentError, "[NetworkX] argument m: Integer, not #{m.class}")
16
+ n.is_a?(Integer) or raise(ArgumentError, "[NetworkX] argument n: Integer, not #{n.class}")
17
+ create_using.is_a?(Class) \
18
+ or raise(ArgumentError, "[NetworkX] argument create_using: `Graph` class or children, not #{create_using.class}")
19
+
20
+ g = create_using.new
21
+
22
+ a = []
23
+ m.times { |i| n.times { |j| a << [i, j] } }
24
+ g.add_nodes_from(a)
25
+
26
+ e1 = []
27
+ (m - 1).times { |i| n.times { |j| e1 << [[i, j], [i + 1, j]] } }
28
+ g.add_edges_from(e1)
29
+
30
+ e2 = []
31
+ m.times { |i| (n - 1).times { |j| e2 << [[i, j], [i, j + 1]] } }
32
+ g.add_edges_from(e2)
33
+
34
+ g.add_edges_from(g.edges.map { |i, j| [j, i] }) if g.directed?
35
+
36
+ g
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ require_relative '../graph'
2
+
3
+ module NetworkX
4
+ def self.info(graph)
5
+ info = ''
6
+ info << "Type: #{graph.class}\n"
7
+ info << "Number of nodes: #{graph.number_of_nodes}\n"
8
+ info << "Number of edges: #{graph.number_of_edges}\n"
9
+ info
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../graph'
2
+ require_relative '../auxillary_functions/union_find'
3
+
4
+ module NetworkX
5
+ # @param [NetworkX::Graph] graph
6
+ #
7
+ # @return [Integer] the number of connected components on graph
8
+ def self.number_connected_components(graph)
9
+ uf = NetworkX::UnionFind.new(graph.nodes(data: false))
10
+ graph.each_edge { |x, y| uf.unite(x, y) }
11
+ uf.groups.size
12
+ end
13
+
14
+ class << self
15
+ alias number_of_connected_components number_connected_components
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ require_relative '../../networkx'
2
+
3
+ module NetworkX
4
+ class Graph
5
+ class << self
6
+ def read_edgelist(path, comment: '#', delimiter: nil)
7
+ edges = File.readlines(path).filter_map do |line|
8
+ line.sub!(/#{comment}.+/, '')
9
+ line.strip.split(delimiter) if line.strip.size > 0
10
+ end
11
+
12
+ edges.each{|edge| edge.map!{|node| NetworkX.to_number_if_possible(node) } }
13
+
14
+ graph = new
15
+ graph.add_edges(edges)
16
+ graph
17
+ end
18
+ alias read_edges read_edgelist
19
+
20
+ def read_weighted_edgelist(path, comment: '#', delimiter: nil)
21
+ edges = File.readlines(path).filter_map do |line|
22
+ line.sub!(/#{comment}.+/, '')
23
+ line.strip.split(delimiter) if line.strip.size > 0
24
+ end
25
+
26
+ edges.map! do |x, y, weight|
27
+ [
28
+ NetworkX.to_number_if_possible(x),
29
+ NetworkX.to_number_if_possible(y),
30
+ {weight: NetworkX.to_number_if_possible(weight)}
31
+ ]
32
+ end
33
+
34
+ graph = new
35
+ graph.add_edges(edges)
36
+ graph
37
+ end
38
+ alias read_weighted_edges read_weighted_edgelist
39
+ end
40
+ end
41
+
42
+ def self.to_number_if_possible(str)
43
+ case str
44
+ when /^[+-]?[0-9]+$/
45
+ str.to_i
46
+ when /^([+-]?\d*\.\d*)|(\d*[eE][+-]?\d+)$/
47
+ str.to_f
48
+ else
49
+ str
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,73 @@
1
+ module NetworkX
2
+ # Returns path using astar algorithm between source and target
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ # @param source [Object] a node to start astar from
6
+ # @param target [Object] a node to end astar
7
+ # @param heuristic [] a lambda to compute heuristic b/w two nodes
8
+ #
9
+ # @return [Array<Object>] an array of nodes forming a path between source
10
+ # and target
11
+ def self.astar_path(graph, source, target, heuristic = nil)
12
+ warn 'A* is not implemented for MultiGraph and MultiDiGraph' if graph.is_a?(MultiGraph) || graph.is_a?(MultiDiGraph)
13
+
14
+ raise ArgumentError, 'Either source or target is not in graph' unless graph.node?(source) && graph.node?(target)
15
+
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.has_key?(curnode)
37
+
38
+ explored[curnode] = parent
39
+
40
+ graph.adj[curnode].each do |u, attrs|
41
+ next if explored.has_key?(u)
42
+
43
+ ncost = dist + (attrs[:weight] || 1)
44
+ if enqueued.has_key?(u)
45
+ qcost, = enqueued[u]
46
+ next if qcost <= ncost
47
+ else
48
+ h = heuristic.call(u, target)
49
+ enqueued[u] = ncost, h
50
+ heap << [ncost + h, count.call(i), u, ncost, curnode]
51
+ end
52
+ end
53
+ end
54
+ raise ArgumentError, 'Target not reachable!'
55
+ end
56
+
57
+ # Returns astar path length b/w source and target
58
+ #
59
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
60
+ # @param source [Object] a node to start astar from
61
+ # @param target [Object] a node to end astar
62
+ # @param heuristic [] a lambda to compute heuristic b/w two nodes
63
+ #
64
+ # @return [Numeric] the length of the path
65
+ def self.astar_path_length(graph, source, target, heuristic = nil)
66
+ raise ArgumentError, 'Either source or target is not in graph' unless graph.node?(source) && graph.node?(target)
67
+
68
+ path = astar_path(graph, source, target, heuristic)
69
+ path_length = 0
70
+ (1..(path.length - 1)).each { |i| path_length += (graph.adj[path[i - 1]][path[i]][:weight] || 1) }
71
+ path_length
72
+ end
73
+ end
@@ -0,0 +1,29 @@
1
+ module NetworkX
2
+ # Returns the all pair distance between all the nodes
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ #
6
+ # @return [Hash{ Object => { Object => { Numeric }}}] a hash containing distances
7
+ # b/w all pairs of nodes
8
+ def self.floyd_warshall(graph)
9
+ a, index = to_matrix(graph, Float::INFINITY, 'min')
10
+ nodelen = graph.nodes.length
11
+ (0..(nodelen - 1)).each { |i| a[i, i] = 0 }
12
+ (0..(nodelen - 1)).each do |k|
13
+ (0..(nodelen - 1)).each do |i|
14
+ (0..(nodelen - 1)).each do |j|
15
+ a[i, j] = [a[i, j], a[i, k] + a[k, j]].min
16
+ end
17
+ end
18
+ end
19
+
20
+ as_hash = {}
21
+ (0..(nodelen - 1)).each do |i|
22
+ (0..(nodelen - 1)).each do |j|
23
+ as_hash[index[i]] = {} unless as_hash.has_key?(index[i])
24
+ as_hash[index[i]][index[j]] = a[i, j]
25
+ end
26
+ end
27
+ as_hash
28
+ end
29
+ end
@@ -0,0 +1,136 @@
1
+ module NetworkX
2
+ # Helper function for single source shortest path length
3
+ def self.help_single_shortest_path_length(adj, firstlevel, cutoff)
4
+ Enumerator.new do |e|
5
+ seen = {}
6
+ level = 0
7
+ nextlevel = firstlevel
8
+
9
+ while !nextlevel.empty? && cutoff >= level
10
+ thislevel = nextlevel
11
+ nextlevel = {}
12
+ thislevel.each do |u, _attrs|
13
+ next if seen.has_key?(u)
14
+
15
+ seen[u] = level
16
+ nextlevel.merge!(adj[u])
17
+ e.yield [u, level]
18
+ end
19
+ level += 1
20
+ end
21
+ seen.clear
22
+ end
23
+ end
24
+
25
+ # Computes shortest path values to all nodes from a given node
26
+ #
27
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
28
+ # @param source [Object] source to compute path length from
29
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
30
+ #
31
+ # @return [Array<Object, Numeric>] path lengths for all nodes
32
+ def self.single_source_shortest_path_length(graph, source, cutoff = nil)
33
+ raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)
34
+
35
+ cutoff = Float::INFINITY if cutoff.nil?
36
+ nextlevel = {source => 1}
37
+ help_single_shortest_path_length(graph.adj, nextlevel, cutoff).take(graph.nodes.length)
38
+ end
39
+
40
+ # Computes shortest path values to all nodes from all nodes
41
+ #
42
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
43
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
44
+ #
45
+ # @return [Array<Object, Array<Object, Numeric>>] path lengths for all nodes from all nodes
46
+ def self.all_pairs_shortest_path_length(graph, cutoff = nil)
47
+ shortest_paths = []
48
+ graph.nodes.each_key { |n| shortest_paths << [n, single_source_shortest_path_length(graph, n, cutoff)] }
49
+ shortest_paths
50
+ end
51
+
52
+ # Helper function for finding single source shortest path
53
+ def self.help_single_shortest_path(adj, firstlevel, paths, cutoff)
54
+ level = 0
55
+ nextlevel = firstlevel
56
+ while !nextlevel.empty? && cutoff > level
57
+ thislevel = nextlevel
58
+ nextlevel = {}
59
+ thislevel.each_key do |u|
60
+ adj[u].each_key do |v|
61
+ unless paths.has_key?(v)
62
+ paths[v] = paths[u] + [v]
63
+ nextlevel[v] = 1
64
+ end
65
+ end
66
+ end
67
+ level += 1
68
+ end
69
+ paths
70
+ end
71
+
72
+ # Computes single source shortest path from a node to every other node
73
+ #
74
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
75
+ # @param source [Object] source from which shortest paths are needed
76
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
77
+ #
78
+ # @return [Array<Object, Array<Object, Array<Object>>>] path lengths for all nodes from all nodes
79
+ def self.single_source_shortest_path(graph, source, cutoff = nil)
80
+ raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)
81
+
82
+ cutoff = Float::INFINITY if cutoff.nil?
83
+ nextlevel = {source => 1}
84
+ paths = {source => [source]}
85
+ help_single_shortest_path(graph.adj, nextlevel, paths, cutoff)
86
+ end
87
+
88
+ # Computes shortest paths to all nodes from all nodes
89
+ #
90
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
91
+ # @param cutoff [Numeric, nil] cutoff for the shortest path algorithm
92
+ #
93
+ # @return [Array<Object, Hash {Object => Array<Object> }>] paths for all nodes from all nodes
94
+ def self.all_pairs_shortest_path(graph, cutoff = nil)
95
+ shortest_paths = []
96
+ graph.nodes.each_key { |n| shortest_paths << [n, single_source_shortest_path(graph, n, cutoff)] }
97
+ shortest_paths
98
+ end
99
+
100
+ # Computes shortest paths to all nodes from all nodes
101
+ #
102
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
103
+ # @param source [Object] source for which predecessors are needed
104
+ # @param cutoff [Numeric, nil] cutoff for finding predecessor
105
+ # @param return_seen [Boolean] if true, returns seen dict
106
+ #
107
+ # @return [Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>,
108
+ # Hash{ Object => Array<Object> }]
109
+ # predecessors of a given node and/or seen dict
110
+ def self.predecessor(graph, source, cutoff = nil, return_seen = false)
111
+ raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)
112
+
113
+ level = 0
114
+ nextlevel = [source]
115
+ seen = {source => level}
116
+ pred = {source => []}
117
+ until nextlevel.empty?
118
+ level += 1
119
+ thislevel = nextlevel
120
+ nextlevel = []
121
+ thislevel.each do |u|
122
+ graph.adj[u].each_key do |v|
123
+ if !seen.has_key?(v)
124
+ pred[v] = [u]
125
+ seen[v] = level
126
+ nextlevel << v
127
+ elsif seen[v] == level
128
+ pred[v] << u
129
+ end
130
+ end
131
+ end
132
+ break if cutoff.nil? && cutoff <= level
133
+ end
134
+ return_seen ? [pred, seen] : pred
135
+ end
136
+ end