louvian_ruby 0.0.4 → 0.0.5

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.
@@ -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: