networkx 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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,36 @@
1
+ module NetworkX
2
+ # Returns the eccentricity of a particular node or all nodes
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ # @param node [Object] node to find the eccentricity of
6
+ #
7
+ # @return [Array<Numeric>, Numeric] eccentricity/eccentricites of all nodes
8
+ def self.eccentricity(graph, node=nil)
9
+ e = {}
10
+ graph.nodes.each do |u, _|
11
+ length = single_source_shortest_path_length(graph, u)
12
+ l = length.length
13
+ raise ArgumentError, 'Found infinite path length!' unless l == graph.nodes.length
14
+ e[u] = length.max_by { |a| a[1] }[1]
15
+ end
16
+ node.nil? ? e : e[node]
17
+ end
18
+
19
+ # Returns the diameter of a graph
20
+ #
21
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
22
+ #
23
+ # @return [Numeric] diameter of the graph
24
+ def self.diameter(graph)
25
+ eccentricity(graph).values.max
26
+ end
27
+
28
+ # Returns the radius of a graph
29
+ #
30
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
31
+ #
32
+ # @return [Numeric] radius of the graph
33
+ def self.radius(graph)
34
+ eccentricity(graph).values.min
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ module NetworkX
2
+ # TODO: Reduce method complexity and method length
3
+
4
+ # Returns the maximal independent set of a graph
5
+ #
6
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
7
+ # @param nodes [Object] nodes to be considered in the MIS
8
+ #
9
+ # @return [Numeric] radius of the graph
10
+ def self.maximal_independent_set(graph, nodes)
11
+ raise 'The array containing the nodes should be a subset of the graph!' if (graph.nodes.keys - nodes).empty?
12
+ neighbours = []
13
+ nodes.each { |u| graph.adj[u].each { |v, _| neighbours |= [v] } }
14
+ raise 'Nodes is not an independent set of graph!' if (neighbours - nodes).empty?
15
+ available_nodes = graph.nodes.keys - (neighbours | nodes)
16
+ until available_nodes.empty?
17
+ node = available_nodes.sample
18
+ nodes << node
19
+ available_nodes -= (graph.adj[node].keys + [node])
20
+ end
21
+ nodes
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module NetworkX
2
+ # Helper function for the minimum spanning tree
3
+ #
4
+ def self.get_edges_weights(graph)
5
+ edges = []
6
+ graph.adj.each do |u, u_edges|
7
+ u_edges.each do |v, uv_attrs|
8
+ edges << [[u, v], uv_attrs[:weight] || Float::INFINITY]
9
+ end
10
+ end
11
+ edges
12
+ end
13
+
14
+ # TODO: Reduce method complexity and method length
15
+
16
+ # Returns the minimum spanning tree of a graph
17
+ #
18
+ # @param graph [Graph, DiGraph] a graph
19
+ #
20
+ # @return [DiGraph, Graph] a minimum spanning tree of the graph
21
+ def self.minimum_spanning_tree(graph)
22
+ mst = Marshal.load(Marshal.dump(graph))
23
+ mst.clear
24
+ edges = get_edges_weights(graph).sort_by { |a| a[1] }
25
+ union_find = UnionFind.new(graph.nodes.keys)
26
+ while edges.any? && mst.nodes.length <= graph.nodes.length
27
+ edge = edges.shift
28
+ unless union_find.connected?(edge[0][0], edge[0][1])
29
+ union_find.union(edge[0][0], edge[0][1])
30
+ mst.add_edge(edge[0][0], edge[0][1], graph.adj[edge[0][0]][edge[0][1]])
31
+ end
32
+ end
33
+ mst
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module NetworkX
2
+ class UnionFind
3
+ def initialize(nodes)
4
+ @unions = {}
5
+ nodes.each_with_index do |node, index|
6
+ @unions[node] = index
7
+ end
8
+ end
9
+
10
+ def connected?(node_1, node_2)
11
+ @unions[node_1] == @unions[node_2]
12
+ end
13
+
14
+ def union(node_1, node_2)
15
+ return if connected?(node_1, node_2)
16
+ node1_id = @unions[node_1]
17
+ node2_id = @unions[node_2]
18
+
19
+ @unions.each do |node, id|
20
+ @unions[node] = node1_id if id == node2_id
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module NetworkX
2
+ # Returns the closeness vitality of a node
3
+ #
4
+ # @param graph [Graph, DiGraph] a graph
5
+ # @param node [Object] node to compute closeness vitality of
6
+ #
7
+ # @return [Numeric] closeness vitality of the given node
8
+ def self.closeness_vitality(graph, node)
9
+ before = wiener_index(graph)
10
+ after = wiener_index(graph.subgraph(graph.nodes.keys - [node]))
11
+ before - after
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module NetworkX
2
+ # Returns the wiener index of the graph
3
+ #
4
+ # @param graph [Graph, DiGraph] a graph
5
+ #
6
+ # @return [Numeric] wiener index of the graph
7
+ def self.wiener_index(graph)
8
+ total = all_pairs_shortest_path_length(graph)
9
+ wiener_ind = 0
10
+ Hash[total].each { |_, distances| Hash[distances].each { |_, val| wiener_ind += val } }
11
+ graph.directed? ? wiener_ind : wiener_ind / 2
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module NetworkX
2
+ # TODO: Reduce method length and method complexity
3
+
4
+ # Saves the graph in a csv file
5
+ #
6
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
7
+ # @param filename [String] filename of the graph
8
+ def self.graph_to_csv(graph, filename='graph.csv')
9
+ CSV.open(filename, 'wb') do |csv|
10
+ csv << [graph.class.name]
11
+ csv << ['graph_values']
12
+ csv << graph.graph.keys
13
+ csv << graph.graph.values
14
+ csv << ['graph_nodes']
15
+ graph.nodes.each do |u, attrs|
16
+ node_attrs = [u]
17
+ attrs.each do |k, v|
18
+ node_attrs << k
19
+ node_attrs << v
20
+ end
21
+ csv << node_attrs
22
+ end
23
+ csv << ['graph_edges']
24
+ graph.adj.each do |u, u_edges|
25
+ u_edges.each do |v, uv_attrs|
26
+ if graph.multigraph?
27
+ uv_attrs.each do |key, attrs|
28
+ node_attrs = [u, v, key]
29
+ attrs.each do |k, k_attrs|
30
+ node_attrs << k
31
+ node_attrs << k_attrs
32
+ end
33
+ csv << node_attrs
34
+ end
35
+ else
36
+ node_attrs = [u, v]
37
+ uv_attrs.each do |k, vals|
38
+ node_attrs << k
39
+ node_attrs << vals
40
+ end
41
+ csv << node_attrs
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ module NetworkX
2
+ # Returns a JSON object of the given graph
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ #
6
+ # @return [JSON] json encoded graph
7
+ def self.graph_to_json(graph)
8
+ json_hash = {}
9
+ json_hash[:class] = graph.class.name
10
+ json_hash[:graph] = graph.graph
11
+ json_hash[:nodes] = graph.nodes
12
+ json_hash[:adj] = graph.adj
13
+ json_hash.to_json
14
+ end
15
+
16
+ # TODO: Reduce method length
17
+
18
+ # Returns a graph from the json encoded graph
19
+ #
20
+ # @param json_str [JSON] json encoded string
21
+ #
22
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] a decoded graph
23
+ def self.json_to_graph(json_str)
24
+ graph_hash = JSON.parse(json_str)
25
+ case json_str['class']
26
+ when 'NetworkX::Graph'
27
+ graph = NetworkX::Graph.new(graph_hash.graph)
28
+ when 'NetworkX::MultiGraph'
29
+ graph = NetworkX::MultiGraph.new(graph_hash.graph)
30
+ when 'NetworkX::DiGraph'
31
+ graph = NetworkX::DiGraph.new(graph_hash.graph)
32
+ when 'NetworkX::MultiDiGraph'
33
+ graph = NetworkX::MultiDiGraph.new(graph_hash.graph)
34
+ end
35
+ graph.adj = graph_hash['adj']
36
+ graph.nodes = graph_hash['nodes']
37
+ graph
38
+ end
39
+ end
@@ -0,0 +1,228 @@
1
+ module NetworkX
2
+ # Describes the class for making Directed Graphs
3
+ #
4
+ # @attr_reader adj [Hash{ Object => Hash{ Object => Hash{ Object => Object } } }]
5
+ # Stores the edges and their attributes in an adjencency list form
6
+ # @attr_reader pred [Hash{ Object => Hash{ Object => Hash{ Object => Object } } }]
7
+ # Stores the reverse edges and their attributes in an adjencency list form
8
+ # @attr_reader nodes [Hash{ Object => Hash{ Object => Object } }] Stores the nodes and their attributes
9
+ # @attr_reader graph [Hash{ Object => Object }] Stores the attributes of the graph
10
+ class DiGraph < Graph
11
+ attr_reader :adj, :nodes, :graph, :pred
12
+
13
+ # Constructor for initializing graph
14
+ #
15
+ # @example Initialize a graph with attributes 'type' and 'name'
16
+ # graph = NetworkX::Graph.new(name: "Social Network", type: "undirected")
17
+ #
18
+ # @param graph_attrs [Hash{ Object => Object }] the graph attributes in a hash format
19
+ def initialize(**graph_attrs)
20
+ super(graph_attrs)
21
+
22
+ @pred = {}
23
+ end
24
+
25
+ # Adds the respective edge
26
+ #
27
+ # @example Add an edge with attribute name
28
+ # graph.add_edge(node1, node2, name: "Edge1")
29
+ #
30
+ # @example Add an edge with no attribute
31
+ # graph.add_edge("Bangalore", "Chennai")
32
+ #
33
+ # @param node_1 [Object] the first node of the edge
34
+ # @param node_2 [Object] the second node of the edge
35
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
36
+ def add_edge(node_1, node_2, **edge_attrs)
37
+ add_node(node_1)
38
+ add_node(node_2)
39
+
40
+ edge_attrs = (@adj[node_1][node_2] || {}).merge(edge_attrs)
41
+ @adj[node_1][node_2] = edge_attrs
42
+ @pred[node_2][node_1] = edge_attrs
43
+ end
44
+
45
+ # Adds a node and its attributes to the graph
46
+ #
47
+ # @example Add a node with attribute 'type'
48
+ # graph.add_node("Noida", type: "city")
49
+ #
50
+ # @param node [Object] the node object
51
+ # @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
52
+ def add_node(node, **node_attrs)
53
+ super(node, node_attrs)
54
+
55
+ @pred[node] = {} unless @pred.key?(node)
56
+ end
57
+
58
+ # Removes node from the graph
59
+ #
60
+ # @example
61
+ # graph.remove_node("Noida")
62
+ #
63
+ # @param node [Object] the node to be removed
64
+ def remove_node(node)
65
+ raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.key?(node)
66
+
67
+ neighbours = @adj[node]
68
+ neighbours.each_key { |k| @pred[k].delete(node) }
69
+ @pred[node].each_key do |k|
70
+ @adj[k].delete(node)
71
+ end
72
+
73
+ @pred.delete(node)
74
+ @adj.delete(node)
75
+ @nodes.delete(node)
76
+ end
77
+
78
+ # Removes edge from the graph
79
+ #
80
+ # @example
81
+ # graph.remove_edge('Noida', 'Bangalore')
82
+ #
83
+ # @param node_1 [Object] the first node of the edge
84
+ # @param node_2 [Object] the second node of the edge
85
+ def remove_edge(node_1, node_2)
86
+ raise KeyError, "#{node_1} is not a valid node." unless @nodes.key?(node_1)
87
+ raise KeyError, "#{node_2} is not a valid node" unless @nodes.key?(node_2)
88
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1].key?(node_2)
89
+
90
+ @adj[node_1].delete(node_2)
91
+ @pred[node_2].delete(node_1)
92
+ end
93
+
94
+ # Clears the graph
95
+ #
96
+ # @example
97
+ # graph.clear
98
+ def clear
99
+ super
100
+
101
+ @pred.clear
102
+ end
103
+
104
+ # Returns number of edges
105
+ #
106
+ # @example
107
+ # graph.number_of_edges
108
+ def number_of_edges
109
+ @adj.values.map(&:length).inject(:+)
110
+ end
111
+
112
+ # Returns the size of graph
113
+ #
114
+ # @example
115
+ # graph.size(true)
116
+ #
117
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
118
+ # else returns number of edges
119
+ def size(is_weighted=false)
120
+ if is_weighted
121
+ graph_size = 0
122
+ @adj.each do |_, hash_val|
123
+ hash_val.each { |_, v| graph_size += v[:weight] if v.key?(:weight) }
124
+ end
125
+ return graph_size
126
+ end
127
+ number_of_edges
128
+ end
129
+
130
+ # Returns in-degree of a given node
131
+ #
132
+ # @example
133
+ # graph.in_degree(node)
134
+ #
135
+ # @param node [Object] the node whose in degree is to be calculated
136
+ def in_degree(node)
137
+ @pred[node].length
138
+ end
139
+
140
+ # Returns out-degree of a given node
141
+ #
142
+ # @example
143
+ # graph.out_degree(node)
144
+ #
145
+ # @param node [Object] the node whose out degree is to be calculated
146
+ def out_degree(node)
147
+ @adj[node].length
148
+ end
149
+
150
+ # Returns the reversed version of the graph
151
+ #
152
+ # @example
153
+ # graph.reverse
154
+ def reverse
155
+ new_graph = NetworkX::DiGraph.new(@graph)
156
+ @nodes.each { |u, attrs| new_graph.add_node(u, attrs) }
157
+ @adj.each do |u, edges|
158
+ edges.each { |v, attrs| new_graph.add_edge(v, u, attrs) }
159
+ end
160
+ new_graph
161
+ end
162
+
163
+ # Returns the undirected version of the graph
164
+ #
165
+ # @example
166
+ # graph.to_undirected
167
+ def to_undirected
168
+ new_graph = NetworkX::Graph.new(@graph)
169
+ @nodes.each { |u, attrs| new_graph.add_node(u, attrs) }
170
+ @adj.each do |u, edges|
171
+ edges.each { |v, attrs| new_graph.add_edge(u, v, attrs) }
172
+ end
173
+ new_graph
174
+ end
175
+
176
+ # TODO: Reduce method complexity and method length
177
+
178
+ # Returns subgraph consisting of given array of nodes
179
+ #
180
+ # @example
181
+ # graph.subgraph(%w[Mumbai Nagpur])
182
+ #
183
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
184
+ def subgraph(nodes)
185
+ case nodes
186
+ when Array, Set
187
+ sub_graph = NetworkX::DiGraph.new(@graph)
188
+ nodes.each do |u|
189
+ raise KeyError, "#{u} does not exist in the current graph!" unless node?(u)
190
+ sub_graph.add_node(u, @nodes[u])
191
+ @adj[u].each do |v, uv_attrs|
192
+ sub_graph.add_edge(u, v, uv_attrs) if @adj[u].key?(v) && nodes.include?(v)
193
+ end
194
+ return sub_graph
195
+ end
196
+ else
197
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, '\
198
+ "received #{nodes.class.name} instead."
199
+ end
200
+ end
201
+
202
+ # TODO: Reduce method complexity and method length
203
+
204
+ # Returns subgraph consisting of given edges
205
+ #
206
+ # @example
207
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
208
+ #
209
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
210
+ def edge_subgraph(edges)
211
+ case edges
212
+ when Array, Set
213
+ sub_graph = NetworkX::DiGraph.new(@graph)
214
+ edges.each do |u, v|
215
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.key?(u)\
216
+ && @adj[u].key?(v)
217
+ sub_graph.add_node(u, @nodes[u])
218
+ sub_graph.add_node(v, @nodes[v])
219
+ sub_graph.add_edge(u, v, @adj[u][v])
220
+ end
221
+ return sub_graph
222
+ else
223
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
224
+ "received #{edges.class.name} instead."
225
+ end
226
+ end
227
+ end
228
+ end