louvian_ruby 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/louvian/graph.rb +159 -0
- data/lib/louvian.rb +1 -1
- 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
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
|
+
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:
|