bribera-rubyvor 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.
@@ -1,28 +1,139 @@
1
1
  module RubyVor
2
-
3
- # Voronoi Digram and Delaunay Triangulation namespace.
4
2
  module VDDT
5
-
6
- # Represents a computation from a set of 2-dimensional points
7
3
  class Computation
8
4
  attr_reader :points, :voronoi_diagram_raw, :delaunay_triangulation_raw
9
-
5
+
6
+ # Create a computation from an existing set of raw data.
10
7
  def initialize(points=[], vd_raw=[], dt_raw=[])
8
+ @points = points
11
9
  @voronoi_diagram_raw = vd_raw
12
- @delaunay_triangulation_raw = dt_raw
10
+ @delaunay_triangulation_raw = dt_raw
11
+ @nn_graph = nil
13
12
  end
14
13
 
15
- class << self
14
+ # Uses the nearest-neighbors information encapsulated by the Delaunay triangulation as a seed for clustering:
15
+ # We take the edges (there are O(n) of them, because defined by the triangulation and delete any edge above a certain distance.
16
+ #
17
+ # 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
+ # > 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
+ clusters = []
22
+ nodes = (0..points.length-1).to_a
23
+ visited = []
24
+
25
+ v = 0
26
+ graph = nn_graph.map do |neighbors|
27
+ neighbors = neighbors.reject do |neighbor|
28
+ dist_proc[points[v], points[neighbor]] > max_distance
29
+ end
30
+ v += 1
31
+ neighbors
32
+ end
33
+
34
+ until nodes.empty?
35
+ v = nodes.pop
36
+
37
+ next if visited[v]
38
+
39
+ cluster = []
40
+ visited[v] = true
41
+ cluster.push(v)
42
+
43
+ children = graph[v]
44
+ until children.nil? || children.empty?
45
+ cnode = children.pop
46
+ next if cnode.nil? || visited[cnode]
47
+
48
+ visited[cnode] = true
49
+ cluster.push(cnode)
50
+ children.concat(graph[cnode])
51
+ end
52
+
53
+ clusters.push(cluster)
54
+ end
16
55
 
17
- # Compute the voronoi diagram and delaunay triangulation from a set of points.
18
- #
19
- # This implementation uses Steven Fortune's sweepline algorithm, which runs in O(n log n) time and O(n) space.
20
- # It is limited to 2-dimensional space, therefore it expects to receive an array of objects that respond to 'x' and 'y' methods.
21
- def from_points(p)
22
- # Stub; implemented as C function in voronoi_interface.so
23
- end
56
+ clusters
24
57
  end
25
-
58
+
59
+ # Computes the minimum spanning tree for given points, using the Delaunay triangulation as a seed.
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.
62
+ def minimum_spanning_tree(dist_proc=lambda{|a,b| a.distance_from(b)})
63
+ nodes = []
64
+ q = RubyVor::PriorityQueue.new
65
+ (0..points.length-1).to_a.map do |n|
66
+ q.push({
67
+ :node => n,
68
+ :parent => nil,
69
+ :adjacency_list => nn_graph[n].clone,
70
+ :min_adjacency_list => [],
71
+ :in_q => true
72
+ }, (n == 0) ? 0.0 : Float::MAX)
73
+ end
74
+
75
+ q.data.each do |n|
76
+ n.data[:adjacency_list].map!{|v| q.data[v]}
77
+ nodes.push(n)
78
+ end
79
+
80
+ while latest_addition = q.pop
81
+ latest_addition.data[:in_q] = false
82
+
83
+ if latest_addition.data[:parent]
84
+ latest_addition.data[:parent].data[:min_adjacency_list].push(latest_addition)
85
+ latest_addition.data[:min_adjacency_list].push(latest_addition.data[:parent])
86
+ end
87
+
88
+ latest_addition.data[:adjacency_list].each do |adjacent|
89
+
90
+ if adjacent.data[:in_q]
91
+
92
+ adjacent_distance = dist_proc[ points[latest_addition.data[:node]], points[adjacent.data[:node]] ]
93
+
94
+ if adjacent_distance < adjacent.priority
95
+ adjacent.data[:parent] = latest_addition
96
+ adjacent.priority = adjacent_distance
97
+ q.percolate_up(adjacent.index)
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+
105
+ nodes.map! do |n|
106
+ n.data[:min_adjacency_list].map!{|v| v.data[:node]}
107
+ end
108
+ end
109
+
110
+ def cluster_by_size(sizes=[])
111
+ # TODO
112
+ # * Create a minimum_spanning_tree routine to:
113
+ # 1. compute weights (should be done for us in C?)
114
+ # 2. Use Prim's algorithm for computation
115
+ # * Take MST, and
116
+ # 1. For n in sizes (taken in descending order), delete the n most expensive edges from MST
117
+ # * use a MaxHeap?
118
+ # 2. Determine remaining connectivity using BFS as above?
119
+ # 3. Some other more efficient connectivity test?
120
+ # 4. Return {n1 => cluster, n2 => cluster} for all n.
121
+ end
122
+
123
+ protected
124
+
125
+ def weighted_edges(dist_proc)
126
+ v = 0
127
+ edges = nn_graph.inject({}) do |eh, neighbors|
128
+ neighbors.each do |neighbor|
129
+ k = [v,neighbor].sort
130
+ eh[k] = dist_proc.call(points[v],points[neighbor]) unless eh.has_key?(k)
131
+ end
132
+ v += 1
133
+ eh
134
+ end.to_a
135
+ end
136
+
26
137
  end
27
138
  end
28
139
  end
@@ -5,5 +5,9 @@ module RubyVor
5
5
  def initialize(x=0.0,y=0.0)
6
6
  @x = x; @y = y
7
7
  end
8
+
9
+ def distance_from(p)
10
+ ((self.x - p.x) ** 2 + (self.y - p.y) ** 2) ** 0.5
11
+ end
8
12
  end
9
13
  end
@@ -0,0 +1,60 @@
1
+ module RubyVor
2
+ class PriorityQueue
3
+
4
+ attr_reader :data, :size
5
+
6
+ def initialize(d=[])
7
+ @data = d || []
8
+ @size = d.length
9
+
10
+ heapify()
11
+ end
12
+
13
+ def peek
14
+ @data[0]
15
+ end
16
+
17
+ def pop
18
+ return nil if @size < 1
19
+
20
+ r = @data[0]
21
+
22
+ @data[0] = @data[@size-1]
23
+ @data[0].index = 1
24
+ @data.delete_at(@size-1)
25
+
26
+ @size -= 1
27
+
28
+ percolate_down(1) if @size > 0
29
+
30
+ return r
31
+ end
32
+
33
+ def push(data, priority=Float::MAX)
34
+ @size += 1
35
+ @data[@size - 1] = QueueItem.new(priority, @size - 1, data)
36
+ percolate_up(@size)
37
+ end
38
+
39
+ # Implemented in C
40
+ def reorder_queue;end
41
+
42
+ protected
43
+
44
+ # Implemented in C
45
+ def percolate_up(i);end
46
+
47
+ # Implemented in C
48
+ def percolate_down(i);end
49
+
50
+ class QueueItem
51
+ attr_accessor :priority, :index, :data
52
+ def initialize(p, i, d)
53
+ @priority = p
54
+ @index = i
55
+ @data = d
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module RubyVor
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
data/lib/ruby_vor.rb CHANGED
@@ -3,10 +3,11 @@ $:.unshift File.join(File.dirname(__FILE__), '..', 'ext')
3
3
 
4
4
  require 'ruby_vor/version'
5
5
  require 'ruby_vor/point'
6
+ require 'ruby_vor/priority_queue'
6
7
  require 'ruby_vor/computation'
7
8
 
8
- # Require voronoi_interface.so last to clobber old from_points
9
- require 'voronoi_interface.so'
9
+ # Require ruby_vor.so last to clobber old from_points
10
+ require 'ruby_vor.so'
10
11
 
11
12
  # DOC HERE
12
13
  module RubyVor
data/rubyvor.gemspec CHANGED
@@ -1,29 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  Gem::Specification.new do |s|
2
4
  s.name = %q{rubyvor}
3
- s.version = "0.0.3"
5
+ s.version = "0.0.4"
4
6
 
5
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
8
  s.authors = ["Brendan Ribera"]
7
- s.date = %q{2008-12-03}
9
+ s.date = %q{2008-12-10}
8
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.}
9
11
  s.email = ["brendan.ribera+rubyvor@gmail.com"]
10
12
  s.extensions = ["ext/extconf.rb"]
11
13
  s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
12
- s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "ext/Doc", "ext/edgelist.c", "ext/extconf.rb", "ext/geometry.c", "ext/heap.c", "ext/memory.c", "ext/output.c", "ext/vdefs.h", "ext/voronoi.c", "ext/voronoi_interface.c", "lib/ruby_vor.rb", "lib/ruby_vor/computation.rb", "lib/ruby_vor/point.rb", "lib/ruby_vor/version.rb", "rubyvor.gemspec", "test/test_voronoi_interface.rb"]
14
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "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_cPriorityQueue.c", "ext/ruby_vor.c", "ext/ruby_vor.h", "ext/vdefs.h", "ext/voronoi.c", "ext/voronoi_interface.c", "lib/ruby_vor.rb", "lib/ruby_vor/computation.rb", "lib/ruby_vor/point.rb", "lib/ruby_vor/priority_queue.rb", "lib/ruby_vor/version.rb", "rubyvor.gemspec", "test/test_computation.rb", "test/test_priority_queue.rb", "test/test_voronoi_interface.rb"]
13
15
  s.has_rdoc = true
14
16
  s.homepage = %q{http://github.com/bribera/rubyvor}
15
17
  s.rdoc_options = ["--main", "README.txt"]
16
18
  s.require_paths = ["lib", "ext"]
17
19
  s.rubyforge_project = %q{rubyvor}
18
- s.rubygems_version = %q{1.2.0}
20
+ s.rubygems_version = %q{1.3.1}
19
21
  s.summary = %q{RubyVor provides efficient computation of Voronoi diagrams and Delaunay triangulation for a set of Ruby points}
20
- s.test_files = ["test/test_voronoi_interface.rb"]
22
+ s.test_files = ["test/test_voronoi_interface.rb", "test/test_computation.rb", "test/test_priority_queue.rb"]
21
23
 
22
24
  if s.respond_to? :specification_version then
23
25
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
26
  s.specification_version = 2
25
27
 
26
- if current_version >= 3 then
28
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
29
  s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
28
30
  else
29
31
  s.add_dependency(%q<hoe>, [">= 1.8.2"])
@@ -0,0 +1,175 @@
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_nn_graph
13
+ comp = RubyVor::VDDT::Computation.from_points(sample_points)
14
+
15
+ # based on this expected delaunay trianglulation:
16
+ # 3 2 1
17
+ # 3 0 2
18
+ # 8 2 0
19
+ # 3 8 0
20
+ # 5 6 8
21
+ # 7 6 5
22
+ # 7 4 6
23
+ # 6 2 8
24
+ # 8 7 5
25
+
26
+ expected_graph = [
27
+ [2, 3, 8], # 0
28
+ [2, 3], # 1
29
+ [0, 1, 3, 6, 8], # 2
30
+ [0, 1, 2, 8], # 3
31
+ [6, 7], # 4
32
+ [6, 7, 8], # 5
33
+ [2, 4, 5, 7, 8], # 6
34
+ [4, 5, 6, 8], # 7
35
+ [0, 2, 3, 5, 6, 7], # 8
36
+ ]
37
+
38
+ assert_equal comp.nn_graph.map{|v| v.sort}.sort, \
39
+ expected_graph.map{|v| v.sort}.sort
40
+ end
41
+
42
+ def test_simple_mst
43
+ points = [
44
+ RubyVor::Point.new(0,0),
45
+ RubyVor::Point.new(1.0,1.1),
46
+ RubyVor::Point.new(4.9,3.1),
47
+ ]
48
+
49
+ comp = RubyVor::VDDT::Computation.from_points(points)
50
+
51
+ expected_mst = [
52
+ [1],
53
+ [0,2],
54
+ [1]
55
+ ]
56
+
57
+ assert_equal expected_mst.map{|v| v.sort}.sort, \
58
+ comp.minimum_spanning_tree.map{|v| v.sort}.sort
59
+ end
60
+
61
+ def test_advanced_mst
62
+ # Test tree taken from an example SVG included in the Wikipedia article on Euclidean minimum spanning trees:
63
+ # http://en.wikipedia.org/wiki/Image:Euclidean_minimum_spanning_tree.svg
64
+
65
+ points = [
66
+ RubyVor::Point.new(155.692, 99.783), # 0
67
+ RubyVor::Point.new(162.285, 120.245), # 1
68
+ RubyVor::Point.new(143.692, 129.893), # 2
69
+ RubyVor::Point.new(150.128, 167.924), # 3
70
+ RubyVor::Point.new(137.617, 188.953), # 4
71
+ RubyVor::Point.new(193.467, 119.345), # 5
72
+ RubyVor::Point.new(196.754, 88.47), # 6
73
+ RubyVor::Point.new(241.629, 70.845), # 7
74
+ RubyVor::Point.new(262.692, 59.97), # 8
75
+ RubyVor::Point.new(269.629, 63.158), # 9
76
+ RubyVor::Point.new(247.257, 200.669), # 10
77
+ RubyVor::Point.new(231.28, 245.974), # 11
78
+ RubyVor::Point.new(268.002, 264.693), # 12
79
+ RubyVor::Point.new(155.442, 64.473), # 13
80
+ RubyVor::Point.new(198.598, 31.804), # 14
81
+ RubyVor::Point.new(216.816, 3.513), # 15
82
+ RubyVor::Point.new(89.624, 27.344), # 16
83
+ RubyVor::Point.new(67.925, 56.999), # 17
84
+ RubyVor::Point.new(77.328, 93.404), # 18
85
+ RubyVor::Point.new(65.525, 158.783), # 19
86
+ RubyVor::Point.new(63.525,170.783), # 20
87
+ RubyVor::Point.new(15.192, 192.783), # 21
88
+ RubyVor::Point.new(7.025, 236.949), # 22
89
+ RubyVor::Point.new(40.525, 262.949), # 23
90
+ RubyVor::Point.new(61.692, 225.95) # 24
91
+ ]
92
+
93
+ comp = RubyVor::VDDT::Computation.from_points(points)
94
+
95
+ expected_mst = [
96
+ [1,13], # 0
97
+ [0,2,5], # 1
98
+ [1,3], # 2
99
+ [2,4], # 3
100
+ [3], # 4
101
+ [1,6,10], # 5
102
+ [5,7], # 6
103
+ [6,8], # 7
104
+ [7,9], # 8
105
+ [8], # 9
106
+ [5,11], # 10
107
+ [10,12], # 11
108
+ [11], # 12
109
+ [0,14,16], # 13
110
+ [13,15], # 14
111
+ [14], # 15
112
+ [13,17], # 16
113
+ [16,18], # 17
114
+ [17,19], # 18
115
+ [18,20], # 19
116
+ [19,21], # 20
117
+ [20,22], # 21
118
+ [21,23], # 22
119
+ [22,24], # 23
120
+ [23] # 24
121
+ ]
122
+ assert_equal expected_mst.map{|v| v.sort}.sort, \
123
+ comp.minimum_spanning_tree.map{|v| v.sort}.sort
124
+ end
125
+
126
+ def test_cluster_by_distance
127
+ comp = RubyVor::VDDT::Computation.from_points(sample_points)
128
+
129
+ expected_clusters = [
130
+ [0,1,2,3],
131
+ [8],
132
+ [4,5,6,7]
133
+ ]
134
+
135
+ # We want to ensure that the nn_graph isn't modified by this call
136
+ original_nn_graph = comp.nn_graph.map{|v| v.clone}
137
+
138
+ # Compute clusters within a distance of 10
139
+ computed_clusters = comp.cluster_by_distance(10)
140
+
141
+ assert_equal expected_clusters.map{|cl| cl.sort}.sort, \
142
+ computed_clusters.map{|cl| cl.sort}.sort
143
+
144
+ assert_equal original_nn_graph, \
145
+ comp.nn_graph
146
+
147
+ end
148
+
149
+ #
150
+ # A few helper methods
151
+ #
152
+
153
+ private
154
+
155
+ def sample_points
156
+ if @points.nil?
157
+ @points = [
158
+ [1.1,1],
159
+ [0,0],
160
+ [1,0],
161
+ [0,1.1],
162
+
163
+ [101.1,101],
164
+ [100,100],
165
+ [101,100],
166
+ [100,101.1],
167
+
168
+ [43, 55]
169
+ ].map{|x,y| RubyVor::Point.new(x,y)}
170
+ end
171
+ @points
172
+ end
173
+ end
174
+
175
+ MiniTest::Unit.autorun
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'minitest/unit'
3
+ require File.dirname(__FILE__) + '/../lib/ruby_vor'
4
+
5
+ class TestPriorityQueue < MiniTest::Unit::TestCase
6
+
7
+ def test_heap_order_property
8
+ # min heap by default
9
+ q = RubyVor::PriorityQueue.new
10
+
11
+ items = [1,2,3,4,5,6,99,4,-20,101,5412,2,-1,-1,-1,33.0,0,55,7,12321,123.123,-123.123,0,0,0]
12
+ items.each{|i| q.push(i,i)}
13
+
14
+ items.sort!
15
+ idx = 0
16
+
17
+ # Test peek
18
+ assert_equal q.peek.data, items[0]
19
+
20
+ while min = q.pop
21
+ # Test pop
22
+ assert_equal min.data, items[idx]
23
+
24
+ # Test peek
25
+ assert_equal q.peek.data, items[idx + 1] if idx + 1 < items.length
26
+
27
+ idx += 1
28
+ end
29
+ end
30
+
31
+ def test_heapify
32
+ q = RubyVor::PriorityQueue.new
33
+
34
+ ([10] * 10).each{|x| q.push(x,x)}
35
+
36
+ q.data[3] = RubyVor::PriorityQueue::QueueItem.new(-34, 3, -34)
37
+ q.data[4] = RubyVor::PriorityQueue::QueueItem.new(1, 4, 1)
38
+ q.data[5] = RubyVor::PriorityQueue::QueueItem.new(100, 5, 100)
39
+ q.data[7] = RubyVor::PriorityQueue::QueueItem.new(0.9, 7, 0.9)
40
+ q.data.sort!{|a,b| rand(3)-1}
41
+
42
+ q.heapify()
43
+
44
+ assert_equal(-34, q.pop.data)
45
+ assert_equal 0.9, q.pop.data
46
+ assert_equal 1, q.pop.data
47
+ assert_equal 10, q.pop.data
48
+ assert_equal 10, q.pop.data
49
+ assert_equal 10, q.pop.data
50
+ assert_equal 10, q.pop.data
51
+ assert_equal 10, q.pop.data
52
+ assert_equal 10, q.pop.data
53
+ assert_equal 100, q.pop.data
54
+ end
55
+
56
+ end
@@ -2,14 +2,16 @@ require 'rubygems'
2
2
  require 'minitest/unit'
3
3
  require File.dirname(__FILE__) + '/../lib/ruby_vor'
4
4
 
5
+
5
6
  class TestVoronoiInterface < MiniTest::Unit::TestCase
6
7
 
7
- MAX_DELTA = 0.000001
8
-
9
8
  def initialize(*args)
10
9
  @points = @trianglulation_raw = @diagram_raw = nil
11
10
  super(*args)
12
11
  end
12
+
13
+
14
+ MAX_DELTA = 0.000001
13
15
 
14
16
  def test_diagram_correct
15
17
  # Perform the computation.
@@ -44,7 +46,7 @@ class TestVoronoiInterface < MiniTest::Unit::TestCase
44
46
 
45
47
  assert_equal example_triangulation_raw, comp.delaunay_triangulation_raw
46
48
  end
47
-
49
+
48
50
 
49
51
 
50
52
  #
@@ -153,6 +155,7 @@ class TestVoronoiInterface < MiniTest::Unit::TestCase
153
155
  end
154
156
  @trianglulation_raw
155
157
  end
158
+
156
159
  end
157
160
 
158
161
  MiniTest::Unit.autorun
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.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brendan Ribera
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-03 00:00:00 -08:00
12
+ date: 2008-12-10 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -44,14 +44,21 @@ files:
44
44
  - ext/heap.c
45
45
  - ext/memory.c
46
46
  - ext/output.c
47
+ - ext/rb_cComputation.c
48
+ - ext/rb_cPriorityQueue.c
49
+ - ext/ruby_vor.c
50
+ - ext/ruby_vor.h
47
51
  - ext/vdefs.h
48
52
  - ext/voronoi.c
49
53
  - ext/voronoi_interface.c
50
54
  - lib/ruby_vor.rb
51
55
  - lib/ruby_vor/computation.rb
52
56
  - lib/ruby_vor/point.rb
57
+ - lib/ruby_vor/priority_queue.rb
53
58
  - lib/ruby_vor/version.rb
54
59
  - rubyvor.gemspec
60
+ - test/test_computation.rb
61
+ - test/test_priority_queue.rb
55
62
  - test/test_voronoi_interface.rb
56
63
  has_rdoc: true
57
64
  homepage: http://github.com/bribera/rubyvor
@@ -83,3 +90,5 @@ specification_version: 2
83
90
  summary: RubyVor provides efficient computation of Voronoi diagrams and Delaunay triangulation for a set of Ruby points
84
91
  test_files:
85
92
  - test/test_voronoi_interface.rb
93
+ - test/test_computation.rb
94
+ - test/test_priority_queue.rb