louvian_ruby 0.0.4 → 0.0.5
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.
- data/lib/louvian.rb +47 -17
- data/lib/louvian/community.rb +19 -22
- data/lib/louvian/graph.rb +58 -27
- metadata +1 -1
data/lib/louvian.rb
CHANGED
@@ -9,13 +9,21 @@ module Louvian
|
|
9
9
|
@@graph
|
10
10
|
end
|
11
11
|
|
12
|
+
def self.graph= value
|
13
|
+
@@graph = value
|
14
|
+
end
|
15
|
+
|
12
16
|
def self.levels
|
13
17
|
@@levels
|
14
18
|
end
|
19
|
+
|
20
|
+
def self.levels= value
|
21
|
+
@@levels = value
|
22
|
+
end
|
15
23
|
# This method sets up the whole environemnt for calculations
|
16
24
|
#
|
17
25
|
# @param string [String] in the form of src dest (one edge per line)
|
18
|
-
def self.init_env string, directed
|
26
|
+
def self.init_env string, directed
|
19
27
|
list = string.split("\n").map {|line| line.split.map{|n| n.to_i}}
|
20
28
|
@@graph = Graph.new list, directed, 0
|
21
29
|
@@levels = [] # List of Graphs
|
@@ -25,12 +33,28 @@ module Louvian
|
|
25
33
|
def self.run
|
26
34
|
l = 0
|
27
35
|
mod = @@graph.modularity
|
28
|
-
|
36
|
+
puts "Level #{l}: Comms #{@@graph.communities.size}"
|
37
|
+
l +=1
|
38
|
+
|
39
|
+
while self.one_level
|
29
40
|
puts "Level #{l}: Comms #{@@graph.communities.size}"
|
30
41
|
@@levels << @@graph
|
31
42
|
@@graph = @@graph.build_graph_from_comms
|
32
43
|
l+=1
|
33
|
-
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.unfold_levels!
|
48
|
+
@@levels[(1..-1)].each_with_index do |graph, i|
|
49
|
+
graph.expand! @@levels[i]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.display_hierarchy
|
54
|
+
@@levels.each do |graph|
|
55
|
+
puts "level #{graph.level}: Nodes #{graph.communities.count}"
|
56
|
+
end
|
57
|
+
|
34
58
|
end
|
35
59
|
|
36
60
|
# This method iterates over the graph to optimze the modularity. Iterations
|
@@ -54,7 +78,7 @@ module Louvian
|
|
54
78
|
|
55
79
|
neighbour_communities = @@graph.get_neighbour_comms node
|
56
80
|
|
57
|
-
#puts "\tneihbours#{neighbour_communities.map {|i| i.id}}"
|
81
|
+
#puts "\tneihbours#{neighbour_communities.map {|i| i.id}} origin #{orig_community.id}"
|
58
82
|
@@graph.remove_node node, orig_community
|
59
83
|
|
60
84
|
|
@@ -72,6 +96,7 @@ module Louvian
|
|
72
96
|
if best_community != orig_community
|
73
97
|
nb_moves += 1
|
74
98
|
improvement = true
|
99
|
+
#puts "\t\tbest comm #{best_community.id}"
|
75
100
|
end
|
76
101
|
|
77
102
|
@@graph.insert_node node, best_community
|
@@ -79,7 +104,6 @@ module Louvian
|
|
79
104
|
@@graph.garbage_collect orig_community
|
80
105
|
|
81
106
|
end
|
82
|
-
#display_communities
|
83
107
|
new_mod = @@graph.modularity
|
84
108
|
#puts "modularity was #{cur_mod} and now #{new_mod}, moves #{nb_moves}"
|
85
109
|
end while nb_moves > 0 and new_mod - cur_mod >= MIN_INCREASE
|
@@ -88,20 +112,26 @@ module Louvian
|
|
88
112
|
|
89
113
|
|
90
114
|
def self.example
|
91
|
-
s='0 1
|
92
|
-
0 8
|
93
|
-
1 3
|
94
|
-
1 4
|
95
|
-
1 8
|
96
|
-
2 3
|
97
|
-
2 5
|
98
|
-
2 7
|
99
|
-
3 8
|
100
|
-
|
101
|
-
5
|
115
|
+
s='0 1 1
|
116
|
+
0 8 1
|
117
|
+
1 3 1
|
118
|
+
1 4 1
|
119
|
+
1 8 1
|
120
|
+
2 3 1
|
121
|
+
2 5 1
|
122
|
+
2 7 1
|
123
|
+
3 8 1
|
124
|
+
4 7 1
|
125
|
+
5 6 1
|
126
|
+
5 7 1'
|
102
127
|
|
103
128
|
Louvian.init_env s, false
|
104
|
-
Louvian.
|
129
|
+
Louvian.one_level
|
130
|
+
ng = Louvian.graph.build_graph_from_comms
|
131
|
+
Louvian.levels << Louvian.graph
|
132
|
+
Louvian.graph = ng
|
133
|
+
#L = Louvian
|
134
|
+
#Louvian.run
|
105
135
|
nil
|
106
136
|
end
|
107
137
|
end
|
data/lib/louvian/community.rb
CHANGED
@@ -15,43 +15,40 @@ class Louvian::Community
|
|
15
15
|
|
16
16
|
@in = 0
|
17
17
|
adj_list.each do |node, neighbors|
|
18
|
-
@in += neighbors.select {|node| @nodes_ids.include? node}.
|
18
|
+
@in += neighbors.select {|node| @nodes_ids.include? node}.values.inject(0,:+)
|
19
|
+
end
|
20
|
+
|
21
|
+
# sum of links weights inside the community
|
22
|
+
@tot = 0
|
23
|
+
adj_list.each do |node, links|
|
24
|
+
@tot += links.values.inject(0, :+)
|
19
25
|
end
|
20
26
|
|
21
|
-
# sum of links weights incident to the community
|
22
|
-
@tot = adj_list.inject(0) {|r,(k,v)| r+v.count}
|
23
27
|
end
|
24
28
|
|
25
29
|
def self.reset
|
26
30
|
@@count = 0
|
27
31
|
end
|
28
32
|
|
29
|
-
def insert node, node_adj
|
30
|
-
|
31
|
-
@nodes_ids << node
|
32
|
-
|
33
|
-
# what makes sense
|
34
|
-
#@in += links_to_comm
|
35
|
-
#@tot += (Louvian.get_adj(node).count - links_to_comm)
|
33
|
+
def insert node, node_adj, links_from_community
|
34
|
+
links_to_comm = node_adj.select {|n| @nodes_ids.include? n}.values.inject(0,:+)
|
36
35
|
|
37
|
-
|
36
|
+
@nodes_ids << node
|
38
37
|
# Copied from the cpp code
|
39
|
-
@in +=
|
40
|
-
@tot += node_adj.
|
38
|
+
@in += links_to_comm + links_from_community + (node_adj[node] || 0)
|
39
|
+
@tot += node_adj.values.inject(0,:+)
|
40
|
+
|
41
41
|
end
|
42
42
|
|
43
|
-
def remove node, node_adj
|
44
|
-
#puts "\t\tremove node #{node} to comm #{@id}"
|
43
|
+
def remove node, node_adj, links_from_community
|
45
44
|
@nodes_ids.delete node
|
45
|
+
links_to_comm = node_adj.select {|n| @nodes_ids.include? n}.values.inject(0,:+)
|
46
46
|
|
47
|
-
#
|
48
|
-
#@in -= links_to_comm
|
49
|
-
#@tot -= (Louvian.get_adj(node).count - links_to_comm)
|
50
|
-
|
51
|
-
links_to_comm = node_adj.select {|n| nodes_ids.include? n}.count
|
47
|
+
#puts "linksto t-com #{links_to_comm}"
|
52
48
|
# Copied from the cpp code
|
53
|
-
@in -=
|
54
|
-
@tot -= node_adj.
|
49
|
+
@in -= (links_to_comm + links_from_community + (node_adj[node] || 0))
|
50
|
+
@tot -= node_adj.values.inject(0,:+)
|
51
|
+
|
55
52
|
end
|
56
53
|
|
57
54
|
end
|
data/lib/louvian/graph.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class Louvian::Graph
|
2
2
|
|
3
|
-
attr_accessor :adj_list, :nodes, :communities, :directed, :n2c, :total_weight
|
3
|
+
attr_accessor :adj_list, :nodes, :communities, :directed, :n2c, :total_weight, :level
|
4
4
|
def initialize edges_list, directed, level
|
5
5
|
# Adjacency list
|
6
6
|
@adj_list = Louvian::Graph.make_adj edges_list, directed
|
@@ -26,21 +26,27 @@ class Louvian::Graph
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# Sum of all links half edges (double the number of edges)
|
29
|
-
|
29
|
+
#@total_weight = @adj_list.inject(0) {|r,(k,v)| r+v.count}
|
30
|
+
@total_weight = 0
|
31
|
+
@adj_list.each do |node, links|
|
32
|
+
@total_weight += links.values.inject(0, :+)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
# This method builds the adjacency list from list of edges
|
33
37
|
# @param list [Array] in the form of [src dest] (edge per cell)
|
34
38
|
# @param directed, whether the edge list is directed or not
|
39
|
+
# @return hash of hashes {src => {dest1 => weight1, dest1 => weight1}}
|
35
40
|
def self.make_adj edges_list, directed
|
36
41
|
adj = {}
|
37
42
|
edges_list.each do |edge|
|
38
|
-
adj[edge[0]] ||=
|
39
|
-
|
43
|
+
adj[edge[0]] ||= {}
|
44
|
+
|
45
|
+
adj[edge[1]] ||= {}
|
40
46
|
|
41
|
-
adj[edge[0]]
|
47
|
+
adj[edge[0]][edge[1]] = (adj[edge[0]][edge[1]] || 0 ) + (edge[2] || 1)
|
42
48
|
if not directed
|
43
|
-
adj[edge[1]]
|
49
|
+
adj[edge[1]][edge[0]] = (adj[edge[1]][edge[0]] || 0 ) + (edge[2] || 1)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
adj
|
@@ -68,8 +74,8 @@ class Louvian::Graph
|
|
68
74
|
def get_node_to_comms_links node
|
69
75
|
neighbour_nodes = get_neighbour_nodes node
|
70
76
|
node_to_comms_links = {}
|
71
|
-
neighbour_nodes.each do |n|
|
72
|
-
node_to_comms_links[@n2c[n]] = (node_to_comms_links[@n2c[n]] || 0) +
|
77
|
+
neighbour_nodes.each do |n, weight|
|
78
|
+
node_to_comms_links[@n2c[n]] = (node_to_comms_links[@n2c[n]] || 0) + weight
|
73
79
|
end
|
74
80
|
node_to_comms_links[@n2c[node]] ||= 0
|
75
81
|
return node_to_comms_links
|
@@ -82,12 +88,13 @@ class Louvian::Graph
|
|
82
88
|
|
83
89
|
# This method calcualtes the current modularity of the communities
|
84
90
|
# @returns q [Float] which is the modularity
|
85
|
-
def modularity
|
91
|
+
def modularity verbose=false
|
86
92
|
q = 0.0
|
87
93
|
m2 = @total_weight
|
88
|
-
|
89
|
-
@communities.each do |
|
90
|
-
|
94
|
+
puts "m2 #{m2}, comms #{@communities.count}" if verbose
|
95
|
+
@communities.each do |comm|
|
96
|
+
puts " in #{comm.in}, tot #{comm.tot}" if verbose
|
97
|
+
q += comm.in.to_f/m2 - (comm.tot.to_f/m2 * comm.tot.to_f/m2)
|
91
98
|
end
|
92
99
|
q
|
93
100
|
end
|
@@ -98,9 +105,12 @@ class Louvian::Graph
|
|
98
105
|
# @param nb_links_to_comm is the number of links from +node+ to community
|
99
106
|
# @returns delta_q (the gain of modularity)
|
100
107
|
def modularity_gain node, community
|
101
|
-
nb_links_to_comm = get_number_of_links node, community
|
108
|
+
nb_links_to_comm = (get_number_of_links node, community) || 0
|
109
|
+
#puts "node #{node}, comm #{community.id}"
|
110
|
+
#puts "####################{nb_links_to_comm}"
|
111
|
+
|
102
112
|
tot = community.tot
|
103
|
-
deg = @adj_list[node].
|
113
|
+
deg = @adj_list[node].values.inject(0,:+)
|
104
114
|
m2 = @total_weight
|
105
115
|
|
106
116
|
#puts "\t\t\tcomm #{community.id} #{[tot, deg, m2, nb_links_to_comm]}"
|
@@ -119,13 +129,22 @@ class Louvian::Graph
|
|
119
129
|
nil
|
120
130
|
end
|
121
131
|
|
132
|
+
def get_links_from_comm_to_node community, node
|
133
|
+
links = 0
|
134
|
+
nodes = community.nodes_ids - [node]
|
135
|
+
nodes.each do |n|
|
136
|
+
links+=@adj_list[n].select {|adj| adj == node}.values.inject(0,:+)
|
137
|
+
end
|
138
|
+
links
|
139
|
+
end
|
140
|
+
|
122
141
|
def insert_node node, comm
|
123
|
-
comm.insert node, @adj_list[node]
|
142
|
+
comm.insert node, @adj_list[node], (get_links_from_comm_to_node comm, node)
|
124
143
|
@n2c[node] = comm.id
|
125
144
|
end
|
126
145
|
|
127
146
|
def remove_node node, comm
|
128
|
-
comm.remove node, @adj_list[node]
|
147
|
+
comm.remove node, @adj_list[node], (get_links_from_comm_to_node comm, node)
|
129
148
|
@n2c[node] = -1
|
130
149
|
end
|
131
150
|
|
@@ -136,24 +155,36 @@ class Louvian::Graph
|
|
136
155
|
end
|
137
156
|
|
138
157
|
def build_graph_from_comms
|
139
|
-
|
140
158
|
comm_edges = []
|
141
159
|
|
142
|
-
|
143
|
-
if not directed # iterate only on one half of communities
|
144
|
-
count % 2 == 0 ? count : count + 1
|
145
|
-
count /=2
|
146
|
-
end
|
147
|
-
|
148
|
-
@communities[0,count].each do |comm|
|
160
|
+
@communities.each do |comm|
|
149
161
|
comm.nodes_ids.each do |node|
|
150
|
-
@adj_list[node].each do |linked_node|
|
162
|
+
@adj_list[node].each do |linked_node, weight|
|
151
163
|
if not comm.nodes_ids.include? linked_node
|
152
|
-
comm_edges << [comm.id, @n2c[linked_node]]
|
164
|
+
#comm_edges << [comm.id, @n2c[linked_node]] * weight
|
165
|
+
comm_edges << [comm.id, @n2c[linked_node], weight]
|
153
166
|
end
|
154
167
|
end
|
155
168
|
end
|
169
|
+
comm_edges << [comm.id, comm.id, comm.in]
|
170
|
+
end
|
171
|
+
return Louvian::Graph.new comm_edges, true, @level+1
|
172
|
+
end
|
173
|
+
|
174
|
+
def expand! lower_graph
|
175
|
+
comm_2_nodes = lower_graph.communities.inject({}) {|r,comm| r[comm.id]= comm.nodes_ids;r}
|
176
|
+
new_graph_nodes = []
|
177
|
+
@communities.each do |comm|
|
178
|
+
new_comm_nodes = []
|
179
|
+
comm.nodes_ids.each do |node|
|
180
|
+
new_comm_nodes += comm_2_nodes[node]
|
181
|
+
comm_2_nodes[node].each do |low_node|
|
182
|
+
@n2c[low_node] = comm.id
|
183
|
+
end
|
184
|
+
end
|
185
|
+
comm.nodes_ids = new_comm_nodes
|
186
|
+
new_graph_nodes += new_comm_nodes
|
156
187
|
end
|
157
|
-
|
188
|
+
@nodes = new_graph_nodes
|
158
189
|
end
|
159
190
|
end
|