louvian_ruby 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|