networkx 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +18 -11
- data/CONTRIBUTING.md +2 -2
- data/Guardfile +1 -1
- data/ISSUE_TEMPLATE.md +15 -0
- data/PULL_REQUEST_TEMPLATE.md +12 -0
- data/README.md +4 -4
- data/RELEASE_POLICY.md +20 -0
- data/lib/networkx.rb +37 -1
- data/lib/networkx/auxillary_functions/cliques.rb +65 -0
- data/lib/networkx/auxillary_functions/cycles.rb +104 -0
- data/lib/networkx/auxillary_functions/dag.rb +54 -0
- data/lib/networkx/auxillary_functions/eccentricity.rb +36 -0
- data/lib/networkx/auxillary_functions/mis.rb +23 -0
- data/lib/networkx/auxillary_functions/mst.rb +35 -0
- data/lib/networkx/auxillary_functions/union_find.rb +24 -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 +47 -0
- data/lib/networkx/converters/to_json.rb +39 -0
- data/lib/networkx/digraph.rb +228 -0
- data/lib/networkx/flow/capacityscaling.rb +255 -0
- data/lib/networkx/flow/edmondskarp.rb +113 -0
- data/lib/networkx/flow/preflowpush.rb +252 -0
- data/lib/networkx/flow/shortestaugmentingpath.rb +157 -0
- data/lib/networkx/flow/utils.rb +160 -0
- data/lib/networkx/graph.rb +341 -0
- data/lib/networkx/link_analysis/hits.rb +59 -0
- data/lib/networkx/link_analysis/pagerank.rb +47 -0
- data/lib/networkx/multidigraph.rb +240 -0
- data/lib/networkx/multigraph.rb +171 -0
- data/lib/networkx/operators/all.rb +61 -0
- data/lib/networkx/operators/binary.rb +244 -0
- data/lib/networkx/operators/product.rb +204 -0
- data/lib/networkx/operators/unary.rb +17 -0
- data/lib/networkx/shortest_path/astar.rb +71 -0
- data/lib/networkx/shortest_path/dense.rb +31 -0
- data/lib/networkx/shortest_path/unweighted.rb +139 -0
- data/lib/networkx/shortest_path/weighted.rb +408 -0
- data/lib/networkx/to_matrix.rb +52 -0
- data/lib/networkx/traversals/bfs.rb +58 -0
- data/lib/networkx/traversals/dfs.rb +79 -0
- data/lib/networkx/traversals/edge_dfs.rb +90 -0
- data/lib/networkx/version.rb +1 -1
- data/networkx.gemspec +4 -1
- metadata +70 -4
@@ -0,0 +1,157 @@
|
|
1
|
+
# TODO: Reduce module length
|
2
|
+
|
3
|
+
module NetworkX
|
4
|
+
# TODO: Reduce method complexity and method length
|
5
|
+
|
6
|
+
# Helper function for running the shortest augmenting path algorithm
|
7
|
+
def self.shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
|
8
|
+
raise ArgumentError, 'Source is not in the graph!' unless graph.nodes.key?(source)
|
9
|
+
raise ArgumentError, 'Target is not in the graph!' unless graph.nodes.key?(target)
|
10
|
+
raise ArgumentError, 'Source and Target are the same!' if source == target
|
11
|
+
|
12
|
+
residual = residual.nil? ? build_residual_network(graph) : residual
|
13
|
+
r_nodes = residual.nodes
|
14
|
+
r_pred = residual.pred
|
15
|
+
r_adj = residual.adj
|
16
|
+
|
17
|
+
r_adj.each_value do |u_edges|
|
18
|
+
u_edges.each_value do |attrs|
|
19
|
+
attrs[:flow] = 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
heights = {target => 0}
|
24
|
+
q = [[target, 0]]
|
25
|
+
|
26
|
+
until q.empty?
|
27
|
+
u, height = q.shift
|
28
|
+
height += 1
|
29
|
+
r_pred[u].each do |v, attrs|
|
30
|
+
if !heights.key?(v) && attrs[:flow] < attrs[:capacity]
|
31
|
+
heights[v] = height
|
32
|
+
q << [v, height]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
unless heights.key?(source)
|
38
|
+
residual.graph[:flow_value] = 0
|
39
|
+
return residual
|
40
|
+
end
|
41
|
+
|
42
|
+
n = graph.nodes.length
|
43
|
+
m = residual.size / 2
|
44
|
+
|
45
|
+
r_nodes.each do |node, attrs|
|
46
|
+
attrs[:height] = heights.key?(node) ? heights[node] : n
|
47
|
+
attrs[:curr_edge] = CurrentEdge.new(r_adj[node])
|
48
|
+
end
|
49
|
+
|
50
|
+
counts = Array.new(2 * n - 1, 0)
|
51
|
+
counts.fill(0)
|
52
|
+
r_nodes.each_value { |attrs| counts[attrs[:height]] += 1 }
|
53
|
+
inf = graph.graph[:inf]
|
54
|
+
|
55
|
+
cutoff = Float::INFINITY if cutoff.nil?
|
56
|
+
flow_value = 0
|
57
|
+
path = [source]
|
58
|
+
u = source
|
59
|
+
d = two_phase ? n : [m ** 0.5, 2 * n ** (2. / 3)].min.floor
|
60
|
+
done = r_nodes[source][:height] >= d
|
61
|
+
|
62
|
+
until done
|
63
|
+
height = r_nodes[u][:height]
|
64
|
+
curr_edge = r_nodes[u][:curr_edge]
|
65
|
+
|
66
|
+
loop do
|
67
|
+
v, attr = curr_edge.get
|
68
|
+
if height == r_nodes[v][:height] + 1 && attr[:flow] < attr[:capacity]
|
69
|
+
path << v
|
70
|
+
u = v
|
71
|
+
break
|
72
|
+
end
|
73
|
+
begin
|
74
|
+
curr_edge.move_to_next
|
75
|
+
rescue StopIteration
|
76
|
+
if counts[height].zero?
|
77
|
+
residual.graph[:flow_value] = flow_value
|
78
|
+
return residual
|
79
|
+
end
|
80
|
+
height = relabel(u, n, r_adj, r_nodes)
|
81
|
+
if u == source && height >= d
|
82
|
+
if !two_phase
|
83
|
+
residual.graph[:flow_value] = flow_value
|
84
|
+
return residual
|
85
|
+
else
|
86
|
+
done = true
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
counts[height] += 1
|
91
|
+
r_nodes[u][:height] = height
|
92
|
+
unless u == source
|
93
|
+
path.pop
|
94
|
+
u = path[-1]
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
next unless u == target
|
100
|
+
flow_value += augment(path, inf, r_adj)
|
101
|
+
if flow_value >= cutoff
|
102
|
+
residual.graph[:flow_value] = flow_value
|
103
|
+
return residual
|
104
|
+
end
|
105
|
+
end
|
106
|
+
flow_value += edmondskarp_core(residual, source, target, cutoff - flow_value)
|
107
|
+
residual.graph[:flow_value] = flow_value
|
108
|
+
residual
|
109
|
+
end
|
110
|
+
|
111
|
+
# TODO: Reduce method complexity and method length
|
112
|
+
|
113
|
+
# Helper function for augmenting flow
|
114
|
+
def augment(path, inf, r_adj)
|
115
|
+
flow = inf
|
116
|
+
temp_path = path.clone
|
117
|
+
u = temp_path.shift
|
118
|
+
temp_path.each do |v|
|
119
|
+
attr = r_adj[u][v]
|
120
|
+
flow = [flow, attr[:capacity] - attr[:flow]].min
|
121
|
+
u = v
|
122
|
+
end
|
123
|
+
raise ArgumentError, 'Infinite capacity path!' if flow * 2 > inf
|
124
|
+
temp_path = path.clone
|
125
|
+
u = temp_path.shift
|
126
|
+
temp_path.each do |v|
|
127
|
+
r_adj[u][v][:flow] += flow
|
128
|
+
r_adj[v][u][:flow] -= flow
|
129
|
+
u = v
|
130
|
+
end
|
131
|
+
flow
|
132
|
+
end
|
133
|
+
|
134
|
+
# Helper function to relable a node to create a permissible edge
|
135
|
+
def self.relabel(node, num, r_adj, r_nodes)
|
136
|
+
height = num - 1
|
137
|
+
r_adj[node].each do |v, attrs|
|
138
|
+
height = [height, r_nodes[v][:height]].min if attrs[:flow] < attrs[:capacity]
|
139
|
+
end
|
140
|
+
height + 1
|
141
|
+
end
|
142
|
+
|
143
|
+
# Computes max flow using shortest augmenting path algorithm
|
144
|
+
#
|
145
|
+
# @param graph [DiGraph] a graph
|
146
|
+
# @param source [Object] source node
|
147
|
+
# @param target [Object] target node
|
148
|
+
# @param residual [DiGraph, nil] residual graph
|
149
|
+
# @param value_only [Boolean] if true, compute only the maximum flow value
|
150
|
+
# @param two_phase [Boolean] if true, two phase variant is used
|
151
|
+
# @param cutoff [Numeric] cutoff value for the algorithm
|
152
|
+
#
|
153
|
+
# @return [DiGraph] a residual graph containing the flow values
|
154
|
+
def self.shortest_augmenting_path(graph, source, target, residual=nil, _value_only=false, two_phase=false, cutoff=nil)
|
155
|
+
shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Helper class for preflow push algorithm
|
3
|
+
class CurrentEdge
|
4
|
+
attr_reader :curr, :edges
|
5
|
+
|
6
|
+
def initialize(edges)
|
7
|
+
@edges = edges
|
8
|
+
@index = {}
|
9
|
+
@n = edges.length
|
10
|
+
@curr = 0
|
11
|
+
edges.each_with_index { |(key, _value), idx| @index[idx] = key }
|
12
|
+
end
|
13
|
+
|
14
|
+
def get
|
15
|
+
[@index[@curr], @edges[@index[@curr]]]
|
16
|
+
end
|
17
|
+
|
18
|
+
def move_to_next
|
19
|
+
@temp = @curr
|
20
|
+
@curr = (@curr + 1) % @n
|
21
|
+
raise StopIteration if @temp == @n - 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Helper class for preflow push algorithm
|
26
|
+
class Level
|
27
|
+
attr_reader :inactive, :active
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@inactive = Set.new
|
31
|
+
@active = Set.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Helper class for preflow push algorithm
|
36
|
+
class GlobalRelabelThreshold
|
37
|
+
def initialize(num_1, num_2, freq)
|
38
|
+
freq = freq.nil? ? Float::INFINITY : freq
|
39
|
+
@threshold = (num_1 + num_2) / freq
|
40
|
+
@work = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_work(work)
|
44
|
+
@work += work
|
45
|
+
end
|
46
|
+
|
47
|
+
def reached?
|
48
|
+
@work >= @threshold
|
49
|
+
end
|
50
|
+
|
51
|
+
def clear_work
|
52
|
+
@work = 0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# TODO: Reduce method complexity and method length
|
57
|
+
|
58
|
+
# Builds a residual graph from a constituent graph
|
59
|
+
#
|
60
|
+
# @param graph [DiGraph] a graph
|
61
|
+
#
|
62
|
+
# @return [DiGraph] residual graph
|
63
|
+
def self.build_residual_network(graph)
|
64
|
+
raise NotImplementedError, 'MultiGraph and MultiDiGraph not supported!' if graph.multigraph?
|
65
|
+
|
66
|
+
r_network = NetworkX::DiGraph.new(inf: 0, flow_value: 0)
|
67
|
+
r_network.add_nodes(graph.nodes.keys)
|
68
|
+
inf = Float::INFINITY
|
69
|
+
edge_list = []
|
70
|
+
|
71
|
+
graph.adj.each do |u, u_edges|
|
72
|
+
require 'spec_helper'
|
73
|
+
RSpec.describe NetworkX::DiGraph do
|
74
|
+
subject { graph }
|
75
|
+
|
76
|
+
let(:graph) { described_class.new }
|
77
|
+
|
78
|
+
before do
|
79
|
+
graph.add_edge(1, 2)
|
80
|
+
graph.add_edge(2, 4)
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when capacity_scaling is called' do
|
84
|
+
subject { NetworkX.capacity_scaling(graph) }
|
85
|
+
|
86
|
+
it { is_expected.to eq([0, {1=>{2=>0}, 2=>{4=>0}, 4=>{}}]) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
u_edges.each do |v, uv_attrs|
|
91
|
+
edge_list << [u, v, uv_attrs] if (uv_attrs[:capacity] || inf) > 0 && u != v
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
inf_chk = 3 * edge_list.inject(0) do |result, arr|
|
96
|
+
arr[2].key?(:capacity) && arr[2][:capacity] != inf ? (result + arr[2][:capacity]) : result
|
97
|
+
end
|
98
|
+
inf = inf_chk.zero? ? 1 : inf_chk
|
99
|
+
|
100
|
+
if graph.directed?
|
101
|
+
edge_list.each do |u, v, attrs|
|
102
|
+
r = [attrs[:capacity] || inf, inf].min
|
103
|
+
if r_network.adj[u][v].nil?
|
104
|
+
r_network.add_edge(u, v, capacity: r)
|
105
|
+
r_network.add_edge(v, u, capacity: 0)
|
106
|
+
else
|
107
|
+
r_network[u][v][:capacity] = r
|
108
|
+
end
|
109
|
+
end
|
110
|
+
else
|
111
|
+
edge_list.each do |u, v, attrs|
|
112
|
+
r = [attrs[:capacity] || inf, inf].min
|
113
|
+
r_network.add_edge(u, v, capacity: r)
|
114
|
+
r_network.add_edge(v, u, capacity: r)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
r_network.graph[:inf] = inf
|
118
|
+
r_network
|
119
|
+
end
|
120
|
+
|
121
|
+
# TODO: Reduce method complexity and method length
|
122
|
+
|
123
|
+
# Detects unboundedness in a graph, raises exception when
|
124
|
+
# infinite capacity flow is found
|
125
|
+
#
|
126
|
+
# @param r_network [DiGraph] a residual graph
|
127
|
+
# @param source [Object] source node
|
128
|
+
# @param target [Object] target node
|
129
|
+
def self.detect_unboundedness(r_network, source, target)
|
130
|
+
q = [source]
|
131
|
+
seen = Set.new([source])
|
132
|
+
inf = r_network.graph[:inf]
|
133
|
+
until q.empty?
|
134
|
+
u = q.shift
|
135
|
+
r_network.adj[u].each do |v, uv_attrs|
|
136
|
+
next unless uv_attrs[:capacity] == inf && !seen.include?(v)
|
137
|
+
raise ArgumentError, 'Infinite capacity flow!' if v == target
|
138
|
+
seen << v
|
139
|
+
q << v
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Build flow dictionary of a graph from its residual graph
|
145
|
+
#
|
146
|
+
# @param graph [DiGraph] a graph
|
147
|
+
# @param residual [DiGraph] residual graph
|
148
|
+
#
|
149
|
+
# @return [Hash{ Object => Hash{ Object => Numeric }] flowdict containing all
|
150
|
+
# the flow values in the edges
|
151
|
+
def self.build_flow_dict(graph, residual)
|
152
|
+
flow_dict = {}
|
153
|
+
graph.edges.each do |u, u_edges|
|
154
|
+
flow_dict[u] = {}
|
155
|
+
u_edges.each_key { |v| flow_dict[u][v] = 0 }
|
156
|
+
u_edges.each_key { |v| flow_dict[u][v] = residual[u][v][:flow] if residual[u][v][:flow] > 0 }
|
157
|
+
end
|
158
|
+
flow_dict
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,341 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Describes the class for making Undirected 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 nodes [Hash{ Object => Hash{ Object => Object } }] Stores the nodes and their attributes
|
7
|
+
# @attr_reader graph [Hash{ Object => Object }] Stores the attributes of the graph
|
8
|
+
class Graph
|
9
|
+
attr_reader :adj, :nodes, :graph
|
10
|
+
|
11
|
+
# Constructor for initializing graph
|
12
|
+
#
|
13
|
+
# @example Initialize a graph with attributes 'type' and 'name'
|
14
|
+
# graph = NetworkX::Graph.new(name: "Social Network", type: "undirected")
|
15
|
+
#
|
16
|
+
# @param graph_attrs [Hash{ Object => Object }] the graph attributes in a hash format
|
17
|
+
def initialize(**graph_attrs)
|
18
|
+
@nodes = {}
|
19
|
+
@adj = {}
|
20
|
+
@graph = {}
|
21
|
+
|
22
|
+
@graph = graph_attrs
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds the respective edges
|
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
|
+
@adj[node_2][node_1] = edge_attrs
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds multiple edges from an array
|
46
|
+
#
|
47
|
+
# @example Add multiple edges without any attributes
|
48
|
+
# graph.add_edges([['Nagpur', 'Kgp'], ['Noida', 'Kgp']])
|
49
|
+
# @param edges [Array<Object, Object>]
|
50
|
+
def add_edges(edges)
|
51
|
+
case edges
|
52
|
+
when Array
|
53
|
+
edges.each { |node_1, node_2, **attrs| add_edge(node_1, node_2, attrs) }
|
54
|
+
else
|
55
|
+
raise ArgumentError, 'Expected argument to be an Array of edges, '\
|
56
|
+
"received #{edges.class.name} instead."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Adds a node and its attributes to the graph
|
61
|
+
#
|
62
|
+
# @example Add a node with attribute 'type'
|
63
|
+
# graph.add_node("Noida", type: "city")
|
64
|
+
#
|
65
|
+
# @param node [Object] the node object
|
66
|
+
# @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
|
67
|
+
def add_node(node, **node_attrs)
|
68
|
+
if @nodes.key?(node)
|
69
|
+
@nodes[node].merge!(node_attrs)
|
70
|
+
else
|
71
|
+
@adj[node] = {}
|
72
|
+
@nodes[node] = node_attrs
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Adds multiple nodes to the graph
|
77
|
+
#
|
78
|
+
# @example Adds multiple nodes with attribute 'type'
|
79
|
+
# graph.add_nodes([["Noida", type: "city"], ["Kgp", type: "town"]])
|
80
|
+
#
|
81
|
+
# @param nodes [Array<Object, Hash{ Object => Object }>] the Array of pair containing nodes and its attributes
|
82
|
+
def add_nodes(nodes)
|
83
|
+
case nodes
|
84
|
+
when Set, Array
|
85
|
+
nodes.each { |node, **node_attrs| add_node(node, node_attrs) }
|
86
|
+
else
|
87
|
+
raise ArgumentError, 'Expected argument to be an Array or Set of nodes, '\
|
88
|
+
"received #{nodes.class.name} instead."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Removes node from the graph
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# graph.remove_node("Noida")
|
96
|
+
#
|
97
|
+
# @param node [Object] the node to be removed
|
98
|
+
def remove_node(node)
|
99
|
+
raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.key?(node)
|
100
|
+
@adj[node].each_key { |k| @adj[k].delete(node) }
|
101
|
+
@adj.delete(node)
|
102
|
+
@nodes.delete(node)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Removes multiple nodes from the graph
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# graph.remove_nodes(["Noida", "Bangalore"])
|
109
|
+
#
|
110
|
+
# @param nodes [Array<Object>] the array of nodes to be removed
|
111
|
+
def remove_nodes(nodes)
|
112
|
+
case nodes
|
113
|
+
when Set, Array
|
114
|
+
nodes.each { |node| remove_node(node) }
|
115
|
+
else
|
116
|
+
raise ArgumentError, 'Expected argument to be an Array or Set of nodes, '\
|
117
|
+
"received #{nodes.class.name} instead."
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Removes edge from the graph
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# graph.remove_edge('Noida', 'Bangalore')
|
125
|
+
#
|
126
|
+
# @param node_1 [Object] the first node of the edge
|
127
|
+
# @param node_2 [Object] the second node of the edge
|
128
|
+
def remove_edge(node_1, node_2)
|
129
|
+
raise KeyError, "#{node_1} is not a valid node." unless @nodes.key?(node_1)
|
130
|
+
raise KeyError, "#{node_2} is not a valid node" unless @nodes.key?(node_2)
|
131
|
+
raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1].key?(node_2)
|
132
|
+
@adj[node_1].delete(node_2)
|
133
|
+
@adj[node_2].delete(node_1) if node_1 != node_2
|
134
|
+
end
|
135
|
+
|
136
|
+
# Removes multiple edges from the graph
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# graph.remove_edges([%w[Noida Bangalore], %w[Bangalore Chennai]])
|
140
|
+
#
|
141
|
+
# @param edges [Array<Object>] the array of edges to be removed
|
142
|
+
def remove_edges(edges)
|
143
|
+
case edges
|
144
|
+
when Array, Set
|
145
|
+
edges.each { |node_1, node_2| remove_edge(node_1, node_2) }
|
146
|
+
else
|
147
|
+
raise ArgumentError, 'Expected Arguement to be Array or Set of edges, '\
|
148
|
+
"received #{edges.class.name} instead."
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Adds weighted edge
|
153
|
+
#
|
154
|
+
# @example
|
155
|
+
# graph.add_weighted_edge('Noida', 'Bangalore', 1000)
|
156
|
+
#
|
157
|
+
# @param node_1 [Object] the first node of the edge
|
158
|
+
# @param node_2 [Object] the second node of the edge
|
159
|
+
# @param weight [Integer] the weight value
|
160
|
+
def add_weighted_edge(node_1, node_2, weight)
|
161
|
+
add_edge(node_1, node_2, weight: weight)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Adds multiple weighted edges
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# graph.add_weighted_edges([['Noida', 'Bangalore'],
|
168
|
+
# ['Noida', 'Nagpur']], [1000, 2000])
|
169
|
+
#
|
170
|
+
# @param edges [Array<Object, Object>] the array of edges
|
171
|
+
# @param weights [Array<Integer>] the array of weights
|
172
|
+
def add_weighted_edges(edges, weights)
|
173
|
+
raise ArgumentError, 'edges and weights array must have equal number of elements.'\
|
174
|
+
unless edges.size == weights.size
|
175
|
+
raise ArgumentError, 'edges and weight must be given in an Array.'\
|
176
|
+
unless edges.is_a?(Array) && weights.is_a?(Array)
|
177
|
+
(edges.transpose << weights).transpose.each do |node_1, node_2, weight|
|
178
|
+
add_weighted_edge(node_1, node_2, weight)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Clears the graph
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# graph.clear
|
186
|
+
def clear
|
187
|
+
@adj.clear
|
188
|
+
@nodes.clear
|
189
|
+
@graph.clear
|
190
|
+
end
|
191
|
+
|
192
|
+
# Checks if a node is present in the graph
|
193
|
+
#
|
194
|
+
# @example
|
195
|
+
# graph.node?(node_1)
|
196
|
+
#
|
197
|
+
# @param node [Object] the node to be checked
|
198
|
+
def node?(node)
|
199
|
+
@nodes.key?(node)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Checks if the the edge consisting of two nodes is present in the graph
|
203
|
+
#
|
204
|
+
# @example
|
205
|
+
# graph.edge?(node_1, node_2)
|
206
|
+
#
|
207
|
+
# @param node_1 [Object] the first node of the edge to be checked
|
208
|
+
# @param node_2 [Object] the second node of the edge to be checked
|
209
|
+
def edge?(node_1, node_2)
|
210
|
+
node?(node_1) && @adj[node_1].key?(node_2)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Gets the node data
|
214
|
+
#
|
215
|
+
# @example
|
216
|
+
# graph.get_node_data(node)
|
217
|
+
#
|
218
|
+
# @param node [Object] the node whose data is to be fetched
|
219
|
+
def get_node_data(node)
|
220
|
+
raise ArgumentError, 'No such node exists!' unless node?(node)
|
221
|
+
@nodes[node]
|
222
|
+
end
|
223
|
+
|
224
|
+
# Gets the edge data
|
225
|
+
#
|
226
|
+
# @example
|
227
|
+
# graph.get_edge_data(node_1, node_2)
|
228
|
+
#
|
229
|
+
# @param node_1 [Object] the first node of the edge
|
230
|
+
# @param node_2 [Object] the second node of the edge
|
231
|
+
def get_edge_data(node_1, node_2)
|
232
|
+
raise KeyError, 'No such edge exists!' unless node?(node_1) && edge?(node_2)
|
233
|
+
@adj[node_1][node_2]
|
234
|
+
end
|
235
|
+
|
236
|
+
# Retus a hash of neighbours of a node
|
237
|
+
#
|
238
|
+
# @example
|
239
|
+
# graph.neighbours(node)
|
240
|
+
#
|
241
|
+
# @param node [Object] the node whose neighbours are to be fetched
|
242
|
+
def neighbours(node)
|
243
|
+
raise KeyError, 'No such node exists!' unless node?(node)
|
244
|
+
@adj[node]
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns number of nodes
|
248
|
+
#
|
249
|
+
# @example
|
250
|
+
# graph.number_of_nodes
|
251
|
+
def number_of_nodes
|
252
|
+
@nodes.length
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns number of edges
|
256
|
+
#
|
257
|
+
# @example
|
258
|
+
# graph.number_of_edges
|
259
|
+
def number_of_edges
|
260
|
+
@adj.values.map(&:length).inject(:+) / 2
|
261
|
+
end
|
262
|
+
|
263
|
+
# Returns the size of the graph
|
264
|
+
#
|
265
|
+
# @example
|
266
|
+
# graph.size(true)
|
267
|
+
#
|
268
|
+
# @param is_weighted [Bool] if true, method returns sum of weights of all edges
|
269
|
+
# else returns number of edges
|
270
|
+
def size(is_weighted=false)
|
271
|
+
if is_weighted
|
272
|
+
graph_size = 0
|
273
|
+
@adj.each do |_, hash_val|
|
274
|
+
hash_val.each { |_, v| graph_size += v[:weight] if v.key?(:weight) }
|
275
|
+
end
|
276
|
+
return graph_size / 2
|
277
|
+
end
|
278
|
+
number_of_edges
|
279
|
+
end
|
280
|
+
|
281
|
+
# TODO: Reduce method length and method complexity
|
282
|
+
|
283
|
+
# Returns subgraph consisting of given array of nodes
|
284
|
+
#
|
285
|
+
# @example
|
286
|
+
# graph.subgraph(%w[Mumbai Nagpur])
|
287
|
+
#
|
288
|
+
# @param nodes [Array<Object>] the nodes to be included in the subgraph
|
289
|
+
def subgraph(nodes)
|
290
|
+
case nodes
|
291
|
+
when Array, Set
|
292
|
+
sub_graph = NetworkX::Graph.new(@graph)
|
293
|
+
nodes.each do |u, _|
|
294
|
+
raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.key?(u)
|
295
|
+
sub_graph.add_node(u, @nodes[u])
|
296
|
+
@adj[u].each do |v, edge_val|
|
297
|
+
sub_graph.add_edge(u, v, edge_val) if @adj[u].key?(v) && nodes.include?(v)
|
298
|
+
end
|
299
|
+
return sub_graph
|
300
|
+
end
|
301
|
+
else
|
302
|
+
raise ArgumentError, 'Expected Argument to be Array or Set of nodes, '\
|
303
|
+
"received #{nodes.class.name} instead."
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# TODO: Reduce method length and method complexity
|
308
|
+
|
309
|
+
# Returns subgraph conisting of given edges
|
310
|
+
#
|
311
|
+
# @example
|
312
|
+
# graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
|
313
|
+
#
|
314
|
+
# @param edges [Array<Object, Object>] the edges to be included in the subraph
|
315
|
+
def edge_subgraph(edges)
|
316
|
+
case edges
|
317
|
+
when Array, Set
|
318
|
+
sub_graph = NetworkX::Graph.new(@graph)
|
319
|
+
edges.each do |u, v|
|
320
|
+
raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.key?(u)\
|
321
|
+
&& @adj[u].key?(v)
|
322
|
+
sub_graph.add_node(u, @nodes[u])
|
323
|
+
sub_graph.add_node(v, @nodes[v])
|
324
|
+
sub_graph.add_edge(u, v, @adj[u][v])
|
325
|
+
end
|
326
|
+
return sub_graph
|
327
|
+
else
|
328
|
+
raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
|
329
|
+
"received #{edges.class.name} instead."
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def multigraph?
|
334
|
+
['NetworkX::MultiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
|
335
|
+
end
|
336
|
+
|
337
|
+
def directed?
|
338
|
+
['NetworkX::DiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|