louvian_ruby 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/louvian/community.rb +23 -8
- data/lib/louvian.rb +47 -139
- metadata +1 -1
data/lib/louvian/community.rb
CHANGED
@@ -1,19 +1,32 @@
|
|
1
1
|
class Louvian::Community
|
2
2
|
attr_accessor :in, :tot, :nodes_ids, :id
|
3
3
|
@@count = 0
|
4
|
-
def initialize
|
4
|
+
def initialize adj_list, level
|
5
|
+
#puts "Adj list is "
|
5
6
|
@id = @@count
|
6
7
|
@@count+=1
|
7
|
-
|
8
|
-
|
9
|
-
@
|
8
|
+
|
9
|
+
# TODO NO NEED TO SORT
|
10
|
+
@nodes_ids = adj_list.keys.sort
|
11
|
+
@level = level
|
12
|
+
|
13
|
+
# sum of links weights inside the community
|
14
|
+
#@in = adj_list.select {|k,v| nodes_ids.include? k}.inject(0) {|r,(k,v)| r+v.count}
|
15
|
+
|
16
|
+
@in = 0
|
17
|
+
adj_list.each do |node, neighbors|
|
18
|
+
@in += neighbors.select {|node| @nodes_ids.include? node}.count
|
19
|
+
end
|
20
|
+
|
21
|
+
# sum of links weights incident to the community
|
22
|
+
@tot = adj_list.inject(0) {|r,(k,v)| r+v.count}
|
10
23
|
end
|
11
24
|
|
12
25
|
def self.reset
|
13
26
|
@@count = 0
|
14
27
|
end
|
15
28
|
|
16
|
-
def insert node,
|
29
|
+
def insert node, node_adj
|
17
30
|
#puts "\t\tinsert node #{node} to comm #{@id}"
|
18
31
|
@nodes_ids << node
|
19
32
|
|
@@ -21,12 +34,13 @@ class Louvian::Community
|
|
21
34
|
#@in += links_to_comm
|
22
35
|
#@tot += (Louvian.get_adj(node).count - links_to_comm)
|
23
36
|
|
37
|
+
links_to_comm = node_adj.select {|n| nodes_ids.include? n}.count
|
24
38
|
# Copied from the cpp code
|
25
39
|
@in += 2*links_to_comm
|
26
|
-
@tot +=
|
40
|
+
@tot += node_adj.count
|
27
41
|
end
|
28
42
|
|
29
|
-
def remove node,
|
43
|
+
def remove node, node_adj
|
30
44
|
#puts "\t\tremove node #{node} to comm #{@id}"
|
31
45
|
@nodes_ids.delete node
|
32
46
|
|
@@ -34,9 +48,10 @@ class Louvian::Community
|
|
34
48
|
#@in -= links_to_comm
|
35
49
|
#@tot -= (Louvian.get_adj(node).count - links_to_comm)
|
36
50
|
|
51
|
+
links_to_comm = node_adj.select {|n| nodes_ids.include? n}.count
|
37
52
|
# Copied from the cpp code
|
38
53
|
@in -= 2*links_to_comm
|
39
|
-
@tot -=
|
54
|
+
@tot -= node_adj.count
|
40
55
|
end
|
41
56
|
|
42
57
|
end
|
data/lib/louvian.rb
CHANGED
@@ -1,179 +1,89 @@
|
|
1
|
-
require 'set'
|
2
1
|
module Louvian
|
3
2
|
require 'louvian/community'
|
3
|
+
require 'louvian/graph'
|
4
4
|
|
5
5
|
MIN_INCREASE = 0.000001
|
6
6
|
|
7
|
-
# Node => community
|
8
|
-
@@n2c = {}
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# List of all communities (meta_nodes)
|
14
|
-
@@communities = []
|
15
|
-
|
16
|
-
# List of all nodes
|
17
|
-
@@nodes = []
|
18
|
-
|
19
|
-
# Sum of all links half edges (double the number of edges)
|
20
|
-
@@total_weight = 0.0
|
21
|
-
|
22
|
-
def self.communities
|
23
|
-
@@communities
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.get_adj node_id
|
27
|
-
return @@adj[node_id]
|
8
|
+
def self.graph
|
9
|
+
@@graph
|
28
10
|
end
|
29
11
|
|
30
|
-
|
31
|
-
|
32
|
-
@@communities.each do |m|
|
33
|
-
puts "#{m.id} => #{m.nodes_ids} in=#{m.in} tot=#{m.tot}"
|
34
|
-
end
|
12
|
+
def self.levels
|
13
|
+
@@levels
|
35
14
|
end
|
36
|
-
|
37
15
|
# This method sets up the whole environemnt for calculations
|
38
16
|
#
|
39
17
|
# @param string [String] in the form of src dest (one edge per line)
|
40
|
-
def self.init_env string
|
41
|
-
|
42
|
-
@@
|
43
|
-
@@
|
44
|
-
|
45
|
-
@@n2c[k] = @@communities.last.id
|
46
|
-
end
|
47
|
-
@@total_weight = @@adj.inject(0) {|r,(k,v)| r+v.count}
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
# This method reset the enviroment and all counters
|
52
|
-
def self.reset
|
53
|
-
@@n2c = {}
|
54
|
-
@@adj={}
|
55
|
-
@@communities = []
|
56
|
-
@@nodes = []
|
57
|
-
@@total_weight = 0.0
|
58
|
-
Community.reset
|
18
|
+
def self.init_env string, directed=false
|
19
|
+
list = string.split("\n").map {|line| line.split.map{|n| n.to_i}}
|
20
|
+
@@graph = Graph.new list, directed, 0
|
21
|
+
@@levels = [] # List of Graphs
|
22
|
+
nil
|
59
23
|
end
|
60
24
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
return q
|
25
|
+
def self.run
|
26
|
+
l = 0
|
27
|
+
mod = @@graph.modularity
|
28
|
+
begin
|
29
|
+
puts "Level #{l}: Comms #{@@graph.communities.size}"
|
30
|
+
@@levels << @@graph
|
31
|
+
@@graph = @@graph.build_graph_from_comms
|
32
|
+
l+=1
|
33
|
+
end while self.one_level
|
71
34
|
end
|
72
35
|
|
73
36
|
# This method iterates over the graph to optimze the modularity. Iterations
|
74
37
|
# stops when there are no possible moves anymore.
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
improvement = 0.0
|
79
|
-
min_improvement = 0.01
|
38
|
+
# @returns improvement [Boolean] indicates whether there was improvment or no
|
39
|
+
def self.one_level
|
40
|
+
improvement = false
|
80
41
|
nb_passes = 0
|
81
|
-
|
42
|
+
cur_mod = @@graph.modularity
|
43
|
+
new_mod = cur_mod
|
82
44
|
begin
|
83
45
|
#puts "Iterating"
|
84
|
-
#puts "modularity is #{
|
46
|
+
#puts "modularity is #{@@graph.modularity}"
|
85
47
|
cur_mod = new_mod
|
86
48
|
nb_moves = 0
|
87
49
|
nb_passes += 1
|
88
|
-
@@nodes.shuffle.each do |node|
|
50
|
+
@@graph.nodes.shuffle.each do |node|
|
51
|
+
#puts "\t#{@@graph.n2c}"
|
89
52
|
#puts "\tconsidering node #{node}"
|
90
|
-
|
91
|
-
orig_community = @@communities.find {|i| i.id == orig_community_id}
|
92
|
-
node_to_comms_links = self.get_node_to_comms_links node
|
93
|
-
neighbour_communities = @@communities.find_all {|i| node_to_comms_links.include? i.id}
|
53
|
+
orig_community = @@graph.get_community node
|
94
54
|
|
95
|
-
|
55
|
+
neighbour_communities = @@graph.get_neighbour_comms node
|
96
56
|
|
57
|
+
#puts "\tneihbours#{neighbour_communities.map {|i| i.id}}"
|
58
|
+
@@graph.remove_node node, orig_community
|
97
59
|
|
98
|
-
|
60
|
+
|
61
|
+
best_community = orig_community
|
99
62
|
max_gain = 0.0
|
100
63
|
|
101
|
-
neighbour_communities.each do |
|
102
|
-
mod_gain =
|
103
|
-
#puts "\t\tfor comm #{
|
64
|
+
neighbour_communities.each do |comm|
|
65
|
+
mod_gain = @@graph.modularity_gain node, comm
|
66
|
+
#puts "\t\tfor comm #{comm.id} mod increase is #{mod_gain}"
|
104
67
|
if mod_gain > max_gain
|
105
68
|
max_gain = mod_gain
|
106
|
-
|
69
|
+
best_community = comm
|
107
70
|
end
|
108
71
|
end
|
109
|
-
if
|
72
|
+
if best_community != orig_community
|
110
73
|
nb_moves += 1
|
74
|
+
improvement = true
|
111
75
|
end
|
112
76
|
|
113
|
-
|
114
|
-
#puts "\t\tbest comm #{best_community.id}"
|
77
|
+
@@graph.insert_node node, best_community
|
115
78
|
|
116
|
-
|
117
|
-
@@n2c[node] = best_community_id
|
79
|
+
@@graph.garbage_collect orig_community
|
118
80
|
|
119
|
-
if orig_community.nodes_ids.empty?
|
120
|
-
@@communities.delete orig_community
|
121
|
-
end
|
122
81
|
end
|
123
|
-
display_communities
|
124
|
-
new_mod =
|
125
|
-
puts "modularity
|
82
|
+
#display_communities
|
83
|
+
new_mod = @@graph.modularity
|
84
|
+
#puts "modularity was #{cur_mod} and now #{new_mod}, moves #{nb_moves}"
|
126
85
|
end while nb_moves > 0 and new_mod - cur_mod >= MIN_INCREASE
|
127
|
-
|
128
|
-
|
129
|
-
private
|
130
|
-
|
131
|
-
# This method builds the adjacency list from +string+
|
132
|
-
# @param string [String] in the form of src dest (edge per line)
|
133
|
-
def self.make_adj string
|
134
|
-
@@adj = {}
|
135
|
-
lines = string.split("\n")
|
136
|
-
lines.each do |l|
|
137
|
-
@@adj[l.split[0].to_i] ||= Set.new
|
138
|
-
@@adj[l.split[0].to_i] << l.split[1].to_i
|
139
|
-
@@adj[l.split[1].to_i] ||= Set.new
|
140
|
-
@@adj[l.split[1].to_i] << l.split[0].to_i
|
141
|
-
end
|
142
|
-
nil
|
143
|
-
end
|
144
|
-
|
145
|
-
|
146
|
-
# This method calcualtes the modularity gain for moving +node+ to +community+
|
147
|
-
# @param +node+ this is the node to be moved
|
148
|
-
# @param +community+ this is the destination community
|
149
|
-
# @param +nb_links_to_comm+ is the number of links from +node+ to +community+
|
150
|
-
# @returns delta_q (the gain of modularity)
|
151
|
-
def self.modularity_gain node, community, nb_links_to_comm
|
152
|
-
tot = community.tot
|
153
|
-
deg = get_adj(node).count
|
154
|
-
m2 = @@total_weight
|
155
|
-
|
156
|
-
#puts "\t\t\tcomm #{community.id} #{[tot, deg, m2, nb_links_to_comm]}"
|
157
|
-
# what makes sense
|
158
|
-
#return (nb_links_to_comm.to_f/m2) - (tot * deg.to_f/m2**2/2)
|
159
|
-
|
160
|
-
# copied from the cpp code
|
161
|
-
return nb_links_to_comm.to_f - tot*deg.to_f/m2
|
162
|
-
end
|
163
|
-
|
164
|
-
# This method gets all neighbour communities and the number of links from +node+
|
165
|
-
# to all neighbou communities
|
166
|
-
# @param +node+ to be considered
|
167
|
-
# @return +node_to_comms_links+ [Hash] {community => number_of_links from +node+
|
168
|
-
# to +community+}
|
169
|
-
def self.get_node_to_comms_links node
|
170
|
-
neighbour_nodes = self.get_adj node
|
171
|
-
node_to_comms_links = {}
|
172
|
-
neighbour_nodes.each do |node|
|
173
|
-
node_to_comms_links[@@n2c[node]] = (node_to_comms_links[@@n2c[node]] || 0) + 1
|
174
|
-
end
|
175
|
-
node_to_comms_links[@@n2c[node]] ||= 0
|
176
|
-
return node_to_comms_links
|
86
|
+
return improvement
|
177
87
|
end
|
178
88
|
|
179
89
|
|
@@ -190,10 +100,8 @@ module Louvian
|
|
190
100
|
5 6
|
191
101
|
5 7'
|
192
102
|
|
193
|
-
Louvian.init_env s
|
194
|
-
Louvian.
|
195
|
-
Louvian.display_communities
|
196
|
-
Louvian.reset
|
103
|
+
Louvian.init_env s, false
|
104
|
+
Louvian.run
|
197
105
|
nil
|
198
106
|
end
|
199
107
|
end
|