find_communities 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/README.md +82 -0
- data/Rakefile +1 -0
- data/bin/community +56 -0
- data/find_communities.gemspec +22 -0
- data/lib/find_communities.rb +1 -0
- data/lib/find_communities/binary_graph.rb +72 -0
- data/lib/find_communities/binary_graph_record.rb +18 -0
- data/lib/find_communities/community.rb +240 -0
- data/lib/find_communities/graph.rb +22 -0
- data/lib/find_communities/version.rb +3 -0
- data/spec/bin/community_spec.rb +13 -0
- data/spec/data/karate.bin +0 -0
- data/spec/data/karate.tree +44 -0
- data/spec/helpers.rb +9 -0
- data/spec/lib/binary_graph_spec.rb +9 -0
- data/spec/lib/community_spec.rb +9 -0
- data/spec/spec_helper.rb +24 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f4baabfdcee6316ce51e6f68b4535e15ce0ecba2
|
4
|
+
data.tar.gz: 160f7bbd40b8bbde535ff26d11df977cf5c34f55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bd8a31638591661e62877c976c3c4be5835efa0781582897c7df3352f91e33c48c936e7b2fd0357a9d6d09cf5a1a35e41be9169d2eb93a978d1ed8c437317683
|
7
|
+
data.tar.gz: 507a858459bc54620e901390dd892069ea838f81159f073483565af48f2824b36244c8b27fda1adaa8985f3bfa31d8ba8782a0ff33997f00ef4dd46630690212
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# find_communities-ruby
|
2
|
+
|
3
|
+
Ruby implementation of [Louvain community detection method](https://sites.google.com/site/findcommunities/).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'find_communities-ruby'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install find_communities-ruby
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
$ community -l -1 karate.bin
|
22
|
+
0 2
|
23
|
+
1 0
|
24
|
+
2 0
|
25
|
+
3 0
|
26
|
+
4 0
|
27
|
+
5 1
|
28
|
+
6 3
|
29
|
+
7 3
|
30
|
+
8 0
|
31
|
+
9 5
|
32
|
+
10 0
|
33
|
+
11 1
|
34
|
+
12 0
|
35
|
+
13 0
|
36
|
+
14 0
|
37
|
+
15 2
|
38
|
+
16 2
|
39
|
+
17 3
|
40
|
+
18 0
|
41
|
+
19 2
|
42
|
+
20 0
|
43
|
+
21 2
|
44
|
+
22 0
|
45
|
+
23 2
|
46
|
+
24 2
|
47
|
+
25 4
|
48
|
+
26 4
|
49
|
+
27 2
|
50
|
+
28 2
|
51
|
+
29 4
|
52
|
+
30 2
|
53
|
+
31 5
|
54
|
+
32 4
|
55
|
+
33 2
|
56
|
+
0 0
|
57
|
+
1 1
|
58
|
+
2 3
|
59
|
+
3 1
|
60
|
+
4 2
|
61
|
+
5 3
|
62
|
+
0 0
|
63
|
+
1 1
|
64
|
+
2 2
|
65
|
+
3 3
|
66
|
+
0.426969
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create new Pull Request
|
75
|
+
|
76
|
+
## CI
|
77
|
+
|
78
|
+
[![Build Status](https://secure.travis-ci.org/gkop/find_communities-ruby.png?branch=master)](http://travis-ci.org/gkop/find_communities-ruby)
|
79
|
+
|
80
|
+
## License
|
81
|
+
|
82
|
+
Released under the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0-standalone.html).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/community
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require File.dirname(__FILE__)+"/../lib/find_communities"
|
5
|
+
|
6
|
+
class CommunityMain < Thor
|
7
|
+
method_option :level, :aliases => "-l", :desc => "displays the graph of level k rather than the hierachical structure. if k=-1 then displays the hierarchical structure rather than the graph at a given level"
|
8
|
+
method_option :verbose, :aliases => "-v", :desc => "verbose mode: gives computation time, information about the hierarchy and modularity."
|
9
|
+
|
10
|
+
desc "community", "Decompose graph into communities"
|
11
|
+
argument :filename
|
12
|
+
def community
|
13
|
+
t0 = Time.now
|
14
|
+
verbose = options[:verbose]
|
15
|
+
puts "Begin: #{t0}" if verbose
|
16
|
+
precision = 0.000001
|
17
|
+
c = FindCommunities::Community.new(filename, -1, precision)
|
18
|
+
display_level = options[:level].to_i
|
19
|
+
level = 0
|
20
|
+
g = nil
|
21
|
+
improvement = true
|
22
|
+
mod = c.modularity
|
23
|
+
|
24
|
+
while improvement do
|
25
|
+
if verbose
|
26
|
+
puts "level #{level}:"
|
27
|
+
puts " start computation #{Time.now}"
|
28
|
+
puts " network size: #{c.g.nb_nodes} nodes, #{c.g.nb_links} links, #{c.g.total_weight} weight."
|
29
|
+
end
|
30
|
+
improvement = c.one_level
|
31
|
+
new_mod = c.modularity
|
32
|
+
level += 1
|
33
|
+
g.display if level == display_level && g
|
34
|
+
c.display_partition if display_level == -1
|
35
|
+
g = c.partition2graph_binary
|
36
|
+
c = FindCommunities::Community.new(g, -1, precision)
|
37
|
+
|
38
|
+
if verbose
|
39
|
+
puts " modularity increased from #{"%.6f" % mod} to #{"%.6f" % new_mod}"
|
40
|
+
puts " end computation #{Time.now}"
|
41
|
+
end
|
42
|
+
mod = new_mod
|
43
|
+
improvement = true if level == 1
|
44
|
+
end
|
45
|
+
|
46
|
+
if verbose
|
47
|
+
puts "End: #{Time.now}"
|
48
|
+
puts "Total duration: #{Time.now - t0} sec."
|
49
|
+
end
|
50
|
+
puts "%.6f" % new_mod
|
51
|
+
end
|
52
|
+
|
53
|
+
default_task :community
|
54
|
+
end
|
55
|
+
|
56
|
+
CommunityMain.start
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'find_communities/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "find_communities"
|
8
|
+
gem.version = FindCommunities::VERSION
|
9
|
+
gem.authors = ["Gabe Kopley"]
|
10
|
+
gem.email = ["gabe@coshx.com"]
|
11
|
+
gem.description = %q{Ruby implementation of Louvain community detection method}
|
12
|
+
gem.summary = %q{Ruby implementation of Louvain community detection method}
|
13
|
+
gem.homepage = "https://github.com/gkop/find_communities-ruby"
|
14
|
+
gem.license = "GPL"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.add_dependency "bindata"
|
21
|
+
gem.add_dependency "thor"
|
22
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + '/find_communities/*.rb'].each {|f| require f }
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module FindCommunities
|
2
|
+
class BinaryGraph
|
3
|
+
attr_accessor :degrees, :nb_nodes, :nb_links, :total_weight
|
4
|
+
attr_reader :links, :weights
|
5
|
+
|
6
|
+
def initialize(filename=nil, type=nil)
|
7
|
+
if filename
|
8
|
+
@record = BinaryGraphRecord.read(open(filename, "rb"))
|
9
|
+
@nb_nodes = @record["nb_nodes"]
|
10
|
+
@degrees = @record["degrees"]
|
11
|
+
@links = @record["links"]
|
12
|
+
@nb_links = @links.count
|
13
|
+
else
|
14
|
+
@nb_nodes = 0
|
15
|
+
@nb_links = 0
|
16
|
+
@links = []
|
17
|
+
end
|
18
|
+
@weights = []
|
19
|
+
@total_weight = nb_nodes.times.inject(0.0) {|sum, node|
|
20
|
+
sum + weighted_degree(node)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def nb_neighbors(node)
|
25
|
+
check_node(node)
|
26
|
+
node == 0 ? degrees[0] : degrees[node] - degrees[node-1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def nb_selfloops(node)
|
30
|
+
check_node(node)
|
31
|
+
p = neighbors(node)
|
32
|
+
nb_neighbors(node).times do |i|
|
33
|
+
if p.first[i] == node
|
34
|
+
return weights.any? ? p.last[i] : 1.0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
0.0
|
38
|
+
end
|
39
|
+
|
40
|
+
def neighbors(node)
|
41
|
+
check_node(node)
|
42
|
+
if node == 0
|
43
|
+
[links, weights]
|
44
|
+
elsif weights.length > 0
|
45
|
+
[links[degrees[node-1], links.length],
|
46
|
+
weights[degrees[node-1], weights.length]]
|
47
|
+
else
|
48
|
+
[links[degrees[node-1], links.length], weights]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def weighted_degree(node)
|
53
|
+
check_node(node)
|
54
|
+
if weights.length == 0
|
55
|
+
nb_neighbors(node)
|
56
|
+
else
|
57
|
+
p = neighbors(node)
|
58
|
+
nb_neighbors(node).times.inject(0.0) { |sum, neighbor|
|
59
|
+
sum + p.last[neighbor]
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def check_node(node)
|
66
|
+
if node < 0 || node >= nb_nodes
|
67
|
+
raise ArgumentError.new("Node index out of bounds: #{node}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
|
3
|
+
# binary file format is
|
4
|
+
# 4 bytes for the number of nodes in the graph
|
5
|
+
# 8*(nb_nodes) bytes for the cumulative degree for each node:
|
6
|
+
# deg(0)=degrees[0]
|
7
|
+
# deg(k)=degrees[k]-degrees[k-1]
|
8
|
+
# 4*(sum_degrees) bytes for the links
|
9
|
+
# IF WEIGHTED 4*(sum_degrees) bytes for the weights in a separate file
|
10
|
+
|
11
|
+
module FindCommunities
|
12
|
+
class BinaryGraphRecord < BinData::Record
|
13
|
+
endian :little
|
14
|
+
uint32 :nb_nodes
|
15
|
+
array :degrees, :type => :uint64, :initial_length => :nb_nodes
|
16
|
+
array :links, :type => :uint32, :initial_length => lambda { degrees.last }
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
module FindCommunities
|
4
|
+
class Community
|
5
|
+
attr_accessor :neigh_weight, :neigh_pos, :neigh_last
|
6
|
+
|
7
|
+
attr_accessor :g # network to compute communities for
|
8
|
+
attr_accessor :size # nummber of nodes in the network and size of all vectors
|
9
|
+
attr_accessor :n2c # community to which each node belongs
|
10
|
+
attr_accessor :in_graph, :tot # used to compute the modularity participation
|
11
|
+
# of each community
|
12
|
+
|
13
|
+
# a new pass is computed if the last one has generated an increase
|
14
|
+
# greater than min_modularity
|
15
|
+
# if 0. even a minor increase is enough to go for one more pass
|
16
|
+
attr_accessor :min_modularity
|
17
|
+
|
18
|
+
def initialize(source, nbp=nil, minm=nil)
|
19
|
+
@g = source.is_a?(BinaryGraph) ? source : BinaryGraph.new(source)
|
20
|
+
@size = g.nb_nodes
|
21
|
+
@neigh_last = 0
|
22
|
+
|
23
|
+
@neigh_weight = size.times.map { -1 }
|
24
|
+
@neigh_pos = size.times.map { 0 }
|
25
|
+
@n2c = []
|
26
|
+
@in_graph = []
|
27
|
+
@tot = []
|
28
|
+
size.times do |i|
|
29
|
+
n2c[i] = i
|
30
|
+
tot[i] = g.weighted_degree(i)
|
31
|
+
in_graph[i] = g.nb_selfloops(i)
|
32
|
+
end
|
33
|
+
@min_modularity = minm
|
34
|
+
end
|
35
|
+
|
36
|
+
def one_level
|
37
|
+
improvement = false
|
38
|
+
nb_pass_done = 0
|
39
|
+
new_mod = modularity
|
40
|
+
cur_mod = new_mod
|
41
|
+
random_order = size.times.map { |i| i }
|
42
|
+
(size-1).times do |i|
|
43
|
+
rand_pos = SecureRandom.random_number(size-i) + i
|
44
|
+
tmp = random_order[i]
|
45
|
+
random_order[i] = random_order[rand_pos]
|
46
|
+
random_order[rand_pos] = tmp
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
cur_mod = new_mod
|
51
|
+
nb_moves = 0
|
52
|
+
nb_pass_done += 1
|
53
|
+
|
54
|
+
size.times do |node_tmp|
|
55
|
+
node = random_order[node_tmp]
|
56
|
+
node_comm = n2c[node]
|
57
|
+
w_degree = g.weighted_degree(node)
|
58
|
+
|
59
|
+
# computation of all neighboring communities of current node
|
60
|
+
compute_neigh_comm(node)
|
61
|
+
# remove node from its current community
|
62
|
+
remove(node, node_comm, neigh_weight[node_comm])
|
63
|
+
|
64
|
+
# compute the nearest community for node
|
65
|
+
# default choice for future insertion is the former community
|
66
|
+
best_comm = node_comm
|
67
|
+
best_nblinks = 0.0
|
68
|
+
best_increase = 0.0
|
69
|
+
|
70
|
+
neigh_last.times do |i|
|
71
|
+
increase = modularity_gain(node, neigh_pos[i],
|
72
|
+
neigh_weight[neigh_pos[i]], w_degree)
|
73
|
+
if increase > best_increase
|
74
|
+
best_comm = neigh_pos[i]
|
75
|
+
best_nblinks = neigh_weight[neigh_pos[i]]
|
76
|
+
best_increase = increase
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# insert node in the nearest community
|
81
|
+
insert(node, best_comm, best_nblinks)
|
82
|
+
|
83
|
+
nb_moves += 1 if best_comm != node_comm
|
84
|
+
end
|
85
|
+
|
86
|
+
new_mod = modularity
|
87
|
+
improvement = true if nb_moves > 0
|
88
|
+
end while nb_moves > 0 && (new_mod - cur_mod > min_modularity)
|
89
|
+
|
90
|
+
improvement
|
91
|
+
end
|
92
|
+
|
93
|
+
def display_partition
|
94
|
+
renumber = size.times.map { -1 }
|
95
|
+
size.times do |node|
|
96
|
+
renumber[n2c[node]] += 1
|
97
|
+
end
|
98
|
+
|
99
|
+
final = 0
|
100
|
+
size.times do |i|
|
101
|
+
if renumber[i] != -1
|
102
|
+
renumber[i] = final
|
103
|
+
final += 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
size.times do |i|
|
108
|
+
puts "#{i} #{renumber[n2c[i]]}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def partition2graph_binary
|
113
|
+
renumber = size.times.map { -1 }
|
114
|
+
size.times do |node|
|
115
|
+
renumber[n2c[node]] += 1
|
116
|
+
end
|
117
|
+
|
118
|
+
final = 0
|
119
|
+
size.times do |i|
|
120
|
+
if renumber[i] != -1
|
121
|
+
renumber[i] = final
|
122
|
+
final += 1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Compute communities
|
127
|
+
comm_nodes = final.times.map { [] }
|
128
|
+
size.times do |node|
|
129
|
+
comm_nodes[renumber[n2c[node]]].push(node)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Compute weighted graph
|
133
|
+
g2 = BinaryGraph.new
|
134
|
+
g2.nb_nodes = comm_nodes.size
|
135
|
+
g2.degrees = comm_nodes.size.times.map { 0 }
|
136
|
+
|
137
|
+
comm_nodes.size.times do |comm|
|
138
|
+
m = {}
|
139
|
+
|
140
|
+
comm_size = comm_nodes[comm].size
|
141
|
+
comm_size.times do |node|
|
142
|
+
p = g.neighbors(comm_nodes[comm][node])
|
143
|
+
deg = g.nb_neighbors(comm_nodes[comm][node])
|
144
|
+
deg.times do |i|
|
145
|
+
neigh = p.first[i]
|
146
|
+
neigh_comm = renumber[n2c[neigh]]
|
147
|
+
local_neigh_weight = (g.weights.size == 0 ? 1.0 : p.last[i])
|
148
|
+
|
149
|
+
if m[neigh_comm]
|
150
|
+
m[neigh_comm][1] += local_neigh_weight
|
151
|
+
else
|
152
|
+
m[neigh_comm] = [neigh_comm, local_neigh_weight]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
g2.degrees[comm] = m.to_a.size + (comm == 0 ? 0 : g2.degrees[comm - 1])
|
157
|
+
g2.nb_links += m.to_a.size
|
158
|
+
|
159
|
+
m.each do |key, value|
|
160
|
+
g2.total_weight += value[1]
|
161
|
+
g2.links.push(value[0])
|
162
|
+
g2.weights.push(value[1])
|
163
|
+
end
|
164
|
+
end
|
165
|
+
g2
|
166
|
+
end
|
167
|
+
|
168
|
+
def modularity
|
169
|
+
q = 0.0
|
170
|
+
m2 = g.total_weight
|
171
|
+
size.times do |i|
|
172
|
+
if tot[i] > 0
|
173
|
+
q += (in_graph[i] / m2 - (tot[i] / m2)**2)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
q
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
def check_node(node)
|
182
|
+
if node < 0 || node >= size
|
183
|
+
raise ArgumentError.new("Node index out of bounds: #{node}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def insert(node, comm, dnodecomm)
|
188
|
+
check_node(node)
|
189
|
+
|
190
|
+
tot[comm] += g.weighted_degree(node)
|
191
|
+
in_graph[comm] += 2 * dnodecomm + g.nb_selfloops(node)
|
192
|
+
n2c[node] = comm
|
193
|
+
end
|
194
|
+
|
195
|
+
def modularity_gain(node, comm, dnodecomm, w_degree)
|
196
|
+
check_node(node)
|
197
|
+
|
198
|
+
totc = tot[comm]
|
199
|
+
degc = w_degree
|
200
|
+
m2 = g.total_weight
|
201
|
+
|
202
|
+
dnodecomm - ((totc.to_f * degc) / m2)
|
203
|
+
end
|
204
|
+
|
205
|
+
def compute_neigh_comm(node)
|
206
|
+
neigh_last.times { |i| neigh_weight[neigh_pos[i]] = -1 }
|
207
|
+
|
208
|
+
p = g.neighbors(node)
|
209
|
+
|
210
|
+
deg = g.nb_neighbors(node)
|
211
|
+
|
212
|
+
neigh_pos[0] = n2c[node]
|
213
|
+
neigh_weight[neigh_pos[0]] = 0
|
214
|
+
self.neigh_last = 1
|
215
|
+
|
216
|
+
deg.times do |i|
|
217
|
+
neigh = p.first[i]
|
218
|
+
neigh_comm = n2c[neigh]
|
219
|
+
neigh_w = (g.weights.size == 0 ? 1.0 : p.last[i])
|
220
|
+
|
221
|
+
if neigh != node
|
222
|
+
if neigh_weight[neigh_comm] == -1
|
223
|
+
neigh_weight[neigh_comm] = 0.0
|
224
|
+
neigh_pos[neigh_last] = neigh_comm
|
225
|
+
self.neigh_last += 1
|
226
|
+
end
|
227
|
+
neigh_weight[neigh_comm] += neigh_w
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def remove(node, comm, dnodecomm)
|
233
|
+
check_node(node)
|
234
|
+
|
235
|
+
tot[comm] -= g.weighted_degree(node)
|
236
|
+
in_graph[comm] -= 2 * dnodecomm + g.nb_selfloops(node)
|
237
|
+
n2c[node] = -1
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module FindCommunities
|
2
|
+
class Graph
|
3
|
+
def initialize(file, type=nil)
|
4
|
+
weight = 1.0
|
5
|
+
links = []
|
6
|
+
|
7
|
+
nb_links = file.count
|
8
|
+
file.each do |line|
|
9
|
+
pieces = line.split
|
10
|
+
src = pieces[0].to_i
|
11
|
+
dest = pieces[1].to_i
|
12
|
+
weight = pieces[2].to_f if type == :weighted
|
13
|
+
links[src] ||= []
|
14
|
+
links[src] << [dest, weight]
|
15
|
+
if src != dest
|
16
|
+
links[dest] ||= []
|
17
|
+
links[dest] ||= [src, weight]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "community (executable)" do
|
4
|
+
it "outputs a hierarchy", :retry => 3 do
|
5
|
+
filename = File.dirname(__FILE__) + "/../data/karate.bin"
|
6
|
+
run "community -l -1 #{filename}"
|
7
|
+
relevant_lines = out.split("\n").last(4)
|
8
|
+
relevant_lines[0].should == "1 1"
|
9
|
+
relevant_lines[1].should == "2 2"
|
10
|
+
relevant_lines[2].should == "3 3"
|
11
|
+
relevant_lines[3].to_f.round(1).should == 0.4
|
12
|
+
end
|
13
|
+
end
|
Binary file
|
@@ -0,0 +1,44 @@
|
|
1
|
+
0 5
|
2
|
+
1 1
|
3
|
+
2 1
|
4
|
+
3 1
|
5
|
+
4 1
|
6
|
+
5 0
|
7
|
+
6 2
|
8
|
+
7 2
|
9
|
+
8 1
|
10
|
+
9 5
|
11
|
+
10 5
|
12
|
+
11 0
|
13
|
+
12 1
|
14
|
+
13 1
|
15
|
+
14 1
|
16
|
+
15 5
|
17
|
+
16 5
|
18
|
+
17 2
|
19
|
+
18 1
|
20
|
+
19 5
|
21
|
+
20 1
|
22
|
+
21 5
|
23
|
+
22 1
|
24
|
+
23 5
|
25
|
+
24 4
|
26
|
+
25 3
|
27
|
+
26 3
|
28
|
+
27 4
|
29
|
+
28 4
|
30
|
+
29 3
|
31
|
+
30 4
|
32
|
+
31 5
|
33
|
+
32 3
|
34
|
+
33 5
|
35
|
+
0 1
|
36
|
+
1 0
|
37
|
+
2 1
|
38
|
+
3 2
|
39
|
+
4 3
|
40
|
+
5 3
|
41
|
+
0 0
|
42
|
+
1 1
|
43
|
+
2 2
|
44
|
+
3 3
|
data/spec/helpers.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'find_communities'
|
2
|
+
require 'pry'
|
3
|
+
require 'pry-nav'
|
4
|
+
require 'helpers'
|
5
|
+
require 'rspec/retry'
|
6
|
+
|
7
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
8
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
9
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
10
|
+
# loaded once.
|
11
|
+
#
|
12
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.filter_run :focus
|
17
|
+
|
18
|
+
# Run specs in random order to surface order dependencies. If you find an
|
19
|
+
# order dependency and want to debug it, you can fix the order by providing
|
20
|
+
# the seed, which is printed after each run.
|
21
|
+
# --seed 1234
|
22
|
+
config.order = 'random'
|
23
|
+
config.include Spec::Helpers
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: find_communities
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabe Kopley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-04-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bindata
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Ruby implementation of Louvain community detection method
|
42
|
+
email:
|
43
|
+
- gabe@coshx.com
|
44
|
+
executables:
|
45
|
+
- community
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- .travis.yml
|
51
|
+
- Gemfile
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/community
|
55
|
+
- find_communities.gemspec
|
56
|
+
- lib/find_communities.rb
|
57
|
+
- lib/find_communities/binary_graph.rb
|
58
|
+
- lib/find_communities/binary_graph_record.rb
|
59
|
+
- lib/find_communities/community.rb
|
60
|
+
- lib/find_communities/graph.rb
|
61
|
+
- lib/find_communities/version.rb
|
62
|
+
- spec/bin/community_spec.rb
|
63
|
+
- spec/data/karate.bin
|
64
|
+
- spec/data/karate.tree
|
65
|
+
- spec/helpers.rb
|
66
|
+
- spec/lib/binary_graph_spec.rb
|
67
|
+
- spec/lib/community_spec.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
homepage: https://github.com/gkop/find_communities-ruby
|
70
|
+
licenses:
|
71
|
+
- GPL
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.0.0
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: Ruby implementation of Louvain community detection method
|
93
|
+
test_files:
|
94
|
+
- spec/bin/community_spec.rb
|
95
|
+
- spec/data/karate.bin
|
96
|
+
- spec/data/karate.tree
|
97
|
+
- spec/helpers.rb
|
98
|
+
- spec/lib/binary_graph_spec.rb
|
99
|
+
- spec/lib/community_spec.rb
|
100
|
+
- spec/spec_helper.rb
|