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.
@@ -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=false
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
- begin
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 while self.one_level
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
- 5 6
101
- 5 7'
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.run
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
@@ -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}.count
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
- #puts "\t\tinsert node #{node} to comm #{@id}"
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
- links_to_comm = node_adj.select {|n| nodes_ids.include? n}.count
36
+ @nodes_ids << node
38
37
  # Copied from the cpp code
39
- @in += 2*links_to_comm
40
- @tot += node_adj.count
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
- # what makes sense
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 -= 2*links_to_comm
54
- @tot -= node_adj.count
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
@@ -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
- @total_weight = @adj_list.inject(0) {|r,(k,v)| r+v.count}
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
- adj[edge[1]] ||= []
43
+ adj[edge[0]] ||= {}
44
+
45
+ adj[edge[1]] ||= {}
40
46
 
41
- adj[edge[0]] << edge[1]
47
+ adj[edge[0]][edge[1]] = (adj[edge[0]][edge[1]] || 0 ) + (edge[2] || 1)
42
48
  if not directed
43
- adj[edge[1]] << edge[0]
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) + 1
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 |m_node|
90
- q += m_node.in.to_f/m2 - (m_node.tot.to_f/m2 * m_node.tot.to_f/m2)
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].count
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
- count = @communities.count
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
- return Louvian::Graph.new comm_edges, directed, @level+1
188
+ @nodes = new_graph_nodes
158
189
  end
159
190
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: louvian_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: