bribera-rubyvor 0.0.5 → 0.0.6

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.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 0.0.6 / 2008-12-11
2
+
3
+ * Implementation of cluster_by_size to partition points into N clusters.
4
+
1
5
  === 0.0.5 / 2008-12-10
2
6
 
3
7
  * Beginnings of clustering and useful graph algorithms.
data/ext/ruby_vor.c CHANGED
@@ -94,3 +94,6 @@ VALUE RubyVor_heapify(VALUE);
94
94
  * Compute the Euclidean distance between two points.
95
95
  */
96
96
  VALUE RubyVor_distance_from(VALUE, VALUE);
97
+
98
+
99
+ // keep comment so RDOC will find the last method definition
@@ -3,6 +3,8 @@ module RubyVor
3
3
  class Computation
4
4
  attr_reader :points, :voronoi_diagram_raw, :delaunay_triangulation_raw
5
5
 
6
+ DIST_PROC = lambda{|a,b| a.distance_from(b)}
7
+
6
8
  # Create a computation from an existing set of raw data.
7
9
  def initialize(points=[], vd_raw=[], dt_raw=[])
8
10
  @points = points
@@ -16,8 +18,7 @@ module RubyVor
16
18
  #
17
19
  # This method allows the caller to pass in a lambda for customizing distance calculations. For instance, to use a GeoRuby::SimpleFeatures::Point, one would:
18
20
  # > cluster_by_distance(50 lambda{|a,b| a.spherical_distance(b, 3958.754)}) # this rejects edges greater than 50 miles, using spherical distance as a measure
19
- def cluster_by_distance(max_distance, dist_proc=lambda{|a,b| a.distance_from(b)})
20
-
21
+ def cluster_by_distance(max_distance, dist_proc=DIST_PROC)
21
22
  clusters = []
22
23
  nodes = (0..points.length-1).to_a
23
24
  visited = [false] * points.length
@@ -58,7 +59,7 @@ module RubyVor
58
59
  # Computes the minimum spanning tree for given points, using the Delaunay triangulation as a seed.
59
60
  #
60
61
  # For points on a Euclidean plane, the MST is always comprised of a subset of the edges in a Delaunay triangulation. This makes computation of the tree very efficient: simply compute the Delaunay triangulation, and then run Prim's algorithm on the resulting edges.
61
- def minimum_spanning_tree(dist_proc=lambda{|a,b| a.distance_from(b)})
62
+ def minimum_spanning_tree(dist_proc=DIST_PROC)
62
63
  nodes = []
63
64
  q = RubyVor::PriorityQueue.new
64
65
  (0..points.length-1).to_a.map do |n|
@@ -112,16 +113,16 @@ module RubyVor
112
113
 
113
114
  end
114
115
 
115
-
116
- nodes.map! do |n|
117
- n.data[3].inject({}) do |h,v|
118
- h[v.data[0]] = v.priority
119
- h
116
+ nodes.inject({}) do |h,adjacency_list|
117
+ v = adjacency_list.data[0]
118
+ adjacency_list.data[3].each do |node|
119
+ h[ (v < node.data[0]) ? [v,node.data[0]] : [node.data[0],v] ] ||= node.priority
120
120
  end
121
+ h
121
122
  end
122
123
  end
123
124
 
124
- def cluster_by_size(sizes=[])
125
+ def cluster_by_size(sizes=[], dist_proc=DIST_PROC)
125
126
  # TODO
126
127
  # * (DONE) Create a minimum_spanning_tree routine to:
127
128
  # 1. (DONE) compute weights (should be done for us in C?)
@@ -133,22 +134,60 @@ module RubyVor
133
134
  # 2. Determine remaining connectivity using BFS as above?
134
135
  # 3. Some other more efficient connectivity test?
135
136
  # 4. Return {n1 => cluster, n2 => cluster} for all n.
136
- end
137
+
138
+ sized_clusters = sizes.inject({}) {|h,s| h[s] = []; h}
139
+
140
+
141
+ mst = minimum_spanning_tree(dist_proc).to_a
142
+ mst.sort!{|a,b|a.last <=> b.last}
143
+
144
+ sizes = sizes.sort
145
+ last_size = 0
146
+ while current_size = sizes.shift
147
+ current_size -= 1
148
+
149
+ # Remove edge count delta
150
+ delta = current_size - last_size
151
+ mst.slice!(-delta,delta)
152
+
153
+ visited = [nil] * points.length
137
154
 
138
- protected
139
155
 
140
- def weighted_edges(dist_proc)
141
- v = 0
142
- edges = nn_graph.inject({}) do |eh, neighbors|
143
- neighbors.each do |neighbor|
144
- k = [v,neighbor].sort
145
- eh[k] = dist_proc.call(points[v],points[neighbor]) unless eh.has_key?(k)
156
+
157
+ for edge in mst
158
+ i, j = edge.first
159
+
160
+ next if !visited[i].nil? && visited[i] == visited[j]
161
+
162
+ if visited[i] && visited[j]
163
+ visited[i].concat(visited[j])
164
+ visited[i].uniq!
165
+ visited[j].each{|v| visited[v] = visited[i]}
166
+ visited[j] = visited[i]
167
+ else
168
+ cluster = visited[i] || visited[j] || []
169
+ cluster.push(i) unless visited[i]
170
+ cluster.push(j) unless visited[j]
171
+ visited[i] = visited[j] = cluster
172
+ end
173
+ end
174
+
175
+
176
+ visited.each_with_index do |visits, v|
177
+ if visits.nil?
178
+ visited[v] = [v]
179
+ end
146
180
  end
147
- v += 1
148
- eh
149
- end.to_a
181
+
182
+ sized_clusters[current_size + 1] = visited.uniq
183
+
184
+ last_size = current_size
185
+ end
186
+
187
+ sized_clusters
150
188
  end
151
189
 
190
+
152
191
  end
153
192
  end
154
193
  end
@@ -2,8 +2,33 @@ module RubyVor
2
2
  class PriorityQueue
3
3
 
4
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
5
30
 
6
- def initialize()
31
+ def initialize
7
32
  @data = []
8
33
  @size = 0
9
34
  heapify()
@@ -1,3 +1,3 @@
1
1
  module RubyVor
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.6'
3
3
  end
data/rubyvor.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{rubyvor}
5
- s.version = "0.0.5"
5
+ s.version = "0.0.6"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Brendan Ribera"]
@@ -47,17 +47,15 @@ class TestComputation < MiniTest::Unit::TestCase
47
47
  ]
48
48
 
49
49
  comp = RubyVor::VDDT::Computation.from_points(points)
50
-
51
50
  expected_mst = [
52
- [1],
53
- [0,2],
54
- [1]
51
+ [0,1],
52
+ [1,2]
55
53
  ]
56
54
  computed_mst = comp.minimum_spanning_tree
57
55
 
58
56
  # Assert nodes are correct
59
- assert_equal expected_mst.map{|v| v.sort}.sort, \
60
- computed_mst.map{|v| v.keys.sort}.sort
57
+ assert_equal expected_mst.sort, \
58
+ computed_mst.keys.sort
61
59
  end
62
60
 
63
61
  def test_advanced_mst
@@ -95,36 +93,35 @@ class TestComputation < MiniTest::Unit::TestCase
95
93
  comp = RubyVor::VDDT::Computation.from_points(points)
96
94
 
97
95
  expected_mst = [
98
- [1,13], # 0
99
- [0,2,5], # 1
100
- [1,3], # 2
101
- [2,4], # 3
102
- [3], # 4
103
- [1,6,10], # 5
104
- [5,7], # 6
105
- [6,8], # 7
106
- [7,9], # 8
107
- [8], # 9
108
- [5,11], # 10
109
- [10,12], # 11
110
- [11], # 12
111
- [0,14,16], # 13
112
- [13,15], # 14
113
- [14], # 15
114
- [13,17], # 16
115
- [16,18], # 17
116
- [17,19], # 18
117
- [18,20], # 19
118
- [19,21], # 20
119
- [20,22], # 21
120
- [21,23], # 22
121
- [22,24], # 23
122
- [23] # 24
96
+ [0,1],
97
+ [0,13],
98
+ [1,2],
99
+ [1,5],
100
+ [2,3],
101
+ [3,4],
102
+ [5,6],
103
+ [5,10],
104
+ [6,7],
105
+ [7,8],
106
+ [8,9],
107
+ [10,11],
108
+ [11,12],
109
+ [13,14],
110
+ [13,16],
111
+ [14,15],
112
+ [16,17],
113
+ [17,18],
114
+ [18,19],
115
+ [19,20],
116
+ [20,21],
117
+ [21,22],
118
+ [22,23],
119
+ [23,24]
123
120
  ]
124
121
  computed_mst = comp.minimum_spanning_tree
125
122
 
126
- assert_equal expected_mst.map{|v| v.sort}.sort, \
127
- computed_mst.map{|v| v.keys.sort}.sort
123
+ assert_equal expected_mst.sort, \
124
+ computed_mst.keys.sort
128
125
  end
129
126
 
130
127
  def test_cluster_by_distance
@@ -150,6 +147,16 @@ class TestComputation < MiniTest::Unit::TestCase
150
147
 
151
148
  end
152
149
 
150
+
151
+ def test_duplicate_points
152
+ comp = RubyVor::VDDT::Computation.from_points([RubyVor::Point.new(1,1), RubyVor::Point.new(1,1), RubyVor::Point.new(2,3), RubyVor::Point.new(1,1), RubyVor::Point.new(4,0)])
153
+ refute_equal comp.nn_graph, nil
154
+ refute_equal comp.minimum_spanning_tree, nil
155
+ refute_equal comp.cluster_by_distance(3), nil
156
+ refute_equal comp.cluster_by_size([2]), nil
157
+ end
158
+
159
+
153
160
  def test_bad_data
154
161
  assert_raises TypeError do
155
162
  comp = RubyVor::VDDT::Computation.from_points([RubyVor::Point.new(1,1), RubyVor::Point.new(21,3), RubyVor::Point.new(2,:s)])
@@ -173,6 +180,39 @@ class TestComputation < MiniTest::Unit::TestCase
173
180
  end
174
181
  end
175
182
 
183
+
184
+ def test_cluster_by_size
185
+ comp = RubyVor::VDDT::Computation.from_points([
186
+ RubyVor::Point.new(0.25, 0.25),
187
+ RubyVor::Point.new(1, 0.25),
188
+ RubyVor::Point.new(0.5, 1),
189
+ RubyVor::Point.new(5, 5),
190
+ RubyVor::Point.new(10.25, 10.25),
191
+ RubyVor::Point.new(13, 9),
192
+ RubyVor::Point.new(9, 9)
193
+ ])
194
+
195
+ sizes = [1, 3, 5, 7]
196
+
197
+ computed_sized_clusters = comp.cluster_by_size(sizes)
198
+
199
+ # Check that we got clusters for each size requested
200
+ assert_equal sizes, \
201
+ computed_sized_clusters.keys.sort
202
+
203
+ assert_equal [[0,1,2,3,4,5,6]], \
204
+ computed_sized_clusters[1].map{|cl| cl.sort}.sort
205
+
206
+ assert_equal [[0,1,2], [3], [4,5,6]], \
207
+ computed_sized_clusters[3].map{|cl| cl.sort}.sort
208
+
209
+ assert_equal [[0,1,2], [3], [4], [5], [6]], \
210
+ computed_sized_clusters[5].map{|cl| cl.sort}.sort
211
+
212
+ assert_equal [[0], [1], [2], [3], [4], [5], [6]], \
213
+ computed_sized_clusters[7].sort
214
+ end
215
+
176
216
  #
177
217
  # A few helper methods
178
218
  #
@@ -44,7 +44,7 @@ class TestPriorityQueue < MiniTest::Unit::TestCase
44
44
 
45
45
  old_n = -1.0 * Float::MAX
46
46
  while n = q.pop
47
- assert n.priority >= old_n
47
+ assert n.priority >= old_n, 'Heap-order property violated'
48
48
  old_n = n.priority
49
49
  end
50
50
  end
@@ -96,6 +96,22 @@ class TestPriorityQueue < MiniTest::Unit::TestCase
96
96
  end
97
97
  end
98
98
 
99
+ def test_build_queue
100
+ n = 100
101
+
102
+ q = RubyVor::PriorityQueue.build_queue(n) do |queue_item|
103
+ queue_item.priority = rand * 10000.0 - 5000.0
104
+ end
105
+
106
+ assert_equal n, q.data.length
107
+ assert_equal n, q.size
108
+
109
+ old_n = -1.0 * Float::MAX
110
+ while n = q.pop
111
+ assert n.priority >= old_n, 'Heap-order property violated'
112
+ old_n = n.priority
113
+ end
114
+ end
99
115
 
100
116
  private
101
117
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bribera-rubyvor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brendan Ribera