networkx 0.1.0 → 0.2.0

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 (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