networkx 0.1.0 → 0.1.1

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