networkx 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +21 -11
- data/.github/ISSUE_TEMPLATE.md +15 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- data/.github/workflows/ci.yml +17 -0
- data/.github/workflows/doc.yml +23 -0
- data/.github/workflows/gem-push.yml +45 -0
- data/.rspec +0 -1
- data/.rubocop.yml +56 -63
- data/.yardopts +0 -1
- data/README.md +27 -27
- data/Rakefile +2 -3
- data/lib/networkx/auxillary_functions/cliques.rb +62 -0
- data/lib/networkx/auxillary_functions/cycles.rb +114 -0
- data/lib/networkx/auxillary_functions/dag.rb +59 -0
- data/lib/networkx/auxillary_functions/eccentricity.rb +37 -0
- data/lib/networkx/auxillary_functions/mis.rb +23 -0
- data/lib/networkx/auxillary_functions/mst.rb +33 -0
- data/lib/networkx/auxillary_functions/union_find.rb +104 -0
- data/lib/networkx/auxillary_functions/vitality.rb +13 -0
- data/lib/networkx/auxillary_functions/wiener.rb +13 -0
- data/lib/networkx/converters/to_csv.rb +45 -0
- data/lib/networkx/converters/to_json.rb +37 -0
- data/lib/networkx/digraph.rb +234 -0
- data/lib/networkx/flow/capacityscaling.rb +249 -0
- data/lib/networkx/flow/edmondskarp.rb +115 -0
- data/lib/networkx/flow/preflowpush.rb +249 -0
- data/lib/networkx/flow/shortestaugmentingpath.rb +154 -0
- data/lib/networkx/flow/utils.rb +139 -0
- data/lib/networkx/graph.rb +448 -0
- data/lib/networkx/link_analysis/hits.rb +59 -0
- data/lib/networkx/link_analysis/pagerank.rb +89 -0
- data/lib/networkx/multidigraph.rb +249 -0
- data/lib/networkx/multigraph.rb +199 -0
- data/lib/networkx/operators/all.rb +65 -0
- data/lib/networkx/operators/binary.rb +222 -0
- data/lib/networkx/operators/product.rb +201 -0
- data/lib/networkx/operators/unary.rb +17 -0
- data/lib/networkx/others/bridges.rb +30 -0
- data/lib/networkx/others/generators.rb +237 -0
- data/lib/networkx/others/grid_2d_graph.rb +38 -0
- data/lib/networkx/others/info.rb +11 -0
- data/lib/networkx/others/number_connected_components.rb +17 -0
- data/lib/networkx/others/reads.rb +52 -0
- data/lib/networkx/shortest_path/astar.rb +73 -0
- data/lib/networkx/shortest_path/dense.rb +29 -0
- data/lib/networkx/shortest_path/unweighted.rb +136 -0
- data/lib/networkx/shortest_path/weighted.rb +417 -0
- data/lib/networkx/to_matrix.rb +51 -0
- data/lib/networkx/traversals/bfs.rb +110 -0
- data/lib/networkx/traversals/dfs.rb +135 -0
- data/lib/networkx/traversals/edge_dfs.rb +114 -0
- data/lib/networkx/version.rb +1 -1
- data/lib/networkx.rb +43 -1
- data/networkx.gemspec +14 -12
- metadata +118 -62
- data/.rspec_formatter.rb +0 -24
- data/.travis.yml +0 -18
- 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,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
|