File Coverage

blib/lib/Math/Vector/Real/Farthest.pm
Criterion Covered Total %
statement 167 229 72.9
branch 58 96 60.4
condition 3 6 50.0
subroutine 16 19 84.2
pod 3 3 100.0
total 247 353 69.9


line stmt bran cond sub pod time code
1             package Math::Vector::Real::Farthest;
2              
3             our $VERSION = '0.02';
4              
5 1     1   18547 use strict;
  1         3  
  1         45  
6 1     1   6 use warnings;
  1         3  
  1         36  
7              
8 1     1   6 use Math::Vector::Real;
  1         6  
  1         55  
9 1     1   1093 use Sort::Key::Top qw(nslotpartref);
  1         1950  
  1         211  
10 1     1   877 use Math::nSphere qw(nsphere_volumen);
  1         591  
  1         63  
11 1     1   6 use Carp;
  1         2  
  1         106  
12              
13             our $optimization_core = 1;
14             our $optimization_convex_hull = 0;
15             our $threshold_brute_force = 16;
16             our $O = 0;
17              
18 1     1   6 use constant _c0 => 0;
  1         2  
  1         81  
19 1     1   6 use constant _c1 => 1;
  1         2  
  1         42  
20 1     1   5 use constant _n => 2;
  1         2  
  1         50  
21 1     1   5 use constant _vs => 3;
  1         1  
  1         41  
22 1     1   5 use constant _s0 => 4;
  1         2  
  1         56  
23 1     1   5 use constant _s1 => 5;
  1         2  
  1         2502  
24              
25             sub _find_brute_force {
26 126     126   220 my $best_d2 = 0;
27 126         298 my @best_vs = ($_[0], $_[0]);
28              
29 126         348 for my $i (0..$#_) {
30 10642         24789 for my $j ($i + 1..$#_) {
31 1836314         2422415 my $vi = $_[$i];
32 1836314         2038411 my $vj = $_[$j];
33 1836314         3714456 my $d2 = Math::Vector::Real::dist2($vi, $vj);
34 1836314 100       44046019 if ($d2 > $best_d2) {
35 837         955 $best_d2 = $d2;
36 837         2348 @best_vs = ($vi, $vj);
37             }
38             }
39             }
40              
41 126 50       911 wantarray ? ($best_d2, map V(@$_), @best_vs) : $best_d2;
42             }
43              
44             sub _find_1d {
45 7     7   20 my $max = $_[0][0];
46 7         15 my $min = $max;
47 7 100       26 shift if @_ & 1;
48 7         22 while (@_) {
49 233         322 my $a = shift->[0];
50 233         263 my $b = shift->[0];
51 233 100       512 if ($a > $b) {
52 132 100       338 $max = $a if $a > $max;
53 132 100       346 $min = $b if $b < $min;
54             }
55             else {
56 101 100       174 $max = $b if $b > $max;
57 101 100       269 $min = $a if $a < $min;
58             }
59             }
60 7         14 my $d = $max - $min;
61 7         13 my $d2 = $d * $d;
62 7 50       38 wantarray ? ($d2, V($min), V($max)) : $d2;
63             }
64              
65              
66             my $skr_loaded;
67             sub _find_2d_convex_hull {
68              
69 0 0   0   0 $skr_loaded++ or require Sort::Key::Radix;
70 0     0   0 my @p = &Sort::Key::Radix::nkeysort(sub { $_->[0] }, @_);
  0         0  
71              
72             # use GD;
73             # my $size = 1024;
74             # my $im = new GD::Image($size, $size);
75             # my $white = $im->colorAllocate(255,255,255);
76             # my $black = $im->colorAllocate(0,0,0);
77             # my $blue = $im->colorAllocate(0,0,255);
78             # my $red = $im->colorAllocate(255,0,0);
79             # my $green = $im->colorAllocate(0,255,0);
80             # my $gray = $im->colorAllocate(200, 200, 200);
81             # my $yellow = $im->colorAllocate(255, 255, 0);
82             # $im->rectangle(0, 0, $size, $size, $white);
83             # $im->filledEllipse($_->[0] * $size, $_->[1] * $size, 2, 2, $black) for @p;
84              
85 0         0 my (@u, @l);
86 0         0 my $i = 0;
87 0         0 while ($i < @p) {
88 0         0 my $iu = my $il = $i;
89 0         0 my ($x, $yu) = @{$p[$i]};
  0         0  
90 0         0 my $yl = $yu;
91             # search for upper and lower Y for the current X
92 0   0     0 while (++$i < @p and $p[$i][0] == $x) {
93 0         0 my $y = $p[$i][1];
94 0 0       0 if ($y < $yl) {
    0          
95 0         0 $il = $i;
96 0         0 $yl = $y;
97             }
98             elsif ($y > $yu) {
99 0         0 $iu = $i;
100 0         0 $yu = $y;
101             }
102             }
103 0         0 while (@l >= 2) {
104 0         0 my ($ox, $oy) = @{$l[-2]};
  0         0  
105 0 0       0 last if ($l[-1][1] - $oy) * ($x - $ox) < ($yl - $oy) * ($l[-1][0] - $ox);
106 0         0 pop @l;
107             }
108 0         0 push @l, $p[$il];
109 0         0 while (@u >= 2) {
110 0         0 my ($ox, $oy) = @{$u[-2]};
  0         0  
111 0 0       0 last if ($u[-1][1] - $oy) * ($x - $ox) > ($yu - $oy) * ($u[-1][0] - $ox);
112 0         0 pop @u;
113             }
114 0         0 push @u, $p[$iu];
115             }
116              
117             # $im->filledEllipse($_->[0] * $size, $_->[1] * $size, 12, 12, $blue) for @u;
118             # $im->filledEllipse($_->[0] * $size, $_->[1] * $size, 12, 12, $green) for @l;
119              
120 0         0 my $u = $l[-1];
121 0         0 my $l = $u[0];
122 0         0 my $d = V(0, 1);
123 0 0       0 pop @u if $u->[1] == $u[-1][1];
124 0 0       0 shift @l if $l->[1] == $l[0][1];
125 0         0 my $best_d2 = 0;
126 0         0 my @best_vs = ($u, $u);
127 0         0 while (1) {
128             # print "u: $u, l: $l\n";
129             # $im->line(map($_ * $size, @$u, @$l), $yellow);
130 0         0 my $d2 = Math::Vector::Real::dist2($u, $l);
131 0 0       0 if ($d2 > $best_d2) {
132 0         0 $best_d2 = $d2;
133 0         0 @best_vs = ($u, $l);
134             }
135              
136 0 0       0 if (not @u) {
    0          
137 0 0       0 last unless @l;
138 0         0 $l = shift @l;
139             }
140             elsif(not @l) {
141 0         0 $u = pop @u;
142             }
143             else {
144 0         0 my $du = Math::Vector::Real::versor($u[-1] - $u);
145 0         0 my $dl = Math::Vector::Real::versor($l - $l[0]);
146 0 0       0 if ($du * $d > $dl * $d) {
147 0         0 $u = pop @u;
148 0         0 $d = $du;
149             }
150             else {
151 0         0 $l = shift @l;
152 0         0 $d = $dl;
153             }
154             }
155             }
156              
157              
158             #my ($alt_d2, @alt_vs) = _find_brute_force(@_);
159             #if ($alt_d2 != $best_d2) {
160             #$im->filledEllipse($_->[0] * $size, $_->[1] * $size, 8, 8, $gray) for @best_vs;
161             #$im->filledEllipse($_->[0] * $size, $_->[1] * $size, 4, 4, $red) for @alt_vs;
162             #open my $fh, '>', "frame.png";
163             #print $fh $im->png;
164             #exit -1;
165             #}
166 0 0       0 wantarray ? ($best_d2, map V(@$_), @best_vs) : $best_d2;
167             }
168              
169             sub find {
170 100     100 1 5292254 shift;
171              
172 100 50       435 return unless @_;
173 100         148 my $dim = @{$_[0]};
  100         345  
174              
175 100         190 my @vs;
176 100 100       345 if (@_ <= 10) {
177 33 100       708 if (@_ <= 2) {
178             # shortcut for sets of 1 and 2 elements
179 5         19 my @best_vs = @_[0, -1];
180 5         18 my $best_d2 = Math::Vector::Real::dist2(@best_vs);
181 5 50       123 return (wantarray ? ($best_d2, map V(@$_), @best_vs) : $best_d2);
182             }
183 28 100       227 $dim > 1 and goto &_find_brute_force;
184             }
185              
186 69 100       214 if ($dim <= 2) {
187             # use specialized algorithm for 1D
188 12 100       75 $dim == 1 and goto &_find_1d;
189              
190             # use specialized algorithm for 2D
191 5 50       21 goto &_find_2d_convex_hull if $optimization_convex_hull;
192             }
193              
194 62         98 my ($best_d2, @best_vs);
195             ### $O++;
196 62         574 my ($c0, $c1) = Math::Vector::Real->box(@_);
197 62         148959 my $diag = $c1 - $c0;
198 62         1971 my $max_comp = $diag->max_component;
199 62         1265 $best_d2 = $max_comp * $max_comp;
200 62 50       188 if ($best_d2) {
201              
202 62         136 my $vs0;
203              
204 62 50       196 if ($optimization_core) {
205             # There is a place in the center of the box which is
206             # guaranteed to not contain any of the target vectors. We
207             # calculate its aproximate hyper-volumen and if it is at least
208             # 10% of that of the box we filter out the points within. This
209             # heuristic works well when the vectors are evenly
210             # distributed.
211              
212             # Benchmarks show that this optimization can provide a
213             # huge gain in some cases while non impacting performance
214             # on the rest.
215              
216 62         510 my $nellipsoid_volumen = nsphere_volumen(scalar(@$diag));
217 62         626 my $ncube_volumen = 1;
218 62         239 my $half = 0.5 * $diag;
219 62         886 my $t2 = $half->abs2 - $best_d2;
220 62         929 for my $ix (0..$#$half) {
221 369         470 my $y = $half->[$ix];
222 369         457 my $y2 = $y * $y;
223 369 100       807 if ($t2 + 3 * $y2 > 0) {
224 367 100       1976 if ($y2 > $t2) {
225 104         177 $y = sqrt($y2 - $t2) - $y;
226             }
227             else {
228 263         328 $y = 0;
229             }
230             }
231 369         444 $nellipsoid_volumen *= $y;
232 369         1310 $ncube_volumen *= $diag->[$ix];
233             }
234              
235             # we don't want to discard points that are at a distance
236             # exactly equal to the bigest box side, so we apply a small
237             # correction factor here:
238 62         160 $best_d2 *= 0.99999;
239              
240 62 100       177 if ($nellipsoid_volumen > $ncube_volumen * 0.1) {
241             # we aim at discarding at least 10% of the points
242 5         23 my $zero = 0.5 * ($c0 + $c1);
243 5         119 my $corner = $c0 - $zero;
244 5         81 $vs0 = [grep { Math::Vector::Real::dist2($corner, ($_ - $zero)->first_orthant_reflection) > $best_d2 } @_];
  493         15471  
245             }
246             else {
247 57         381 $vs0 = \@_;
248             }
249             }
250             else {
251 0         0 $best_d2 *= 0.99999;
252 0         0 $vs0 = \@_;
253             }
254              
255 62         444 my @d2 = $diag->abs2;
256 62         772 my @a = [$c0, $c1, scalar(@$vs0), $vs0];
257 62         135 my @b = undef;
258              
259 62         170 while (@d2) {
260 14132         22905 my $d2 = pop @d2;
261 14132 100       33237 $d2 > $best_d2 or last;
262             ### $O++;
263 14116         24375 my $a = pop @a;
264 14116         25260 my $b = pop @b;
265 14116 100 100     80911 ($a, $b) = ($b, $a) if $b and $a->[_n] < $b->[_n];
266 14116 100       35884 if (my $avs = $a->[_vs]) {
267 7041 100       15828 if ($a->[_n] <= $threshold_brute_force) {
268 6296 100       12445 if ($b) {
269             # brute force
270             ### $O += @$avs * $b->[_n];
271 6199         11700 for my $v0 (@{$b->[_vs]}) {
  6199         18395  
272 73742         113991 for my $v1 (@$avs) {
273 954822         1963931 my $d2 = Math::Vector::Real::dist2($v0, $v1);
274 954822 100       30387231 if ($best_d2 < $d2) {
275 448         514 $best_d2 = $d2;
276 448         1083 @best_vs = ($v0, $v1);
277             }
278             }
279             }
280             }
281             else {
282             ### $O += ((@$avs - 1) * @$avs) >> 1;
283 97         225 for my $i (1..$#$avs) {
284 967         1316 my $v0 = $avs->[$i];
285 967         2103 for my $v1 (@$avs[0 .. $i - 1]) {
286 5566         11206 my $d2 = Math::Vector::Real::dist2($v0, $v1);
287 5566 100       151660 if ($best_d2 < $d2) {
288 27         37 $best_d2 = $d2;
289 27         65 @best_vs = ($v0, $v1);
290             }
291             }
292             }
293             }
294 6296         30266 next;
295             }
296              
297             # else part it in two...
298             ### $O += @$avs;
299 745         2594 my $ix = ($a->[_c0] - $a->[_c1])->max_component_index;
300 745         49292 my ($avs0, $avs1) = nslotpartref $ix => @$avs / 2 => @$avs;
301 745         3155 $a->[_s0] = [Math::Vector::Real->box(@$avs0), scalar(@$avs0), $avs0];
302 745         372452 $a->[_s1] = [Math::Vector::Real->box(@$avs1), scalar(@$avs1), $avs1];
303 745         363956 undef $a->[_vs];
304              
305             # and fall-through...
306             }
307              
308 7820         11591 my ($a0, $a1) = @{$a}[_s0, _s1];
  7820         18163  
309             # If $b is defined we generate the combinations ($a0-$b,
310             # $a1-$b), otherwise it means we want to compare a with
311             # itself and so we generate the trio of pairs ($a0-$a1,
312             # $a0-$a0, $a1-$a1).
313 7820         9318 my (@na, @nb, @nd2);
314 7820 100       16339 if ($b) {
315 7434         15220 @na = ($a0, $a1);
316 7434         11885 @nb = ($b, $b);
317 7434         15146 @nd2 = (Math::Vector::Real->max_dist2_between_boxes(@{$a0}[_c0, _c1], @{$b}[_c0, _c1]),
  7434         36459  
  7434         1488662  
318 7434         9512 Math::Vector::Real->max_dist2_between_boxes(@{$a1}[_c0, _c1], @{$b}[_c0, _c1]));
  7434         23237  
319             }
320             else {
321 386         892 @na = ($a0, $a0, $a1);
322 386         617 @nb = ($a1);
323 386         862 @nd2 = (Math::Vector::Real->max_dist2_between_boxes(@{$a0}[_c0, _c1], @{$a1}[_c0, _c1]),
  386         2153  
  386         47383  
324 386         12236 Math::Vector::Real::dist2(@{$a0}[_c0, _c1]),
325 386         654 Math::Vector::Real::dist2(@{$a1}[_c0, _c1]));
326             }
327              
328 7820         827578 while (@na) {
329 16026         22758 my $a = shift @na;
330 16026         22547 my $b = shift @nb;
331 16026         19965 my $d2 = shift @nd2;
332              
333 16026 100       38821 if ($d2 > $best_d2) {
334 14179         13178 my $p;
335 14179         31537 for ($p = @d2; $p > 0; $p--) {
336             ### $O++;
337 2198637 100       5436440 last if $d2[$p - 1] <= $d2;
338             }
339 14179         29543 splice @d2, $p, 0, $d2;
340 14179         22706 splice @a, $p, 0, $a;
341 14179         59193 splice @b, $p, 0, $b;
342             }
343             }
344             }
345             }
346             else {
347 0         0 @best_vs = ($_[0], $_[0]);
348             }
349 62 50       479 wantarray ? ($best_d2, map V(@$_), @best_vs) : $best_d2;
350             }
351              
352             sub find_brute_force {
353 100     100 1 2918 shift;
354 100         531 goto &_find_brute_force;
355             }
356              
357             sub find_2d_convex_hull {
358 0     0 1   shift;
359              
360 0 0         return unless @_;
361 0           my $dim = @{$_[0]};
  0            
362 0 0         $dim == 2 or croak "find_2d_convex_hull called with vectors of dimension $dim";
363              
364 0           goto &_find_2d_convex_hull;
365             }
366              
367             1;
368             __END__