rubyvor 0.1.3
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.
- data/History.rdoc +60 -0
- data/Manifest.txt +30 -0
- data/README.rdoc +76 -0
- data/Rakefile +41 -0
- data/ext/Doc +30 -0
- data/ext/edgelist.c +204 -0
- data/ext/extconf.rb +3 -0
- data/ext/geometry.c +219 -0
- data/ext/heap.c +118 -0
- data/ext/memory.c +118 -0
- data/ext/output.c +251 -0
- data/ext/rb_cComputation.c +369 -0
- data/ext/rb_cPoint.c +35 -0
- data/ext/rb_cPriorityQueue.c +120 -0
- data/ext/ruby_vor_c.c +115 -0
- data/ext/ruby_vor_c.h +40 -0
- data/ext/vdefs.h +150 -0
- data/ext/voronoi.c +271 -0
- data/lib/ruby_vor.rb +16 -0
- data/lib/ruby_vor/computation.rb +136 -0
- data/lib/ruby_vor/geo_ruby_extensions.rb +15 -0
- data/lib/ruby_vor/point.rb +32 -0
- data/lib/ruby_vor/priority_queue.rb +87 -0
- data/lib/ruby_vor/version.rb +3 -0
- data/lib/ruby_vor/visualizer.rb +218 -0
- data/rubyvor.gemspec +35 -0
- data/test/test_computation.rb +354 -0
- data/test/test_point.rb +100 -0
- data/test/test_priority_queue.rb +129 -0
- data/test/test_voronoi_interface.rb +161 -0
- metadata +99 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module RubyVor
|
2
|
+
class PriorityQueue
|
3
|
+
|
4
|
+
attr_reader :data, :size
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def build_queue(max_index=-1,&block)
|
8
|
+
data = []
|
9
|
+
|
10
|
+
index = 0
|
11
|
+
loop do
|
12
|
+
x = QueueItem.new(nil, nil, nil)
|
13
|
+
|
14
|
+
yield(x)
|
15
|
+
break if !(max_index < 0 || index < max_index) || x.priority.nil?
|
16
|
+
|
17
|
+
x.index = index
|
18
|
+
data.push(x)
|
19
|
+
index += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
q = new
|
23
|
+
q.instance_variable_set(:@data, data)
|
24
|
+
q.instance_variable_set(:@size, data.length)
|
25
|
+
q.heapify()
|
26
|
+
|
27
|
+
return q
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@data = []
|
33
|
+
@size = 0
|
34
|
+
heapify()
|
35
|
+
end
|
36
|
+
|
37
|
+
def peek
|
38
|
+
@data[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
def pop
|
42
|
+
return nil if @size < 1
|
43
|
+
|
44
|
+
r = @data[0]
|
45
|
+
|
46
|
+
@data[0] = @data[@size-1]
|
47
|
+
@data[0].index = 0
|
48
|
+
@data.delete_at(@size-1)
|
49
|
+
|
50
|
+
@size -= 1
|
51
|
+
|
52
|
+
percolate_down(0) if @size > 0
|
53
|
+
|
54
|
+
return r
|
55
|
+
end
|
56
|
+
|
57
|
+
def push(data, priority=data)
|
58
|
+
@size += 1
|
59
|
+
@data[@size - 1] = QueueItem.new(priority, @size - 1, data)
|
60
|
+
percolate_up(@size - 1)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Implemented in C
|
64
|
+
def reorder_queue;end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
# Implemented in C
|
69
|
+
# def heapify(data);end
|
70
|
+
|
71
|
+
# Implemented in C
|
72
|
+
# def percolate_up(i);end
|
73
|
+
|
74
|
+
# Implemented in C
|
75
|
+
# def percolate_down(i);end
|
76
|
+
|
77
|
+
class QueueItem
|
78
|
+
attr_accessor :priority, :index, :data
|
79
|
+
def initialize(p, i, d)
|
80
|
+
@priority = p
|
81
|
+
@index = i
|
82
|
+
@data = d
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'xml/libxml' unless defined?(LibXML)
|
2
|
+
module RubyVor
|
3
|
+
class Visualizer
|
4
|
+
|
5
|
+
COLORS = %w{black red blue lime gray yellow purple orange pink}
|
6
|
+
|
7
|
+
# Support various versions of LibXML
|
8
|
+
include LibXML if defined?(LibXML)
|
9
|
+
|
10
|
+
def self.make_svg(computation, opts={})
|
11
|
+
@opts = opts = {
|
12
|
+
:name => 'vddt.svg',
|
13
|
+
:triangulation => true,
|
14
|
+
:voronoi_diagram => false,
|
15
|
+
:mst => false,
|
16
|
+
:cbd => false,
|
17
|
+
}.merge(opts)
|
18
|
+
|
19
|
+
line_colors = COLORS.clone
|
20
|
+
|
21
|
+
doc = XML::Document.new()
|
22
|
+
|
23
|
+
doc.root = XML::Node.new('svg')
|
24
|
+
doc.root['xmlns'] = 'http://www.w3.org/2000/svg'
|
25
|
+
doc.root['xml:space'] = 'preserve'
|
26
|
+
|
27
|
+
max_x = 0
|
28
|
+
min_x = Float::MAX
|
29
|
+
max_y = 0
|
30
|
+
min_y = Float::MAX
|
31
|
+
pmax_x = 0
|
32
|
+
pmin_x = Float::MAX
|
33
|
+
pmax_y = 0
|
34
|
+
pmin_y = Float::MAX
|
35
|
+
|
36
|
+
computation.points.each do |point|
|
37
|
+
max_x = point.x if point.x > max_x
|
38
|
+
min_x = point.x if point.x < min_x
|
39
|
+
max_y = point.y if point.y > max_y
|
40
|
+
min_y = point.y if point.y < min_y
|
41
|
+
pmax_x = point.x if point.x > pmax_x
|
42
|
+
pmin_x = point.x if point.x < pmin_x
|
43
|
+
pmax_y = point.y if point.y > pmax_y
|
44
|
+
pmin_y = point.y if point.y < pmin_y
|
45
|
+
end
|
46
|
+
|
47
|
+
if opts[:voronoi_diagram]
|
48
|
+
computation.voronoi_diagram_raw.each do |item|
|
49
|
+
if item.first == :v
|
50
|
+
max_x = item[1] if item[1] > max_x
|
51
|
+
min_x = item[1] if item[1] < min_x
|
52
|
+
max_y = item[2] if item[2] > max_y
|
53
|
+
min_y = item[2] if item[2] < min_y
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
opts[:offset_x] = -1.0 * min_x + 20
|
59
|
+
opts[:offset_y] = -1.0 * min_y + 20
|
60
|
+
|
61
|
+
if opts[:triangulation]
|
62
|
+
# Draw in the triangulation
|
63
|
+
color = line_colors.shift
|
64
|
+
computation.delaunay_triangulation_raw.each do |triplet|
|
65
|
+
for i in 0..2
|
66
|
+
line = line_from_points(computation.points[triplet[i % 2 + 1]], computation.points[triplet[i & 6]])
|
67
|
+
line['style'] = "stroke:#{color};stroke-width:1;"
|
68
|
+
doc.root << line
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if opts[:mst]
|
74
|
+
color = line_colors.shift
|
75
|
+
computation.minimum_spanning_tree.each do |edge, weight|
|
76
|
+
line = line_from_points(computation.points[edge[0]], computation.points[edge[1]])
|
77
|
+
line['style'] = "stroke:#{color};stroke-width:1;"
|
78
|
+
doc.root << line
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if opts[:cbd]
|
83
|
+
mst = computation.minimum_spanning_tree
|
84
|
+
|
85
|
+
computation.cluster_by_distance(opts[:cbd]).each do |cluster|
|
86
|
+
|
87
|
+
color = new_color
|
88
|
+
min_dist = Float::MAX
|
89
|
+
set_min = false
|
90
|
+
cluster.each do |a|
|
91
|
+
cluster.each do |b|
|
92
|
+
next if a == b
|
93
|
+
k = a < b ? [a,b] : [b,a]
|
94
|
+
next unless mst.has_key?(k)
|
95
|
+
|
96
|
+
if mst[k] < min_dist
|
97
|
+
min_dist = mst[k]
|
98
|
+
set_min = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
min_dist = 10 unless set_min
|
104
|
+
|
105
|
+
|
106
|
+
cluster.each do |v|
|
107
|
+
node = circle_from_point(computation.points[v])
|
108
|
+
node['r'] = (min_dist + 10).to_s
|
109
|
+
node['style'] = "fill:#{color};stroke:#{color};"
|
110
|
+
node['opacity'] = '0.4'
|
111
|
+
|
112
|
+
doc.root << node
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if opts[:voronoi_diagram]
|
119
|
+
voronoi_vertices = []
|
120
|
+
draw_lines = []
|
121
|
+
draw_points = []
|
122
|
+
line_functions = []
|
123
|
+
|
124
|
+
xcut = (pmax_x + pmin_x) / 2.0
|
125
|
+
ycut = (pmax_y + pmin_y) / 2.0
|
126
|
+
|
127
|
+
color = line_colors.shift
|
128
|
+
|
129
|
+
computation.voronoi_diagram_raw.each do |item|
|
130
|
+
case item.first
|
131
|
+
when :v
|
132
|
+
# Draw a voronoi vertex
|
133
|
+
v = RubyVor::Point.new(item[1], item[2])
|
134
|
+
voronoi_vertices.push(v)
|
135
|
+
node = circle_from_point(v)
|
136
|
+
node['fill'] = 'red'
|
137
|
+
node['r'] = '2'
|
138
|
+
node['stroke'] = 'black'
|
139
|
+
node['stroke-width'] = '1'
|
140
|
+
|
141
|
+
draw_points << node
|
142
|
+
when :l
|
143
|
+
# :l a b c --> ax + by = c
|
144
|
+
a = item[1]
|
145
|
+
b = item[2]
|
146
|
+
c = item[3]
|
147
|
+
line_functions.push({ :y => lambda{|x| (c - a * x) / b},
|
148
|
+
:x => lambda{|y| (c - b * y) / a} })
|
149
|
+
when :e
|
150
|
+
if item[2] == -1 || item[3] == -1
|
151
|
+
from_vertex = voronoi_vertices[item[2] == -1 ? item[3] : item[2]]
|
152
|
+
|
153
|
+
next if from_vertex < RubyVor::Point.new(0,0)
|
154
|
+
|
155
|
+
if item[2] == -1
|
156
|
+
inf_vertex = RubyVor::Point.new(0, line_functions[item[1]][:y][0])
|
157
|
+
else
|
158
|
+
inf_vertex = RubyVor::Point.new(max_x, line_functions[item[1]][:y][max_x])
|
159
|
+
end
|
160
|
+
|
161
|
+
line = line_from_points(from_vertex, inf_vertex)
|
162
|
+
else
|
163
|
+
line = line_from_points(voronoi_vertices[item[2]], voronoi_vertices[item[3]])
|
164
|
+
end
|
165
|
+
|
166
|
+
line['style'] = "stroke:#{color};stroke-width:1;"
|
167
|
+
draw_lines << line
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
draw_lines.each {|l| doc.root << l}
|
173
|
+
draw_points.each {|p| doc.root << p}
|
174
|
+
end
|
175
|
+
|
176
|
+
# Now draw in nodes
|
177
|
+
computation.points.each do |point|
|
178
|
+
node = circle_from_point(point)
|
179
|
+
node['fill'] = 'lime'
|
180
|
+
|
181
|
+
doc.root << node
|
182
|
+
end
|
183
|
+
|
184
|
+
doc.root['width'] = (max_x + opts[:offset_x] + 50).to_s
|
185
|
+
doc.root['height'] = (max_y + opts[:offset_y] + 50).to_s
|
186
|
+
|
187
|
+
doc.save(opts[:name] || 'vddt.svg')
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def self.line_from_points(a, b)
|
192
|
+
line = XML::Node.new('line')
|
193
|
+
line['x1'] = (a.x + @opts[:offset_x] + 10).to_s
|
194
|
+
line['y1'] = (a.y + @opts[:offset_y] + 10).to_s
|
195
|
+
line['x2'] = (b.x + @opts[:offset_x] + 10).to_s
|
196
|
+
line['y2'] = (b.y + @opts[:offset_y] + 10).to_s
|
197
|
+
line
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.circle_from_point(point)
|
201
|
+
node = XML::Node.new('circle')
|
202
|
+
node['cx'] = (point.x + @opts[:offset_x] + 10).to_s
|
203
|
+
node['cy'] = (point.y + @opts[:offset_y] + 10).to_s
|
204
|
+
node['r'] = 5.to_s
|
205
|
+
node['stroke'] = 'black'
|
206
|
+
node['stroke-width'] = 2.to_s
|
207
|
+
node
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.new_color
|
211
|
+
a = rand(256)
|
212
|
+
b = rand(256) | a
|
213
|
+
c = rand(256) ^ b
|
214
|
+
|
215
|
+
"rgb(#{[a,b,c].sort{|k,l| rand(3)-1}.join(',')})"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/rubyvor.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{rubyvor}
|
5
|
+
s.version = "0.1.3"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Brendan Ribera"]
|
9
|
+
s.date = %q{2010-01-26}
|
10
|
+
s.description = %q{RubyVor provides efficient computation of Voronoi diagrams and Delaunay triangulation for a set of Ruby points. It is intended to function as a complemenet to GeoRuby. These structures can be used to compute a nearest-neighbor graph for a set of points. This graph can in turn be used for proximity-based clustering of the input points.}
|
11
|
+
s.email = ["brendan.ribera+rubyvor@gmail.com"]
|
12
|
+
s.extensions = ["ext/extconf.rb"]
|
13
|
+
s.extra_rdoc_files = ["Manifest.txt"]
|
14
|
+
s.files = ["History.rdoc", "Manifest.txt", "README.rdoc", "Rakefile", "ext/Doc", "ext/edgelist.c", "ext/extconf.rb", "ext/geometry.c", "ext/heap.c", "ext/memory.c", "ext/output.c", "ext/rb_cComputation.c", "ext/rb_cPoint.c", "ext/rb_cPriorityQueue.c", "ext/ruby_vor_c.c", "ext/ruby_vor_c.h", "ext/vdefs.h", "ext/voronoi.c", "lib/ruby_vor.rb", "lib/ruby_vor/computation.rb", "lib/ruby_vor/geo_ruby_extensions.rb", "lib/ruby_vor/point.rb", "lib/ruby_vor/priority_queue.rb", "lib/ruby_vor/version.rb", "lib/ruby_vor/visualizer.rb", "rubyvor.gemspec", "test/test_computation.rb", "test/test_point.rb", "test/test_priority_queue.rb", "test/test_voronoi_interface.rb"]
|
15
|
+
s.homepage = %q{http://github.com/bribera/rubyvor}
|
16
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib", "ext"]
|
18
|
+
s.rubyforge_project = %q{rubyvor}
|
19
|
+
s.rubygems_version = %q{1.3.5}
|
20
|
+
s.summary = %q{Efficient Voronoi diagrams and Delaunay trianglation for Ruby}
|
21
|
+
s.test_files = ["test/test_computation.rb", "test/test_point.rb", "test/test_priority_queue.rb", "test/test_voronoi_interface.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_development_dependency(%q<hoe>, [">= 2.4.0"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<hoe>, [">= 2.4.0"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<hoe>, [">= 2.4.0"])
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,354 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'minitest/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/ruby_vor'
|
4
|
+
|
5
|
+
class TestComputation < MiniTest::Unit::TestCase
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
@points = @trianglulation_raw = @diagram_raw = nil
|
9
|
+
super(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_empty_points
|
13
|
+
assert_raises TypeError do
|
14
|
+
RubyVor::VDDT::Computation.from_points(nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_raises RuntimeError do
|
18
|
+
RubyVor::VDDT::Computation.from_points([])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_nn_graph
|
23
|
+
comp = RubyVor::VDDT::Computation.from_points(sample_points)
|
24
|
+
|
25
|
+
# based on this expected delaunay trianglulation:
|
26
|
+
# 3 2 1
|
27
|
+
# 3 0 2
|
28
|
+
# 8 2 0
|
29
|
+
# 3 8 0
|
30
|
+
# 5 6 8
|
31
|
+
# 7 6 5
|
32
|
+
# 7 4 6
|
33
|
+
# 6 2 8
|
34
|
+
# 8 7 5
|
35
|
+
|
36
|
+
expected_graph = [
|
37
|
+
[2, 3, 8], # 0
|
38
|
+
[2, 3], # 1
|
39
|
+
[0, 1, 3, 6, 8], # 2
|
40
|
+
[0, 1, 2, 8], # 3
|
41
|
+
[6, 7], # 4
|
42
|
+
[6, 7, 8], # 5
|
43
|
+
[2, 4, 5, 7, 8], # 6
|
44
|
+
[4, 5, 6, 8], # 7
|
45
|
+
[0, 2, 3, 5, 6, 7], # 8
|
46
|
+
]
|
47
|
+
|
48
|
+
|
49
|
+
comp.nn_graph.each_with_index do |neighbors,i|
|
50
|
+
refute_empty neighbors, "@nn_graph returned empty neighbors for node #{i}"
|
51
|
+
end
|
52
|
+
|
53
|
+
assert_equal comp.nn_graph.map{|v| v.sort}.sort, \
|
54
|
+
expected_graph.map{|v| v.sort}.sort
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_simple_mst
|
58
|
+
points = [
|
59
|
+
RubyVor::Point.new(0,0),
|
60
|
+
RubyVor::Point.new(1.0,1.1),
|
61
|
+
RubyVor::Point.new(4.9,3.1),
|
62
|
+
]
|
63
|
+
|
64
|
+
comp = RubyVor::VDDT::Computation.from_points(points)
|
65
|
+
expected_mst = [
|
66
|
+
[0,1],
|
67
|
+
[1,2]
|
68
|
+
]
|
69
|
+
computed_mst = comp.minimum_spanning_tree
|
70
|
+
|
71
|
+
# Assert nodes are correct
|
72
|
+
assert_equal expected_mst.sort, \
|
73
|
+
computed_mst.keys.sort
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_advanced_mst
|
77
|
+
# Test tree taken from an example SVG included in the Wikipedia article on Euclidean minimum spanning trees:
|
78
|
+
# http://en.wikipedia.org/wiki/Image:Euclidean_minimum_spanning_tree.svg
|
79
|
+
|
80
|
+
points = [
|
81
|
+
RubyVor::Point.new(155.692, 99.783), # 0
|
82
|
+
RubyVor::Point.new(162.285, 120.245), # 1
|
83
|
+
RubyVor::Point.new(143.692, 129.893), # 2
|
84
|
+
RubyVor::Point.new(150.128, 167.924), # 3
|
85
|
+
RubyVor::Point.new(137.617, 188.953), # 4
|
86
|
+
RubyVor::Point.new(193.467, 119.345), # 5
|
87
|
+
RubyVor::Point.new(196.754, 88.47), # 6
|
88
|
+
RubyVor::Point.new(241.629, 70.845), # 7
|
89
|
+
RubyVor::Point.new(262.692, 59.97), # 8
|
90
|
+
RubyVor::Point.new(269.629, 63.158), # 9
|
91
|
+
RubyVor::Point.new(247.257, 200.669), # 10
|
92
|
+
RubyVor::Point.new(231.28, 245.974), # 11
|
93
|
+
RubyVor::Point.new(268.002, 264.693), # 12
|
94
|
+
RubyVor::Point.new(155.442, 64.473), # 13
|
95
|
+
RubyVor::Point.new(198.598, 31.804), # 14
|
96
|
+
RubyVor::Point.new(216.816, 3.513), # 15
|
97
|
+
RubyVor::Point.new(89.624, 27.344), # 16
|
98
|
+
RubyVor::Point.new(67.925, 56.999), # 17
|
99
|
+
RubyVor::Point.new(77.328, 93.404), # 18
|
100
|
+
RubyVor::Point.new(65.525, 158.783), # 19
|
101
|
+
RubyVor::Point.new(63.525,170.783), # 20
|
102
|
+
RubyVor::Point.new(15.192, 192.783), # 21
|
103
|
+
RubyVor::Point.new(7.025, 236.949), # 22
|
104
|
+
RubyVor::Point.new(40.525, 262.949), # 23
|
105
|
+
RubyVor::Point.new(61.692, 225.95) # 24
|
106
|
+
]
|
107
|
+
|
108
|
+
comp = RubyVor::VDDT::Computation.from_points(points)
|
109
|
+
|
110
|
+
expected_mst = [
|
111
|
+
[0,1],
|
112
|
+
[0,13],
|
113
|
+
[1,2],
|
114
|
+
[1,5],
|
115
|
+
[2,3],
|
116
|
+
[3,4],
|
117
|
+
[5,6],
|
118
|
+
[5,10],
|
119
|
+
[6,7],
|
120
|
+
[7,8],
|
121
|
+
[8,9],
|
122
|
+
[10,11],
|
123
|
+
[11,12],
|
124
|
+
[13,14],
|
125
|
+
[13,16],
|
126
|
+
[14,15],
|
127
|
+
[16,17],
|
128
|
+
[17,18],
|
129
|
+
[18,19],
|
130
|
+
[19,20],
|
131
|
+
[20,21],
|
132
|
+
[21,22],
|
133
|
+
[22,23],
|
134
|
+
[23,24]
|
135
|
+
]
|
136
|
+
computed_mst = comp.minimum_spanning_tree
|
137
|
+
|
138
|
+
assert_equal expected_mst.sort, \
|
139
|
+
computed_mst.keys.sort
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_cluster_by_distance
|
143
|
+
comp = RubyVor::VDDT::Computation.from_points(sample_points)
|
144
|
+
|
145
|
+
expected_clusters = [
|
146
|
+
[0,1,2,3],
|
147
|
+
[8],
|
148
|
+
[4,5,6,7]
|
149
|
+
]
|
150
|
+
|
151
|
+
# We want to ensure that the nn_graph isn't modified by this call
|
152
|
+
original_nn_graph = comp.nn_graph.map{|v| v.clone}
|
153
|
+
|
154
|
+
# Compute clusters within a distance of 10
|
155
|
+
computed_clusters = comp.cluster_by_distance(10)
|
156
|
+
|
157
|
+
assert_equal expected_clusters.map{|cl| cl.sort}.sort, \
|
158
|
+
computed_clusters.map{|cl| cl.sort}.sort
|
159
|
+
|
160
|
+
assert_equal original_nn_graph, \
|
161
|
+
comp.nn_graph
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def test_duplicate_points
|
167
|
+
comp = RubyVor::VDDT::Computation.from_points([
|
168
|
+
RubyVor::Point.new(2,3), # 0
|
169
|
+
RubyVor::Point.new(1,1), # 1
|
170
|
+
RubyVor::Point.new(1,1), # 2
|
171
|
+
RubyVor::Point.new(1,1), # 3
|
172
|
+
RubyVor::Point.new(1,1), # 4
|
173
|
+
RubyVor::Point.new(1,1), # 5
|
174
|
+
RubyVor::Point.new(1,1), # 6
|
175
|
+
RubyVor::Point.new(1,1), # 7
|
176
|
+
RubyVor::Point.new(1,1), # 8
|
177
|
+
RubyVor::Point.new(4,10), # 9
|
178
|
+
RubyVor::Point.new(4,10.0), # 10
|
179
|
+
RubyVor::Point.new(4.0,10.0), # 11
|
180
|
+
RubyVor::Point.new(4.0,10.0), # 12
|
181
|
+
])
|
182
|
+
comp.nn_graph.each_with_index do |neighbors,i|
|
183
|
+
refute_empty neighbors, "@nn_graph has empty neighbors for node #{i}"
|
184
|
+
end
|
185
|
+
|
186
|
+
=begin
|
187
|
+
assert_equal [[0], [1,2,3,4,5,6,7,8], [9,10,11,12]], \
|
188
|
+
comp.cluster_by_distance(1).map{|cl| cl.sort}.sort, \
|
189
|
+
"cluster by distance 1"
|
190
|
+
|
191
|
+
assert_equal [[0,1,2,3,4,5,6,7,8], [9,10,11,12]], \
|
192
|
+
comp.cluster_by_distance(5).map{|cl| cl.sort}.sort, \
|
193
|
+
"cluster by distance 5"
|
194
|
+
|
195
|
+
assert_equal [[0,1,2,3,4,5,6,7,8,9,10,11,12]], \
|
196
|
+
comp.cluster_by_distance(10).map{|cl| cl.sort}.sort, \
|
197
|
+
"cluster by distance 10"
|
198
|
+
|
199
|
+
assert_equal [[0,1,2,3,4,5,6,7,8], [9,10,11,12]], \
|
200
|
+
comp.cluster_by_size([2])[2].map{|cl| cl.sort}.sort, \
|
201
|
+
"cluster by size 2"
|
202
|
+
=end
|
203
|
+
|
204
|
+
assert_equal [[0], [1], [2]], \
|
205
|
+
comp.cluster_by_distance(1).map{|cl| cl.sort}.sort, \
|
206
|
+
"cluster by distance 1"
|
207
|
+
|
208
|
+
assert_equal [[0,1], [2]], \
|
209
|
+
comp.cluster_by_distance(5).map{|cl| cl.sort}.sort, \
|
210
|
+
"cluster by distance 5"
|
211
|
+
|
212
|
+
assert_equal [[0,1,2]], \
|
213
|
+
comp.cluster_by_distance(10).map{|cl| cl.sort}.sort, \
|
214
|
+
"cluster by distance 10"
|
215
|
+
|
216
|
+
assert_equal [[0,1], [2]], \
|
217
|
+
comp.cluster_by_size([2])[2].map{|cl| cl.sort}.sort, \
|
218
|
+
"cluster by size 2"
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
def test_bad_data
|
224
|
+
assert_raises TypeError do
|
225
|
+
comp = RubyVor::VDDT::Computation.from_points([RubyVor::Point.new(1,1), RubyVor::Point.new(21,3), RubyVor::Point.new(2,:s)])
|
226
|
+
end
|
227
|
+
|
228
|
+
assert_raises TypeError do
|
229
|
+
comp = RubyVor::VDDT::Computation.from_points(RubyVor::Point.new(21,3))
|
230
|
+
end
|
231
|
+
|
232
|
+
assert_raises RuntimeError do
|
233
|
+
comp = RubyVor::VDDT::Computation.from_points([RubyVor::Point.new(1,1), RubyVor::Point.new(21,3), nil])
|
234
|
+
end
|
235
|
+
|
236
|
+
assert_raises RuntimeError do
|
237
|
+
comp = RubyVor::VDDT::Computation.from_points([])
|
238
|
+
end
|
239
|
+
|
240
|
+
comp = RubyVor::VDDT::Computation.from_points([RubyVor::Point.new(1,1), RubyVor::Point.new(21,3), RubyVor::Point.new(2,1.5)])
|
241
|
+
assert_raises ArgumentError do
|
242
|
+
cl = comp.cluster_by_distance(nil)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
def test_no_neighbors
|
248
|
+
nn_graph = nil
|
249
|
+
|
250
|
+
# Test :raise
|
251
|
+
comp = RubyVor::VDDT::Computation.from_points([1,2,3,4,5].map{|xy| RubyVor::Point.new(xy,xy)})
|
252
|
+
comp.no_neighbor_response = :raise
|
253
|
+
|
254
|
+
assert_equal comp.no_neighbor_response, :raise
|
255
|
+
assert_raises(IndexError) { nn_graph = comp.nn_graph }
|
256
|
+
assert_raises(IndexError) { comp.cluster_by_distance(5) }
|
257
|
+
assert_equal nn_graph, nil
|
258
|
+
|
259
|
+
|
260
|
+
# Test :use_all (default)
|
261
|
+
comp = RubyVor::VDDT::Computation.from_points([1,2,3,4,5].map{|xy| RubyVor::Point.new(xy,xy)})
|
262
|
+
comp.no_neighbor_response = :use_all
|
263
|
+
|
264
|
+
assert_equal comp.no_neighbor_response, :use_all
|
265
|
+
assert_nothing_raised { nn_graph = comp.nn_graph }
|
266
|
+
assert_nothing_raised do
|
267
|
+
assert_equal comp.cluster_by_distance(5).map{|a| a.sort}.sort, [[0,1,2,3,4]]
|
268
|
+
end
|
269
|
+
refute_equal nn_graph, nil
|
270
|
+
|
271
|
+
|
272
|
+
# Test :ignore
|
273
|
+
comp = RubyVor::VDDT::Computation.from_points([1,2,3,4,5].map{|xy| RubyVor::Point.new(xy,xy)})
|
274
|
+
comp.no_neighbor_response = :ignore
|
275
|
+
|
276
|
+
assert_equal comp.no_neighbor_response, :ignore
|
277
|
+
assert_nothing_raised { nn_graph = comp.nn_graph }
|
278
|
+
assert_nothing_raised do
|
279
|
+
assert_equal comp.cluster_by_distance(5).map{|a| a.sort}.sort, [[0],[1],[2],[3],[4]]
|
280
|
+
end
|
281
|
+
refute_equal nn_graph, nil
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
def test_cluster_by_size
|
286
|
+
comp = RubyVor::VDDT::Computation.from_points([
|
287
|
+
RubyVor::Point.new(0.25, 0.25), # 0
|
288
|
+
RubyVor::Point.new(1, 0.25), # 1
|
289
|
+
RubyVor::Point.new(0.5, 1), # 2
|
290
|
+
RubyVor::Point.new(5, 5), # 3
|
291
|
+
RubyVor::Point.new(10.25, 10.25), # 4
|
292
|
+
RubyVor::Point.new(13, 9), # 5
|
293
|
+
RubyVor::Point.new(9, 9) # 6
|
294
|
+
])
|
295
|
+
|
296
|
+
sizes = [1, 3, 5, 7]
|
297
|
+
|
298
|
+
computed_sized_clusters = comp.cluster_by_size(sizes)
|
299
|
+
|
300
|
+
# Check that we got clusters for each size requested
|
301
|
+
assert_equal sizes, \
|
302
|
+
computed_sized_clusters.keys.sort
|
303
|
+
|
304
|
+
assert_equal [[0,1,2,3,4,5,6]], \
|
305
|
+
computed_sized_clusters[1].map{|cl| cl.sort}.sort, \
|
306
|
+
'Failed cluster of size 1'
|
307
|
+
|
308
|
+
assert_equal [[0,1,2], [3], [4,5,6]], \
|
309
|
+
computed_sized_clusters[3].map{|cl| cl.sort}.sort
|
310
|
+
|
311
|
+
assert_equal [[0,1,2], [3], [4], [5], [6]], \
|
312
|
+
computed_sized_clusters[5].map{|cl| cl.sort}.sort
|
313
|
+
|
314
|
+
assert_equal [[0], [1], [2], [3], [4], [5], [6]], \
|
315
|
+
computed_sized_clusters[7].sort
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
# A few helper methods
|
320
|
+
#
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
def sample_points
|
325
|
+
if @points.nil?
|
326
|
+
@points = [
|
327
|
+
[1.1,1],
|
328
|
+
[0,0],
|
329
|
+
[1,0],
|
330
|
+
[0,1.1],
|
331
|
+
|
332
|
+
[101.1,101],
|
333
|
+
[100,100],
|
334
|
+
[101,100],
|
335
|
+
[100,101.1],
|
336
|
+
|
337
|
+
[43, 55]
|
338
|
+
].map{|x,y| RubyVor::Point.new(x,y)}
|
339
|
+
end
|
340
|
+
@points
|
341
|
+
end
|
342
|
+
|
343
|
+
def assert_nothing_raised(&b)
|
344
|
+
begin
|
345
|
+
yield
|
346
|
+
rescue Exception => e
|
347
|
+
flunk "#{mu_pp(e)} exception encountered, expected no exceptions"
|
348
|
+
return
|
349
|
+
end
|
350
|
+
pass()
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
MiniTest::Unit.autorun
|