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