louvian_ruby 0.0.3 → 0.0.4

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/graph.rb +159 -0
  2. data/lib/louvian.rb +1 -1
  3. metadata +2 -1
@@ -0,0 +1,159 @@
1
+ class Louvian::Graph
2
+
3
+ attr_accessor :adj_list, :nodes, :communities, :directed, :n2c, :total_weight
4
+ def initialize edges_list, directed, level
5
+ # Adjacency list
6
+ @adj_list = Louvian::Graph.make_adj edges_list, directed
7
+
8
+ # List of all nodes
9
+ # TODO remove sort
10
+ @nodes = @adj_list.keys.sort
11
+
12
+ # List of all communities (meta_nodes)
13
+ @communities = []
14
+
15
+ # whether the graph is diercted or not
16
+ @directed = false
17
+
18
+ # node_id => community_id
19
+ @n2c = {}
20
+
21
+ @level = level
22
+ # TODO remove sort
23
+ @adj_list.sort.each do |k, v|
24
+ @communities << Louvian::Community.new({k => v}, @level)
25
+ @n2c[k] = @communities.last.id
26
+ end
27
+
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}
30
+ end
31
+
32
+ # This method builds the adjacency list from list of edges
33
+ # @param list [Array] in the form of [src dest] (edge per cell)
34
+ # @param directed, whether the edge list is directed or not
35
+ def self.make_adj edges_list, directed
36
+ adj = {}
37
+ edges_list.each do |edge|
38
+ adj[edge[0]] ||= []
39
+ adj[edge[1]] ||= []
40
+
41
+ adj[edge[0]] << edge[1]
42
+ if not directed
43
+ adj[edge[1]] << edge[0]
44
+ end
45
+ end
46
+ adj
47
+ end
48
+
49
+ def get_neighbour_nodes node
50
+ neighbours = adj_list[node]
51
+ end
52
+
53
+ def get_neighbour_comms node
54
+ node_to_comms_links = get_node_to_comms_links node
55
+
56
+ neighbour_communities = @communities.find_all {|comm| node_to_comms_links.include? comm.id}
57
+ end
58
+
59
+ def get_community node
60
+ @communities.find {|comm| comm.id == @n2c[node]}
61
+ end
62
+
63
+ # This method gets all neighbour communities and the number of links from node
64
+ # to all neighbou communities
65
+ # @param node to be considered
66
+ # @return node_to_comms_links [Hash] {community => number_of_links from node
67
+ # to community}
68
+ def get_node_to_comms_links node
69
+ neighbour_nodes = get_neighbour_nodes node
70
+ 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
73
+ end
74
+ node_to_comms_links[@n2c[node]] ||= 0
75
+ return node_to_comms_links
76
+ end
77
+
78
+ # OPTIMIZE
79
+ def get_number_of_links from_node, to_comm
80
+ get_node_to_comms_links(from_node)[to_comm.id]
81
+ end
82
+
83
+ # This method calcualtes the current modularity of the communities
84
+ # @returns q [Float] which is the modularity
85
+ def modularity
86
+ q = 0.0
87
+ 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)
91
+ end
92
+ q
93
+ end
94
+
95
+ # This method calcualtes the modularity gain for moving +node+ to community
96
+ # @param node this is the node to be moved
97
+ # @param community this is the destination community
98
+ # @param nb_links_to_comm is the number of links from +node+ to community
99
+ # @returns delta_q (the gain of modularity)
100
+ def modularity_gain node, community
101
+ nb_links_to_comm = get_number_of_links node, community
102
+ tot = community.tot
103
+ deg = @adj_list[node].count
104
+ m2 = @total_weight
105
+
106
+ #puts "\t\t\tcomm #{community.id} #{[tot, deg, m2, nb_links_to_comm]}"
107
+ # what makes sense
108
+ #return (nb_links_to_comm.to_f/m2) - (tot * deg.to_f/m2**2/2)
109
+
110
+ # copied from the cpp code
111
+ return nb_links_to_comm.to_f - tot*deg.to_f/m2
112
+ end
113
+
114
+ # This method outputs information about communities
115
+ def display_communities
116
+ @communities.each do |m|
117
+ puts "#{m.id} => #{m.nodes_ids} in=#{m.in} tot=#{m.tot}"
118
+ end
119
+ nil
120
+ end
121
+
122
+ def insert_node node, comm
123
+ comm.insert node, @adj_list[node]
124
+ @n2c[node] = comm.id
125
+ end
126
+
127
+ def remove_node node, comm
128
+ comm.remove node, @adj_list[node]
129
+ @n2c[node] = -1
130
+ end
131
+
132
+ def garbage_collect community
133
+ if community.nodes_ids.empty?
134
+ @communities.delete community
135
+ end
136
+ end
137
+
138
+ def build_graph_from_comms
139
+
140
+ comm_edges = []
141
+
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|
149
+ comm.nodes_ids.each do |node|
150
+ @adj_list[node].each do |linked_node|
151
+ if not comm.nodes_ids.include? linked_node
152
+ comm_edges << [comm.id, @n2c[linked_node]]
153
+ end
154
+ end
155
+ end
156
+ end
157
+ return Louvian::Graph.new comm_edges, directed, @level+1
158
+ end
159
+ end
data/lib/louvian.rb CHANGED
@@ -25,7 +25,7 @@ module Louvian
25
25
  def self.run
26
26
  l = 0
27
27
  mod = @@graph.modularity
28
- begin
28
+ begin
29
29
  puts "Level #{l}: Comms #{@@graph.communities.size}"
30
30
  @@levels << @@graph
31
31
  @@graph = @@graph.build_graph_from_comms
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.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -19,6 +19,7 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - lib/louvian.rb
21
21
  - lib/louvian/community.rb
22
+ - lib/louvian/graph.rb
22
23
  homepage: http://rubygems.org/gems/louvian_ruby
23
24
  licenses: []
24
25
  post_install_message: