louvian_ruby 0.0.2 → 0.0.3
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/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
|