louvian_ruby 0.0.0

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.
Files changed (3) hide show
  1. data/lib/louvian.rb +195 -0
  2. data/lib/louvian/community.rb +42 -0
  3. metadata +46 -0
data/lib/louvian.rb ADDED
@@ -0,0 +1,195 @@
1
+ require 'set'
2
+ module Louvian
3
+ require 'louvian/community'
4
+
5
+ # Node => community
6
+ @@n2c = {}
7
+
8
+ # Adjacency list
9
+ @@adj={}
10
+
11
+ # List of all communities (meta_nodes)
12
+ @@communities = []
13
+
14
+ # List of all nodes
15
+ @@nodes = []
16
+
17
+ # Sum of all links half edges (double the number of edges)
18
+ @@total_weight = 0.0
19
+
20
+ def self.communities
21
+ @@communities
22
+ end
23
+
24
+ def self.get_adj node_id
25
+ return @@adj[node_id]
26
+ end
27
+
28
+ # This method outputs information about communities
29
+ def self.display_communities
30
+ @@communities.each do |m|
31
+ puts "#{m.id} => #{m.nodes_ids} in=#{m.in} tot=#{m.tot}"
32
+ end
33
+ end
34
+
35
+ # This method sets up the whole environemnt for calculations
36
+ #
37
+ # @param string [String] in the form of src dest (one edge per line)
38
+ def self.init_env string
39
+ make_adj string
40
+ @@nodes = @@adj.keys.sort
41
+ @@nodes.each do |k,v|
42
+ @@communities << Community.new(k)
43
+ @@n2c[k] = @@communities.last.id
44
+ end
45
+ @@total_weight = @@adj.inject(0) {|r,(k,v)| r+v.count}
46
+
47
+ end
48
+
49
+ # This method reset the enviroment and all counters
50
+ def self.reset
51
+ @@n2c = {}
52
+ @@adj={}
53
+ @@communities = []
54
+ @@nodes = []
55
+ @@total_weight = 0.0
56
+ Community.reset
57
+ end
58
+
59
+ # This method calcualtes the current modularity of the communities
60
+ # @returns q [Float]
61
+ def self.modularity
62
+ q = 0.0
63
+ m2 = @@total_weight
64
+
65
+ @@communities.each do |m_node|
66
+ q += m_node.in.to_f/m2 - (m_node.tot.to_f/m2 * m_node.tot.to_f/m2)
67
+ end
68
+ return q
69
+ end
70
+
71
+ # This method iterates over the graph to optimze the modularity. Iterations
72
+ # stops when there are no possible moves anymore.
73
+ def self.iterate
74
+ cur_mod = self.modularity
75
+ new_mod = cur_mod
76
+ improvement = 0.0
77
+ min_improvement = 0.01
78
+ nb_passes = 0
79
+ max_passes = 10
80
+ begin
81
+ #puts "Iterating"
82
+ #puts "modularity is #{self.modularity}"
83
+ cur_mod = new_mod
84
+ nb_moves = 0
85
+ nb_passes += 1
86
+ @@nodes.shuffle.each do |node|
87
+ #puts "\tconsidering node #{node}"
88
+ orig_community_id = @@n2c[node]
89
+ orig_community = @@communities.find {|i| i.id == orig_community_id}
90
+ node_to_comms_links = self.get_node_to_comms_links node
91
+ neighbour_communities = @@communities.find_all {|i| node_to_comms_links.include? i.id}
92
+
93
+ orig_community.remove node, node_to_comms_links[orig_community_id]
94
+ if orig_community.nodes_ids.empty?
95
+ @@communities.delete orig_community
96
+ end
97
+
98
+
99
+ best_community_id = orig_community_id
100
+ max_gain = 0.0
101
+
102
+ neighbour_communities.each do |m_node|
103
+ mod_gain = self.modularity_gain(node, m_node, node_to_comms_links[m_node.id])
104
+ #puts "\t\tfor comm #{m_node.id} mod increase is #{mod_gain}"
105
+ if mod_gain > max_gain
106
+ max_gain = mod_gain
107
+ best_community_id = m_node.id
108
+ end
109
+ end
110
+ if best_community_id != orig_community_id
111
+ nb_moves += 1
112
+ end
113
+
114
+ best_community = @@communities.find{|m| m.id == best_community_id}
115
+ #puts "\t\tbest comm #{best_community.id}"
116
+
117
+ best_community.insert node, node_to_comms_links[best_community.id]
118
+ @@n2c[node] = best_community_id
119
+ end
120
+ display_communities
121
+ puts "modularity is #{self.modularity}"
122
+ end while nb_moves > 0
123
+ end
124
+
125
+ private
126
+
127
+ # This method builds the adjacency list from +string+
128
+ # @param string [String] in the form of src dest (edge per line)
129
+ def self.make_adj string
130
+ @@adj = {}
131
+ lines = string.split("\n")
132
+ lines.each do |l|
133
+ @@adj[l.split[0].to_i] ||= Set.new
134
+ @@adj[l.split[0].to_i] << l.split[1].to_i
135
+ @@adj[l.split[1].to_i] ||= Set.new
136
+ @@adj[l.split[1].to_i] << l.split[0].to_i
137
+ end
138
+ nil
139
+ end
140
+
141
+
142
+ # This method calcualtes the modularity gain for moving +node+ to +community+
143
+ # @param +node+ this is the node to be moved
144
+ # @param +community+ this is the destination community
145
+ # @param +nb_links_to_comm+ is the number of links from +node+ to +community+
146
+ # @returns delta_q (the gain of modularity)
147
+ def self.modularity_gain node, community, nb_links_to_comm
148
+ tot = community.tot
149
+ deg = get_adj(node).count
150
+ m2 = @@total_weight
151
+
152
+ #puts "\t\t\tcomm #{community.id} #{[tot, deg, m2, nb_links_to_comm]}"
153
+ # what makes sense
154
+ return (nb_links_to_comm.to_f/m2) - (tot * deg.to_f/m2**2/2)
155
+
156
+ # copied from the cpp code
157
+ #return nb_links_to_comm.to_f - tot*deg.to_f/m2
158
+ end
159
+
160
+ # This method gets all neighbour communities and the number of links from +node+
161
+ # to all neighbou communities
162
+ # @param +node+ to be considered
163
+ # @return +node_to_comms_links+ [Hash] {community => number_of_links from +node+
164
+ # to +community+}
165
+ def self.get_node_to_comms_links node
166
+ neighbour_nodes = self.get_adj node
167
+ node_to_comms_links = {}
168
+ neighbour_nodes.each do |node|
169
+ node_to_comms_links[@@n2c[node]] = (node_to_comms_links[@@n2c[node]] || 0) + 1
170
+ end
171
+ node_to_comms_links[@@n2c[node]] ||= 0
172
+ return node_to_comms_links
173
+ end
174
+
175
+
176
+ def self.example
177
+ s='0 1
178
+ 0 8
179
+ 1 3
180
+ 1 4
181
+ 1 8
182
+ 2 3
183
+ 2 5
184
+ 2 7
185
+ 3 8
186
+ 5 6
187
+ 5 7'
188
+
189
+ Louvian.init_env s
190
+ Louvian.iterate
191
+ Louvian.display_communities
192
+ Louvian.reset
193
+ nil
194
+ end
195
+ end
@@ -0,0 +1,42 @@
1
+ class Louvian::Community
2
+ attr_accessor :in, :tot, :nodes_ids, :id
3
+ @@count = 0
4
+ def initialize node_id
5
+ @id = @@count
6
+ @@count+=1
7
+ @nodes_ids = [node_id]
8
+ @in = 0 # sum of links weights inside the community
9
+ @tot = Louvian.get_adj(node_id).count # sum of links weights incident to the community
10
+ end
11
+
12
+ def self.reset
13
+ @@count = 0
14
+ end
15
+
16
+ def insert node, links_to_comm
17
+ #puts "\t\tinsert node #{node} to comm #{@id}"
18
+ @nodes_ids << node
19
+
20
+ # what makes sense
21
+ #@in += links_to_comm
22
+ #@tot += (Louvian.get_adj(node).count - links_to_comm)
23
+
24
+ # Copied from the cpp code
25
+ @in += 2*links_to_comm
26
+ @tot += (Louvian.get_adj(node).count)
27
+ end
28
+
29
+ def remove node, links_to_comm
30
+ #puts "\t\tremove node #{node} to comm #{@id}"
31
+ @nodes_ids.delete node
32
+
33
+ # what makes sense
34
+ #@in -= links_to_comm
35
+ #@tot -= (Louvian.get_adj(node).count - links_to_comm)
36
+
37
+ # Copied from the cpp code
38
+ @in -= 2*links_to_comm
39
+ @tot -= (Louvian.get_adj(node).count)
40
+ end
41
+
42
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: louvian_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yahia El Gamal
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-03 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Ruby implementation of Louvian community detection algorithim
15
+ email: yahiaelgamal@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/louvian.rb
21
+ - lib/louvian/community.rb
22
+ homepage: http://rubygems.org/gems/louvian_ruby
23
+ licenses: []
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 1.8.21
43
+ signing_key:
44
+ specification_version: 3
45
+ summary: Louvian wrapper
46
+ test_files: []