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