networkx 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e86cfdb083751f6aa9d45d894dd4a83ec75623acd237c6cdc8e7cd356306409
4
- data.tar.gz: 60ef78a9da3df8d7f61e297af48873795b21d9675138e953dd160597d9bfdc09
3
+ metadata.gz: 4bce7e05d9da7dd9774ad976cb82c8de0bd5847be9fb0dfc07af1042568446a9
4
+ data.tar.gz: '085683454ca374c50f594509e13da7c3114131e54936a11f6daf4dd3f4924b29'
5
5
  SHA512:
6
- metadata.gz: a7d64db11e72ecd05bba646d89116da859caa2e48b9063b158780e625c7fdf802cd840c3aace32a00314762805a27fa1373b36795462d7640f4ed7385466d403
7
- data.tar.gz: 46886910fc5f3bb550fb089af740bb5c0e32cd5a1ff3dd2394a13256ddc3f6b4e9f32c48c37d2d827b2bc8edba36cd0894201aec833a2fb4d59318f7fb85b7ae
6
+ metadata.gz: 91cdd246c4692b87a90405676f58a330257ecc02d6cdc9e55c79428afc4d22a9d621fd175d6408e0a7f6aafec50deaa8cd2eb9a0d5ef49360befd5ae8e88dd30
7
+ data.tar.gz: 79bade11b9709a78102bb83d13d77a3abe661fbaf75bdc18dbc5f822e71d6c5ecd53c8b7c46324cf9d6eda1df570f299c77191e8079426c50e4fd5b3f87e7ffd
data/.rubocop.yml CHANGED
@@ -1,12 +1,12 @@
1
1
  AllCops:
2
2
  NewCops: enable
3
- TargetRubyVersion: 3.0
3
+ TargetRubyVersion: 2.7
4
4
  Include:
5
5
  - 'lib/**/*'
6
6
  - 'spec/**/*_spec.rb'
7
7
  - 'Gemfile'
8
8
  - 'Rakefile'
9
- - '*.gemspace'
9
+ - '*.gemspec'
10
10
  DisplayCopNames: true
11
11
 
12
12
  require:
data/README.md CHANGED
@@ -1,23 +1,37 @@
1
1
  # NetworkX.rb
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/networkx.svg)](https://badge.fury.io/rb/networkx)
4
+
3
5
  [NetworkX](https://networkx.github.io/) is a very popular Python library, that handles various use-cases of the Graph Data Structure.
4
6
  This project intends to provide a working alternative to the Ruby community, by closely mimicing as many features as possible.
5
7
 
6
8
  ## List of contents
7
9
 
8
10
  - [Installing](#installing)
11
+ - [Usage](#Usage)
9
12
  - [Document](#document)
10
- - [Roadmap](#roadmap)
11
13
  - [Contributing](#contributing)
12
14
  - [License](#license)
13
15
 
14
16
  ## Installing
15
17
 
16
- - Clone the repository or fork
17
- - Clone this repository with `git clone https://github.com/SciRuby/networkx.rb.git`
18
- - or You can fork and do clone it.
19
- - Navigate to networkx with `cd networkx.rb`
20
- - Install dependencies with `gem install bundler && bundle install`
18
+ - install it yourself as:
19
+ ```console
20
+ $ gem install networkx
21
+ ```
22
+
23
+
24
+ - Or use Bundler & Gemfile
25
+ 1. add this line to your application's Gemfile:
26
+ ```ruby
27
+ gem 'networkx'
28
+ ```
29
+ 2. And then execute:
30
+ ```console
31
+ $ bundle install
32
+ ```
33
+
34
+ ## Usage
21
35
 
22
36
  ```ruby
23
37
  require 'networkx'
@@ -30,22 +44,6 @@ g.add_edge('start', 'stop')
30
44
 
31
45
  You can read [Document](https://SciRuby.github.io/networkx.rb/) for this library.
32
46
 
33
- ## Roadmap
34
-
35
- Quite easily, any networkx user would be able to understand the number of details that have been implemented in the Python library. As a humble start towards the release of v0.1.0, the following could be the goals to achieve :
36
-
37
- - `Node` : This class should be capable of handling different types of nodes (not just `String` / `Integer`).
38
- A possible complex use-case could be XML nodes.
39
-
40
- - `Edge` : This class should be capable of handling different types of edges.
41
- Though a basic undirected Graph doesn't store any metadata in the edges, weighted edges and parametric edges are something that need to be handled.
42
-
43
- - `Graph` : The simplest of graphs.
44
- This class handles just connections between different `Node`s via `Edge`s.
45
-
46
- - `DirectedGraph` : Inherits from `Graph` class.
47
- Uses directions between `Edge`s.
48
-
49
47
  ## Contributing
50
48
 
51
49
  Your contributions are always welcome!
@@ -5,15 +5,15 @@ module NetworkX
5
5
  #
6
6
  # @return [Array<Array<Object>>] Arrays of nodes in the cliques
7
7
  def self.find_cliques(graph)
8
- return nil if graph.nodes.empty?
8
+ return nil if graph.nodes(data: true).empty?
9
9
 
10
10
  q = [nil]
11
11
  adj = {}
12
- graph.nodes.each_key { |u| adj[u] = [] }
12
+ graph.nodes(data: true).each_key { |u| adj[u] = [] }
13
13
  graph.adj.each { |u, u_edges| u_edges.each_key { |v| adj[u] << v if u != v } }
14
14
 
15
- subg = graph.nodes.keys
16
- cand = graph.nodes.keys
15
+ subg = graph.nodes(data: true).keys
16
+ cand = graph.nodes(data: true).keys
17
17
  u = subg.max { |n1, n2| (cand & adj[n1]).length <=> (cand & adj[n2]).length }
18
18
  ext_u = cand - adj[u]
19
19
  stack = []
@@ -6,7 +6,7 @@ module NetworkX
6
6
  #
7
7
  # @return [Array<Array<Object>>] Arrays of nodes in the cycles
8
8
  def self.cycle_basis(graph, root = nil)
9
- gnodes = graph.nodes.keys
9
+ gnodes = graph.nodes(data: true).keys
10
10
  cycles = []
11
11
  until gnodes.empty?
12
12
  root = gnodes.shift if root.nil?
@@ -34,14 +34,14 @@ module NetworkX
34
34
  raise ArgumentError, 'Topological Sort not defined on undirected graphs!' unless graph.directed?
35
35
 
36
36
  nodes = []
37
- indegree_map = graph.nodes.each_key.map do |u|
37
+ indegree_map = graph.nodes(data: true).each_key.map do |u|
38
38
  [u, graph.in_degree(u)] if graph.in_degree(u).positive?
39
39
  end.compact.to_h
40
- zero_indegree = graph.nodes.each_key.select { |u| graph.in_degree(u).zero? }
40
+ zero_indegree = graph.nodes(data: true).each_key.select { |u| graph.in_degree(u).zero? }
41
41
 
42
42
  until zero_indegree.empty?
43
43
  node = zero_indegree.shift
44
- raise ArgumentError, 'Graph changed during iteration!' unless graph.nodes.has_key?(node)
44
+ raise ArgumentError, 'Graph changed during iteration!' unless graph.nodes(data: true).has_key?(node)
45
45
 
46
46
  graph.adj[node].each_key do |child|
47
47
  indegree_map[child] -= 1
@@ -7,10 +7,10 @@ module NetworkX
7
7
  # @return [Array<Numeric>, Numeric] eccentricity/eccentricites of all nodes
8
8
  def self.eccentricity(graph, node = nil)
9
9
  e = {}
10
- graph.nodes.each do |u, _|
10
+ graph.nodes(data: true).each do |u, _|
11
11
  length = single_source_shortest_path_length(graph, u)
12
12
  l = length.length
13
- raise ArgumentError, 'Found infinite path length!' unless l == graph.nodes.length
13
+ raise ArgumentError, 'Found infinite path length!' unless l == graph.nodes(data: true).length
14
14
 
15
15
  e[u] = length.max_by { |a| a[1] }[1]
16
16
  end
@@ -6,13 +6,15 @@ module NetworkX
6
6
  #
7
7
  # @return [Numeric] radius of the graph
8
8
  def self.maximal_independent_set(graph, nodes)
9
- raise 'The array containing the nodes should be a subset of the graph!' if (graph.nodes.keys - nodes).empty?
9
+ if (graph.nodes(data: true).keys - nodes).empty?
10
+ raise 'The array containing the nodes should be a subset of the graph!'
11
+ end
10
12
 
11
13
  neighbours = []
12
14
  nodes.each { |u| graph.adj[u].each { |v, _| neighbours |= [v] } }
13
15
  raise 'Nodes is not an independent set of graph!' if (neighbours - nodes).empty?
14
16
 
15
- available_nodes = graph.nodes.keys - (neighbours | nodes)
17
+ available_nodes = graph.nodes(data: true).keys - (neighbours | nodes)
16
18
  until available_nodes.empty?
17
19
  node = available_nodes.sample
18
20
  nodes << node
@@ -20,8 +20,8 @@ module NetworkX
20
20
  mst = Marshal.load(Marshal.dump(graph))
21
21
  mst.clear
22
22
  edges = get_edges_weights(graph).sort_by { |a| a[1] }
23
- union_find = UnionFind.new(graph.nodes.keys)
24
- while edges.any? && mst.nodes.length <= graph.nodes.length
23
+ union_find = UnionFind.new(graph.nodes(data: true).keys)
24
+ while edges.any? && mst.nodes(data: true).length <= graph.nodes(data: true).length
25
25
  edge = edges.shift
26
26
  unless union_find.connected?(edge[0][0], edge[0][1])
27
27
  union_find.union(edge[0][0], edge[0][1])
@@ -7,7 +7,7 @@ module NetworkX
7
7
  # @return [Numeric] closeness vitality of the given node
8
8
  def self.closeness_vitality(graph, node)
9
9
  before = wiener_index(graph)
10
- after = wiener_index(graph.subgraph(graph.nodes.keys - [node]))
10
+ after = wiener_index(graph.subgraph(graph.nodes(data: true).keys - [node]))
11
11
  before - after
12
12
  end
13
13
  end
@@ -10,7 +10,7 @@ module NetworkX
10
10
  csv << graph.graph.keys
11
11
  csv << graph.graph.values
12
12
  csv << ['graph_nodes']
13
- graph.nodes.each do |u, attrs|
13
+ graph.nodes(data: true).each do |u, attrs|
14
14
  node_attrs = [u]
15
15
  attrs.each do |k, v|
16
16
  node_attrs << k
@@ -11,7 +11,7 @@ module NetworkX
11
11
  # @return [Boolean] whether there exists a negative cycle in graph
12
12
  def self.negative_edge_cycle(graph)
13
13
  newnode = generate_unique_node
14
- graph.add_edges(graph.nodes.keys.map { |n| [newnode, n] })
14
+ graph.add_edges(graph.nodes(data: true).keys.map { |n| [newnode, n] })
15
15
  begin
16
16
  bellmanford_predecesor_distance(graph, newnode)
17
17
  rescue ArgumentError
@@ -25,10 +25,10 @@ module NetworkX
25
25
  # Detects the unboundedness in the residual graph
26
26
  def self._detect_unboundedness(residual)
27
27
  g = NetworkX::DiGraph.new
28
- g.add_nodes(residual.nodes.keys.zip(residual.nodes.values))
28
+ g.add_nodes(residual.nodes(data: true).keys.zip(residual.nodes(data: true).values))
29
29
  inf = residual.graph[:inf]
30
30
 
31
- residual.nodes.each do |u, _attr|
31
+ residual.nodes(data: true).each do |u, _attr|
32
32
  residual.adj[u].each do |v, uv_attrs|
33
33
  w = inf
34
34
  uv_attrs.each { |_key, edge_attrs| w = [w, edge_attrs[:weight]].min if edge_attrs[:capacity] == inf }
@@ -41,10 +41,10 @@ module NetworkX
41
41
  # Returns the residual graph of the given graph
42
42
  def self._build_residual_network(graph)
43
43
  raise ArgumentError, 'Sum of demands should be 0!' unless \
44
- graph.nodes.values.map { |attr| attr[:demand] || 0 }.inject(0, :+).zero?
44
+ graph.nodes(data: true).values.map { |attr| attr[:demand] || 0 }.inject(0, :+).zero?
45
45
 
46
46
  residual = NetworkX::MultiDiGraph.new(inf: 0)
47
- residual.add_nodes(graph.nodes.map { |u, attr| [u, {excess: (attr[:demand] || 0) * -1, potential: 0}] })
47
+ residual.add_nodes(graph.nodes(data: true).map { |u, attr| [u, {excess: (attr[:demand] || 0) * -1, potential: 0}] })
48
48
  inf = Float::INFINITY
49
49
  edge_list = []
50
50
 
@@ -66,9 +66,11 @@ module NetworkX
66
66
  end
67
67
  end
68
68
 
69
- temp_inf = [residual.nodes.map { |_u, attrs| attrs[:excess].abs }.inject(0, :+), edge_list.map do |_, _, _, e|
70
- (e.has_key?(:capacity) && e[:capacity] != inf ? e[:capacity] : 0)
71
- end.inject(0, :+) * 2].max
69
+ temp_inf = [residual.nodes(data: true).map do |_u, attrs|
70
+ attrs[:excess].abs
71
+ end.inject(0, :+), edge_list.map do |_, _, _, e|
72
+ (e.has_key?(:capacity) && e[:capacity] != inf ? e[:capacity] : 0)
73
+ end.inject(0, :+) * 2].max
72
74
  inf = temp_inf.zero? ? 1 : temp_inf
73
75
 
74
76
  edge_list.each do |u, v, k, e|
@@ -88,7 +90,7 @@ module NetworkX
88
90
  inf = Float::INFINITY
89
91
 
90
92
  if graph.multigraph?
91
- graph.nodes.each_key do |u|
93
+ graph.nodes(data: true).each_key do |u|
92
94
  flow_dict[u] = {}
93
95
  graph.adj[u].each do |v, uv_edges|
94
96
  flow_dict[u][v] = uv_edges.transform_values do |e|
@@ -102,7 +104,7 @@ module NetworkX
102
104
  end
103
105
  end
104
106
  else
105
- graph.nodes.each_key do |u|
107
+ graph.nodes(data: true).each_key do |u|
106
108
  flow_dict[u] = graph.adj[u].to_h do |v, e|
107
109
  [v, u != v || (e[:capacity] || inf) <= 0 || (e[:weight] || 0) >= 0 ? 0 : e[:capacity]]
108
110
  end
@@ -163,7 +165,7 @@ module NetworkX
163
165
  s_set = Set.new
164
166
  t_set = Set.new
165
167
 
166
- residual.nodes.each do |u, _attrs|
168
+ residual.nodes(data: true).each do |u, _attrs|
167
169
  excess = r_nodes[u][:excess]
168
170
  if excess >= delta
169
171
  s_set.add(u)
@@ -236,7 +238,7 @@ module NetworkX
236
238
 
237
239
  r_nodes.each_value { |attrs| raise ArgumentError, 'No flow satisfying all demands!' if attrs[:excess] != 0 }
238
240
 
239
- residual.nodes.each_key do |node|
241
+ residual.nodes(data: true).each_key do |node|
240
242
  residual.adj[node].each_value do |uv_edges|
241
243
  uv_edges.each_value do |k_attrs|
242
244
  flow = k_attrs[:flow]
@@ -84,8 +84,8 @@ module NetworkX
84
84
 
85
85
  # Helper function for the edmondskarp function
86
86
  def self.edmondskarp_impl(graph, source, target, residual, cutoff)
87
- raise ArgumentError, 'Source not in graph!' unless graph.nodes.has_key?(source)
88
- raise ArgumentError, 'Target not in graph!' unless graph.nodes.has_key?(target)
87
+ raise ArgumentError, 'Source not in graph!' unless graph.nodes(data: true).has_key?(source)
88
+ raise ArgumentError, 'Target not in graph!' unless graph.nodes(data: true).has_key?(target)
89
89
  raise ArgumentError, 'Source and target are same node!' if source == target
90
90
 
91
91
  res_graph = residual.nil? ? build_residual_network(graph) : residual.clone
@@ -12,8 +12,8 @@ module NetworkX
12
12
 
13
13
  # Helper function to apply the preflow push algorithm
14
14
  def self.preflowpush_impl(graph, source, target, residual, globalrelabel_freq, value_only)
15
- raise ArgumentError, 'Source not in graph!' unless graph.nodes.has_key?(source)
16
- raise ArgumentError, 'Target not in graph!' unless graph.nodes.has_key?(target)
15
+ raise ArgumentError, 'Source not in graph!' unless graph.nodes(data: true).has_key?(source)
16
+ raise ArgumentError, 'Target not in graph!' unless graph.nodes(data: true).has_key?(target)
17
17
  raise ArgumentError, 'Source and Target are same!' if source == target
18
18
 
19
19
  globalrelabel_freq = 0 if globalrelabel_freq.nil?
@@ -38,7 +38,7 @@ module NetworkX
38
38
  return r_network
39
39
  end
40
40
 
41
- n = r_network.nodes.length
41
+ n = r_network.nodes(data: true).length
42
42
  max_height = heights.map { |u, h| u == source ? -1 : h }.max
43
43
  heights[source] = n
44
44
 
@@ -1,8 +1,8 @@
1
1
  module NetworkX
2
2
  # Helper function for running the shortest augmenting path algorithm
3
3
  def self.shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
4
- raise ArgumentError, 'Source is not in the graph!' unless graph.nodes.has_key?(source)
5
- raise ArgumentError, 'Target is not in the graph!' unless graph.nodes.has_key?(target)
4
+ raise ArgumentError, 'Source is not in the graph!' unless graph.nodes(data: true).has_key?(source)
5
+ raise ArgumentError, 'Target is not in the graph!' unless graph.nodes(data: true).has_key?(target)
6
6
  raise ArgumentError, 'Source and Target are the same!' if source == target
7
7
 
8
8
  residual = residual.nil? ? build_residual_network(graph) : residual
@@ -35,7 +35,7 @@ module NetworkX
35
35
  return residual
36
36
  end
37
37
 
38
- n = graph.nodes.length
38
+ n = graph.nodes(data: true).length
39
39
  m = residual.size / 2
40
40
 
41
41
  r_nodes.each do |node, attrs|
@@ -62,7 +62,7 @@ module NetworkX
62
62
  raise NotImplementedError, 'MultiGraph and MultiDiGraph not supported!' if graph.multigraph?
63
63
 
64
64
  r_network = NetworkX::DiGraph.new(inf: 0, flow_value: 0)
65
- r_network.add_nodes(graph.nodes.keys)
65
+ r_network.add_nodes(graph.nodes(data: true).keys)
66
66
  inf = Float::INFINITY
67
67
  edge_list = []
68
68
 
@@ -213,14 +213,13 @@ module NetworkX
213
213
  end
214
214
  end
215
215
 
216
- # [TODO] Current default of `data` is true.
217
- # [Alert] Change the default in the futher. Please specify the `data`.
216
+ # Return nodes of graph
218
217
  #
219
218
  # @param data [bool] true if you want data of each edge
220
219
  #
221
220
  # @return [Hash | Array] if data is true, it returns hash including data.
222
221
  # otherwise, simple nodes array.
223
- def nodes(data: true)
222
+ def nodes(data: false)
224
223
  if data
225
224
  @nodes
226
225
  else
@@ -8,7 +8,7 @@ module NetworkX
8
8
  #
9
9
  # @return [Array<Numeric, Numeric>] hits and authority scores
10
10
  def self.hits(graph, max_iter = 100, tol = 1e-8, nstart)
11
- return [{}, {}] if graph.nodes.empty?
11
+ return [{}, {}] if graph.nodes(data: true).empty?
12
12
 
13
13
  h = nstart
14
14
  sum = h.values.sum
@@ -2,61 +2,16 @@ module NetworkX
2
2
  # Computes pagerank values for the graph
3
3
  #
4
4
  # @param graph [Graph] a graph
5
- # @param init [Array<Numeric>] initial pagerank values for the nodes
6
5
  # @param alpha [Numeric] the alpha value to compute the pagerank
7
6
  # @param eps [Numeric] tolerence to check for convergence
8
7
  # @param max_iter [Integer] max iterations for the pagerank algorithm to run
9
8
  #
10
- # @return [Array<Numeric>] pagerank values of the graph
11
- def self.pagerank(graph, init = nil, alpha = 0.85, eps = 1e-4, max_iter = 100)
12
- dim = graph.nodes.length
13
- if init.nil?
14
- init = graph.nodes(data: false).to_h{ |i| [i, 1.0 / dim] }
15
- else
16
- s = init.values.sum.to_f
17
- init = init.transform_values { |v| v / s }
18
- end
19
- raise ArgumentError, 'Init array needs to have same length as number of graph nodes!' \
20
- unless dim == init.length
21
-
22
- matrix = []
23
- elem_ind = {}
24
- p = []
25
- curr = init.values
26
- init.keys.each_with_index { |n, i| elem_ind[n] = i }
27
- graph.adj.each do |_u, u_edges|
28
- adj_arr = Array.new(dim, 0)
29
- u_edges.each do |v, _|
30
- adj_arr[elem_ind[v]] = 1
31
- end
32
- matrix << adj_arr
33
- end
34
- (0..(dim - 1)).each do |i|
35
- p[i] = []
36
- (0..(dim - 1)).each { |j| p[i][j] = matrix[i][j] / (matrix[i].sum * 1.0) }
37
- end
38
-
39
- max_iter.times do |_|
40
- prev = curr.clone
41
- dim.times do |i|
42
- ip = 0
43
- dim.times { |j| ip += p.transpose[i][j] * prev[j] }
44
- curr[i] = (alpha * ip) + ((1 - alpha) / (dim * 1.0))
45
- end
46
- err = 0
47
- dim.times { |i| err += (prev[i] - curr[i]).abs }
48
- return curr if err < eps
49
- end
50
- raise ArgumentError, 'PageRank failed to converge!'
51
- end
52
-
53
- def self.pagerank2(graph, alpha: 0.85, personalization: nil, eps: 1e-6, max_iter: 100)
9
+ # @return [Hash of Object => Float] pagerank values of the graph
10
+ def self.pagerank(graph, alpha: 0.85, personalization: nil, eps: 1e-6, max_iter: 100)
54
11
  n = graph.number_of_nodes
55
12
 
56
13
  matrix, index_to_node = NetworkX.to_matrix(graph, 0)
57
14
 
58
- # index_to_node = {0=>0, 1=>1, 2=>2, 3=>3}
59
-
60
15
  index_from_node = index_to_node.invert
61
16
 
62
17
  probabilities = Array.new(n) do |i|
@@ -84,6 +39,7 @@ module NetworkX
84
39
  err = (0...n).map{|i| (prev[i] - curr[i]).abs }.sum
85
40
  return (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h if err < eps
86
41
  end
42
+ warn "pagerank() failed within #{max_iter} iterations. Please inclease max_iter: or loosen eps:"
87
43
  (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h
88
44
  end
89
45
  end
@@ -27,12 +27,12 @@ module NetworkX
27
27
  def self.convert_to_distinct_labels(graph, starting_int = -1)
28
28
  new_graph = graph.class.new
29
29
 
30
- idx_dict = graph.nodes.keys.to_h do |v|
30
+ idx_dict = graph.nodes(data: true).keys.to_h do |v|
31
31
  starting_int += 1
32
32
  [v, starting_int]
33
33
  end
34
34
 
35
- graph.nodes.each do |u, attrs|
35
+ graph.nodes(data: true).each do |u, attrs|
36
36
  new_graph.add_node(u.to_s + idx_dict[u].to_s, **attrs)
37
37
  end
38
38
 
@@ -60,9 +60,12 @@ module NetworkX
60
60
  result = g1.class.new
61
61
 
62
62
  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?
63
- raise ArgumentError, 'Node sets must be equal!' unless (g1.nodes.keys - g2.nodes.keys).empty?
64
63
 
65
- g1.nodes.each { |u, attrs| result.add_node(u, **attrs) }
64
+ unless (g1.nodes(data: true).keys - g2.nodes(data: true).keys).empty?
65
+ raise ArgumentError, 'Node sets must be equal!'
66
+ end
67
+
68
+ g1.nodes(data: true).each { |u, attrs| result.add_node(u, **attrs) }
66
69
 
67
70
  g1, g2 = g2, g1 if g1.number_of_edges > g2.number_of_edges
68
71
  g1.adj.each do |u, u_edges|
@@ -91,9 +94,12 @@ module NetworkX
91
94
  result = g1.class.new
92
95
 
93
96
  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?
94
- raise ArgumentError, 'Node sets must be equal!' unless (g1.nodes.keys - g2.nodes.keys).empty?
95
97
 
96
- g1.nodes.each { |u, attrs| result.add_node(u, **attrs) }
98
+ unless (g1.nodes(data: true).keys - g2.nodes(data: true).keys).empty?
99
+ raise ArgumentError, 'Node sets must be equal!'
100
+ end
101
+
102
+ g1.nodes(data: true).each { |u, attrs| result.add_node(u, **attrs) }
97
103
 
98
104
  g1.adj.each do |u, u_edges|
99
105
  u_edges.each do |v, uv_attrs|
@@ -121,9 +127,12 @@ module NetworkX
121
127
  result = g1.class.new
122
128
 
123
129
  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?
124
- raise ArgumentError, 'Node sets must be equal!' unless (g1.nodes.keys - g2.nodes.keys).empty?
125
130
 
126
- g1.nodes.each { |u, attrs| result.add_node(u, **attrs) }
131
+ unless (g1.nodes(data: true).keys - g2.nodes(data: true).keys).empty?
132
+ raise ArgumentError, 'Node sets must be equal!'
133
+ end
134
+
135
+ g1.nodes(data: true).each { |u, attrs| result.add_node(u, **attrs) }
127
136
 
128
137
  g1.adj.each do |u, u_edges|
129
138
  u_edges.each do |v, uv_attrs|
@@ -166,8 +175,8 @@ module NetworkX
166
175
 
167
176
  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?
168
177
 
169
- result.add_nodes(g1.nodes.map { |u, attrs| [u, attrs] })
170
- result.add_nodes(g2.nodes.map { |u, attrs| [u, attrs] })
178
+ result.add_nodes(g1.nodes(data: true).map { |u, attrs| [u, attrs] })
179
+ result.add_nodes(g2.nodes(data: true).map { |u, attrs| [u, attrs] })
171
180
 
172
181
  if g1.multigraph?
173
182
  g1.adj.each { |u, e| e.each { |v, uv_edges| uv_edges.each_value { |attrs| result.add_edge(u, v, **attrs) } } }
@@ -192,14 +201,16 @@ module NetworkX
192
201
  new_graph.graph.merge!(g1.graph)
193
202
  new_graph.graph.merge!(g2.graph)
194
203
 
195
- raise ArgumentError, 'Graphs must be disjoint!' unless (g1.nodes.keys & g2.nodes.keys).empty?
204
+ unless (g1.nodes(data: true).keys & g2.nodes(data: true).keys).empty?
205
+ raise ArgumentError, 'Graphs must be disjoint!'
206
+ end
196
207
 
197
208
  g1_edges = get_edges(g1)
198
209
  g2_edges = get_edges(g2)
199
210
 
200
- new_graph.add_nodes(g1.nodes.keys)
211
+ new_graph.add_nodes(g1.nodes(data: true).keys)
201
212
  new_graph.add_edges(g1_edges)
202
- new_graph.add_nodes(g2.nodes.keys)
213
+ new_graph.add_nodes(g2.nodes(data: true).keys)
203
214
  new_graph.add_edges(g2_edges)
204
215
 
205
216
  new_graph
@@ -28,8 +28,8 @@ module NetworkX
28
28
  # Returns the node product of nodes of two graphs
29
29
  def self.node_product(g1, g2)
30
30
  n_product = []
31
- g1.nodes.each do |k1, attrs1|
32
- g2.nodes.each do |k2, attrs2|
31
+ g1.nodes(data: true).each do |k1, attrs1|
32
+ g2.nodes(data: true).each do |k2, attrs2|
33
33
  n_product << [[k1, k2], hash_product(attrs1, attrs2)]
34
34
  end
35
35
  end
@@ -62,7 +62,7 @@ module NetworkX
62
62
  def self.edges_cross_nodes(g1, g2)
63
63
  result = []
64
64
  edges_in_array(g1).each do |u, v, d|
65
- g2.nodes.each_key do |x|
65
+ g2.nodes(data: true).each_key do |x|
66
66
  result << [[u, x], [v, x], d]
67
67
  end
68
68
  end
@@ -72,7 +72,7 @@ module NetworkX
72
72
  # Returns the product of directed nodes with edges
73
73
  def self.nodes_cross_edges(g1, g2)
74
74
  result = []
75
- g1.nodes.each_key do |x|
75
+ g1.nodes(data: true).each_key do |x|
76
76
  edges_in_array(g2).each do |u, v, d|
77
77
  result << [[x, u], [x, v], d]
78
78
  end
@@ -84,8 +84,8 @@ module NetworkX
84
84
  def self.edges_cross_nodes_and_nodes(g1, g2)
85
85
  result = []
86
86
  edges_in_array(g1).each do |u, v, d|
87
- g2.nodes.each_key do |x|
88
- g2.nodes.each_key do |y|
87
+ g2.nodes(data: true).each_key do |x|
88
+ g2.nodes(data: true).each_key do |y|
89
89
  result << [[u, x], [v, y], d]
90
90
  end
91
91
  end
@@ -174,8 +174,8 @@ module NetworkX
174
174
  raise ArgumentError, 'Power must be a positive quantity!' if pow <= 0
175
175
 
176
176
  result = NetworkX::Graph.new
177
- result.add_nodes(graph.nodes.map { |n, attrs| [n, attrs] })
178
- graph.nodes.each do |n, _attrs|
177
+ result.add_nodes(graph.nodes(data: true).map { |n, attrs| [n, attrs] })
178
+ graph.nodes(data: true).each do |n, _attrs|
179
179
  seen = {}
180
180
  level = 1
181
181
  next_level = graph.adj[n]
@@ -8,9 +8,9 @@ module NetworkX
8
8
  result = Marshal.load(Marshal.dump(graph))
9
9
  result.clear
10
10
 
11
- result.add_nodes(graph.nodes.map { |u, attrs| [u, attrs] })
11
+ result.add_nodes(graph.nodes(data: true).map { |u, attrs| [u, attrs] })
12
12
  graph.adj.each do |u, u_edges|
13
- graph.nodes.each { |v, attrs| result.add_edge(u, v, **attrs) if !u_edges.has_key?(v) && u != v }
13
+ graph.nodes(data: true).each { |v, attrs| result.add_edge(u, v, **attrs) if !u_edges.has_key?(v) && u != v }
14
14
  end
15
15
  result
16
16
  end
@@ -7,7 +7,7 @@ module NetworkX
7
7
  # b/w all pairs of nodes
8
8
  def self.floyd_warshall(graph)
9
9
  a, index = to_matrix(graph, Float::INFINITY, 'min')
10
- nodelen = graph.nodes.length
10
+ nodelen = graph.nodes(data: true).length
11
11
  (0..(nodelen - 1)).each { |i| a[i, i] = 0 }
12
12
  (0..(nodelen - 1)).each do |k|
13
13
  (0..(nodelen - 1)).each do |i|
@@ -34,7 +34,7 @@ module NetworkX
34
34
 
35
35
  cutoff = Float::INFINITY if cutoff.nil?
36
36
  nextlevel = {source => 1}
37
- help_single_shortest_path_length(graph.adj, nextlevel, cutoff).take(graph.nodes.length)
37
+ help_single_shortest_path_length(graph.adj, nextlevel, cutoff).take(graph.nodes(data: true).length)
38
38
  end
39
39
 
40
40
  # Computes shortest path values to all nodes from all nodes
@@ -45,7 +45,7 @@ module NetworkX
45
45
  # @return [Array<Object, Array<Object, Numeric>>] path lengths for all nodes from all nodes
46
46
  def self.all_pairs_shortest_path_length(graph, cutoff = nil)
47
47
  shortest_paths = []
48
- graph.nodes.each_key { |n| shortest_paths << [n, single_source_shortest_path_length(graph, n, cutoff)] }
48
+ graph.nodes(data: true).each_key { |n| shortest_paths << [n, single_source_shortest_path_length(graph, n, cutoff)] }
49
49
  shortest_paths
50
50
  end
51
51
 
@@ -93,7 +93,7 @@ module NetworkX
93
93
  # @return [Array<Object, Hash {Object => Array<Object> }>] paths for all nodes from all nodes
94
94
  def self.all_pairs_shortest_path(graph, cutoff = nil)
95
95
  shortest_paths = []
96
- graph.nodes.each_key { |n| shortest_paths << [n, single_source_shortest_path(graph, n, cutoff)] }
96
+ graph.nodes(data: true).each_key { |n| shortest_paths << [n, single_source_shortest_path(graph, n, cutoff)] }
97
97
  shortest_paths
98
98
  end
99
99
 
@@ -188,7 +188,7 @@ module NetworkX
188
188
  # paths and path lengths between all nodes
189
189
  def self.all_pairs_dijkstra(graph, cutoff = nil)
190
190
  path = []
191
- graph.nodes.each_key { |n| path << [n, singlesource_dijkstra(graph, n, nil, cutoff)] }
191
+ graph.nodes(data: true).each_key { |n| path << [n, singlesource_dijkstra(graph, n, nil, cutoff)] }
192
192
  path
193
193
  end
194
194
 
@@ -200,7 +200,7 @@ module NetworkX
200
200
  # @return [Array<Object, Hash{ Object => Numeric }>] path lengths between all nodes
201
201
  def self.all_pairs_dijkstra_path_length(graph, cutoff = nil)
202
202
  path_lengths = []
203
- graph.nodes.each_key { |n| path_lengths << [n, singlesource_dijkstra_path_length(graph, n, cutoff)] }
203
+ graph.nodes(data: true).each_key { |n| path_lengths << [n, singlesource_dijkstra_path_length(graph, n, cutoff)] }
204
204
  path_lengths
205
205
  end
206
206
 
@@ -212,7 +212,7 @@ module NetworkX
212
212
  # @return [Array<Object, Hash{ Object => Array<Object> }>] path lengths between all nodes
213
213
  def self.all_pairs_dijkstra_path(graph, cutoff = nil)
214
214
  paths = []
215
- graph.nodes.each_key { |n| paths << singlesource_dijkstra_path(graph, n, cutoff) }
215
+ graph.nodes(data: true).each_key { |n| paths << singlesource_dijkstra_path(graph, n, cutoff) }
216
216
  paths
217
217
  end
218
218
 
@@ -221,7 +221,7 @@ module NetworkX
221
221
  pred = sources.product([[]]).to_h if pred.nil?
222
222
  dist = sources.product([0]).to_h if dist.nil?
223
223
 
224
- inf, n, count, q, in_q = Float::INFINITY, graph.nodes.length, {}, sources.clone, Set.new(sources)
224
+ inf, n, count, q, in_q = Float::INFINITY, graph.nodes(data: true).length, {}, sources.clone, Set.new(sources)
225
225
  until q.empty?
226
226
  u = q.shift
227
227
  in_q.delete(u)
@@ -281,7 +281,7 @@ module NetworkX
281
281
  # TODO: Detection of selfloop edges
282
282
  dist = {source => 0}
283
283
  pred = {source => []}
284
- return [pred, dist] if graph.nodes.length == 1
284
+ return [pred, dist] if graph.nodes(data: true).length == 1
285
285
 
286
286
  dist = help_bellman_ford(graph, [source], weight, pred, nil, dist, cutoff, target)
287
287
  [pred, dist]
@@ -360,7 +360,7 @@ module NetworkX
360
360
  # @return [Array<Object, Hash{ Object => Numeric }>] path lengths from source to all nodes
361
361
  def self.allpairs_bellmanford_path_length(graph, cutoff = nil)
362
362
  path_lengths = []
363
- graph.nodes.each_key { |n| path_lengths << [n, singlesource_bellmanford_path_length(graph, n, cutoff)] }
363
+ graph.nodes(data: true).each_key { |n| path_lengths << [n, singlesource_bellmanford_path_length(graph, n, cutoff)] }
364
364
  path_lengths
365
365
  end
366
366
 
@@ -372,13 +372,13 @@ module NetworkX
372
372
  # @return [Array<Object, Hash{ Object => Array<Object> }>] path lengths from source to all nodes
373
373
  def self.allpairs_bellmanford_path(graph, cutoff = nil)
374
374
  paths = []
375
- graph.nodes.each_key { |n| paths << [n, singlesource_bellmanford_path(graph, n, cutoff)] }
375
+ graph.nodes(data: true).each_key { |n| paths << [n, singlesource_bellmanford_path(graph, n, cutoff)] }
376
376
  paths
377
377
  end
378
378
 
379
379
  # Helper function to get sources
380
380
  def self.get_sources(graph)
381
- graph.nodes.collect { |k, _v| k }
381
+ graph.nodes(data: true).collect { |k, _v| k }
382
382
  end
383
383
 
384
384
  # Helper function to get distances
@@ -393,7 +393,7 @@ module NetworkX
393
393
  # Helper function to set path lengths for Johnson algorithm
394
394
  def self.set_path_lengths_johnson(graph, dist_path, new_weight)
395
395
  path_lengths = []
396
- graph.nodes.each_key { |n| path_lengths << [n, dist_path.call(graph, n, new_weight)] }
396
+ graph.nodes(data: true).each_key { |n| path_lengths << [n, dist_path.call(graph, n, new_weight)] }
397
397
  path_lengths
398
398
  end
399
399
 
@@ -405,7 +405,7 @@ module NetworkX
405
405
  def self.johnson(graph)
406
406
  dist, pred = {}, {}
407
407
  sources = get_sources(graph)
408
- graph.nodes.each_key do |n|
408
+ graph.nodes(data: true).each_key do |n|
409
409
  dist[n], pred[n] = 0, []
410
410
  end
411
411
  weight = get_weight(graph)
@@ -2,14 +2,14 @@ module NetworkX
2
2
  def self.to_matrix(graph, val, multigraph_weight = 'sum')
3
3
  is_undirected = !graph.directed?
4
4
  is_multigraph = graph.multigraph?
5
- nodelen = graph.nodes.length
5
+ nodelen = graph.nodes(data: true).length
6
6
 
7
7
  m = Matrix.build(nodelen) { val }
8
8
  index = {}
9
9
  inv_index = {}
10
10
  ind = 0
11
11
 
12
- graph.nodes.each do |u, _|
12
+ graph.nodes(data: true).each do |u, _|
13
13
  index[u] = ind
14
14
  inv_index[ind] = u
15
15
  ind += 1
@@ -11,7 +11,7 @@ module NetworkX
11
11
  raise KeyError, "There exists no node names #{source} in the given graph." unless graph.node?(source)
12
12
 
13
13
  depth_limit += 1 if depth_limit
14
- depth_limit = graph.nodes.length if depth_limit.nil?
14
+ depth_limit = graph.nodes(data: true).length if depth_limit.nil?
15
15
  dfs_edges = []
16
16
  visited = [source]
17
17
  stack = [[-1, source, depth_limit, graph.neighbours(source)]]
@@ -1,3 +1,3 @@
1
1
  module NetworkX
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
data/networkx.gemspec CHANGED
@@ -13,13 +13,15 @@ Gem::Specification.new do |spec|
13
13
  spec.email = ['athityakumar@gmail.com']
14
14
  spec.summary = 'A Ruby implemenation of the well-known graph library called networkx.'
15
15
  spec.description = NetworkX::DESCRIPTION
16
- spec.homepage = 'https://github.com/athityakumar/networkx.rb'
16
+ spec.homepage = 'https://github.com/SciRuby/networkx.rb'
17
17
  spec.license = 'MIT'
18
18
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
19
  spec.bindir = 'bin'
20
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
+ spec.required_ruby_version = '>= 2.7'
24
+
23
25
  spec.add_development_dependency 'bundler', '~> 2.0'
24
26
  spec.add_development_dependency 'rake', '~> 13.0'
25
27
  spec.add_development_dependency 'rspec', '~> 3.0'
@@ -34,4 +36,6 @@ Gem::Specification.new do |spec|
34
36
 
35
37
  spec.add_runtime_dependency 'matrix', '~> 0.4'
36
38
  spec.add_runtime_dependency 'rb_heap', '~> 1.0'
39
+
40
+ spec.metadata['rubygems_mfa_required'] = 'true'
37
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: networkx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Athitya Kumar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-25 00:00:00.000000000 Z
11
+ date: 2022-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -207,7 +207,6 @@ files:
207
207
  - ".github/PULL_REQUEST_TEMPLATE.md"
208
208
  - ".github/workflows/ci.yml"
209
209
  - ".github/workflows/doc.yml"
210
- - ".github/workflows/gem-push.yml"
211
210
  - ".gitignore"
212
211
  - ".rspec"
213
212
  - ".rubocop.yml"
@@ -259,10 +258,11 @@ files:
259
258
  - lib/networkx/traversals/edge_dfs.rb
260
259
  - lib/networkx/version.rb
261
260
  - networkx.gemspec
262
- homepage: https://github.com/athityakumar/networkx.rb
261
+ homepage: https://github.com/SciRuby/networkx.rb
263
262
  licenses:
264
263
  - MIT
265
- metadata: {}
264
+ metadata:
265
+ rubygems_mfa_required: 'true'
266
266
  post_install_message:
267
267
  rdoc_options: []
268
268
  require_paths:
@@ -271,7 +271,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
271
271
  requirements:
272
272
  - - ">="
273
273
  - !ruby/object:Gem::Version
274
- version: '0'
274
+ version: '2.7'
275
275
  required_rubygems_version: !ruby/object:Gem::Requirement
276
276
  requirements:
277
277
  - - ">="
@@ -1,45 +0,0 @@
1
- name: Ruby Gem
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- build:
11
- name: Build + Publish
12
- runs-on: ubuntu-latest
13
- permissions:
14
- contents: read
15
- packages: write
16
-
17
- steps:
18
- - uses: actions/checkout@v2
19
- - name: Set up Ruby 2.6
20
- uses: actions/setup-ruby@v1
21
- with:
22
- ruby-version: 2.6.x
23
-
24
- - name: Publish to GPR
25
- run: |
26
- mkdir -p $HOME/.gem
27
- touch $HOME/.gem/credentials
28
- chmod 0600 $HOME/.gem/credentials
29
- printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
30
- gem build *.gemspec
31
- gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
32
- env:
33
- GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
34
- OWNER: ${{ github.repository_owner }}
35
-
36
- - name: Publish to RubyGems
37
- run: |
38
- mkdir -p $HOME/.gem
39
- touch $HOME/.gem/credentials
40
- chmod 0600 $HOME/.gem/credentials
41
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
42
- gem build *.gemspec
43
- gem push *.gem
44
- env:
45
- GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"