File Coverage

blib/lib/Math/Vector/Real.pm
Criterion Covered Total %
statement 103 379 27.1
branch 19 126 15.0
condition 4 17 23.5
subroutine 23 61 37.7
pod 30 53 56.6
total 179 636 28.1


line stmt bran cond sub pod time code
1             package Math::Vector::Real;
2              
3             our $VERSION = '0.17';
4              
5 1     1   12634 use strict;
  1         2  
  1         25  
6 1     1   3 use warnings;
  1         1  
  1         17  
7 1     1   3 use Carp;
  1         3  
  1         51  
8 1     1   405 use POSIX ();
  1         4002  
  1         26  
9              
10 1     1   6 use Exporter qw(import);
  1         1  
  1         3039  
11             our @EXPORT = qw(V);
12              
13             our $dont_use_XS;
14             unless ($dont_use_XS) {
15             my $xs_version = do {
16             local ($@, $!, $SIG{__DIE__});
17             eval {
18             require Math::Vector::Real::XS;
19             $Math::Vector::Real::XS::VERSION;
20             }
21             };
22              
23             if (defined $xs_version and $xs_version < 0.07) {
24             croak "Old and buggy version of Math::Vector::Real::XS detected, update it!";
25             }
26             }
27              
28              
29             our %op = (add => '+',
30             neg => 'neg',
31             sub => '-',
32             mul => '*',
33             div => '/',
34             cross => 'x',
35             add_me => '+=',
36             sub_me => '-=',
37             mul_me => '*=',
38             div_me => '/=',
39             abs => 'abs',
40             atan2 => 'atan2',
41             equal => '==',
42             nequal => '!=',
43             clone => '=',
44             as_string => '""');
45              
46             our %ol;
47             $ol{$op{$_}} = \&{${Math::Vector::Real::}{$_}} for keys %op;
48              
49             require overload;
50             overload->import(%ol);
51              
52 6     6 0 21 sub V { bless [@_] }
53              
54             sub new {
55 0     0 1 0 my $class = shift;
56 0         0 bless [@_], $class
57             }
58              
59             sub new_ref {
60 0     0 0 0 my $class = shift;
61 0         0 bless [@{shift()}], $class;
  0         0  
62             }
63              
64             sub zero {
65 0     0 1 0 my ($class, $dim) = @_;
66 0 0       0 $dim >= 0 or croak "negative dimension";
67 0         0 bless [(0) x $dim], $class
68             }
69              
70             sub is_zero {
71 0   0 0 0 0 $_ and return 0 for @$_[0];
72 0         0 return 1
73             }
74              
75             sub cube {
76 0     0 1 0 my ($class, $dim, $size) = @_;
77 0         0 bless [($size) x $dim], $class;
78             }
79              
80             sub axis_versor {
81 0     0 1 0 my ($class, $dim, $ix);
82 0 0       0 if (ref $_[0]) {
83 0         0 my ($self, $ix) = @_;
84 0         0 $class = ref $self;
85 0         0 $dim = @$self;
86             }
87             else {
88 0         0 ($class, $dim, $ix) = @_;
89 0 0       0 $dim >= 0 or croak "negative dimension";
90             }
91 0 0 0     0 ($ix >= 0 and $ix < $dim) or croak "axis index out of range";
92 0         0 my $self = [(0) x $dim];
93 0         0 $self->[$ix] = 1;
94 0         0 bless $self, $class
95             }
96              
97             sub _caller_op {
98 0   0 0   0 my $level = (shift||1) + 1;
99 0         0 my $sub = (caller $level)[3];
100 0         0 $sub =~ s/.*:://;
101 0         0 my $op = $op{$sub};
102 0 0       0 (defined $op ? $op : $sub);
103             }
104              
105             sub _check_dim {
106 78     78   129 local ($@, $SIG{__DIE__});
107 78 50       74 eval { @{$_[0]} == @{$_[1]} } and return;
  78         41  
  78         69  
  78         205  
108 0         0 my $op = _caller_op(1);
109 0 0       0 my $loc = ($_[2] ? 'first' : 'second');
110 0 0       0 UNIVERSAL::isa($_[1], 'ARRAY') or croak "$loc argument to vector operator '$op' is not a vector";
111 0         0 croak "vector dimensions do not match";
112             }
113              
114 0     0 0 0 sub clone { bless [@{$_[0]}] }
  0         0  
115              
116             sub set {
117 0     0 1 0 &_check_dim;
118 0         0 my ($v0, $v1) = @_;
119 0         0 $v0->[$_] = $v1->[$_] for 0..$#$v1;
120             }
121              
122             sub add {
123 13     13 0 226 &_check_dim;
124 13         13 my ($v0, $v1) = @_;
125 13         56 bless [map $v0->[$_] + $v1->[$_], 0..$#$v0]
126             }
127              
128             sub add_me {
129 0     0 0 0 &_check_dim;
130 0         0 my ($v0, $v1) = @_;
131 0         0 $v0->[$_] += $v1->[$_] for 0..$#$v0;
132 0         0 $v0;
133             }
134              
135 2     2 0 2 sub neg { bless [map -$_, @{$_[0]}] }
  2         7  
136              
137             sub sub {
138 16     16 0 15 &_check_dim;
139 16 100       27 my ($v0, $v1) = ($_[2] ? @_[1, 0] : @_);
140 16         60 bless [map $v0->[$_] - $v1->[$_], 0..$#$v0]
141             }
142              
143             sub sub_me {
144 0     0 0 0 &_check_dim;
145 0         0 my ($v0, $v1) = @_;
146 0         0 $v0->[$_] -= $v1->[$_] for 0..$#$v0;
147 0         0 $v0;
148             }
149              
150             sub mul {
151 46 100   46 0 55 if (ref $_[1]) {
152 17         18 &_check_dim;
153 17         16 my ($v0, $v1) = @_;
154 17         11 my $acu = 0;
155 17         47 $acu += $v0->[$_] * $v1->[$_] for 0..$#$v0;
156 17         29 $acu;
157             }
158             else {
159 29         21 my ($v, $s) = @_;
160 29         70 bless [map $s * $_, @$v];
161             }
162             }
163              
164             sub mul_me {
165 0 0   0 0 0 ref $_[1] and croak "can not multiply by a vector in place as the result is not a vector";
166 0         0 my ($v, $s) = @_;
167 0         0 $_ *= $s for @$v;
168 0         0 $v
169             }
170              
171             sub div {
172 6 50 33 6 0 21 croak "can't use vector as dividend"
173             if ($_[2] or ref $_[1]);
174 6         5 my ($v, $div) = @_;
175 6 50       11 $div == 0 and croak "illegal division by zero";
176 6         6 my $i = 1 / $div;
177 6         23 bless [map $i * $_, @$v]
178             }
179              
180             sub div_me {
181 1 50   1 0 7 croak "can't use vector as dividend" if ref $_[1];
182 1         1 my $v = shift;
183 1         3 my $i = 1.0 / shift;
184 1         4 $_ *= $i for @$v;
185 1         2 $v;
186             }
187              
188             sub equal {
189 16     16 0 36 &_check_dim;
190 16         19 my ($v0, $v1) = @_;
191 16   50     81 $v0->[$_] == $v1->[$_] || return 0 for 0..$#$v0;
192 16         45 1;
193             }
194              
195             sub nequal {
196 1     1 0 2 &_check_dim;
197 1         1 my ($v0, $v1) = @_;
198 1   100     8 $v0->[$_] == $v1->[$_] || return 1 for 0..$#$v0;
199 0         0 0;
200             }
201              
202             sub cross {
203 15     15 0 39 &_check_dim;
204 15 100       24 my ($v0, $v1) = ($_[2] ? @_[1, 0] : @_);
205 15         14 my $dim = @$v0;
206 15 50       20 if ($dim == 3) {
207 15         69 return bless [$v0->[1] * $v1->[2] - $v0->[2] * $v1->[1],
208             $v0->[2] * $v1->[0] - $v0->[0] * $v1->[2],
209             $v0->[0] * $v1->[1] - $v0->[1] * $v1->[0]]
210             }
211 0 0       0 if ($dim == 7) {
212 0         0 croak "cross product for dimension 7 not implemented yet, patches welcome!";
213             }
214             else {
215 0         0 croak "cross product not defined for dimension $dim"
216             }
217             }
218              
219 0     0 0 0 sub as_string { "{" . join(", ", @{$_[0]}). "}" }
  0         0  
220              
221             sub abs {
222 11     11 0 616 my $acu = 0;
223 11         10 $acu += $_ * $_ for @{$_[0]};
  11         29  
224 11         55 sqrt $acu;
225             }
226              
227             sub abs2 {
228 4     4 0 3 my $acu = 0;
229 4         4 $acu += $_ * $_ for @{$_[0]};
  4         9  
230 4         4 $acu;
231             }
232              
233             sub dist {
234 0     0 1 0 &_check_dim;
235 0         0 my ($v0, $v1) = @_;
236 0         0 my $d2 = 0;
237 0         0 for (0..$#$v0) {
238 0         0 my $d = $v0->[$_] - $v1->[$_];
239 0         0 $d2 += $d * $d;
240             }
241 0         0 sqrt($d2);
242             }
243              
244             sub dist2 {
245 0     0 1 0 &_check_dim;
246 0         0 my ($v0, $v1) = @_;
247 0         0 my $d2 = 0;
248 0         0 for (0..$#$v0) {
249 0         0 my $d = $v0->[$_] - $v1->[$_];
250 0         0 $d2 += $d * $d;
251             }
252 0         0 $d2;
253             }
254              
255             sub max_component {
256 0     0 1 0 my $max = 0;
257 0         0 for (@{shift()}) {
  0         0  
258 0         0 my $abs = CORE::abs($_);
259 0 0       0 $abs > $max and $max = $abs;
260             }
261             $max
262 0         0 }
263              
264             sub min_component {
265 0     0 1 0 my $self = shift;
266 0         0 my $min = CORE::abs($self->[0]);
267 0         0 for (@$self) {
268 0         0 my $abs = CORE::abs($_);
269 0 0       0 $abs < $min and $min = $abs;
270             }
271             $min
272 0         0 }
273              
274             sub manhattan_norm {
275 0     0 1 0 my $n = 0;
276 0         0 $n += CORE::abs($_) for @{$_[0]};
  0         0  
277 0         0 return $n;
278             }
279              
280             sub manhattan_dist {
281 0     0 1 0 &_check_dim;
282 0         0 my ($v0, $v1) = @_;
283 0         0 my $d = 0;
284 0         0 $d += CORE::abs($v0->[$_] - $v1->[$_]) for 0..$#$v0;
285 0         0 return $d;
286             }
287              
288             sub chebyshev_dist {
289 0     0 1 0 &_check_dim;
290 0         0 my ($v0, $v1) = @_;
291 0         0 my $max = 0;
292 0         0 for (0..$#$v0) {
293 0         0 my $d = CORE::abs($v0->[$_] - $v1->[$_]);
294 0 0       0 $max = $d if $d > $max;
295             }
296 0         0 $max;
297             }
298              
299             sub _upgrade {
300 0     0   0 my $dim;
301             map {
302 0         0 my $d = eval { @{$_} };
  0         0  
  0         0  
  0         0  
303 0 0       0 defined $d or croak "argument is not a vector or array";
304 0 0       0 if (defined $dim) {
305 0 0       0 $d == $dim or croak "dimensions do not match";
306             }
307             else {
308 0         0 $dim = $d;
309             }
310 0 0       0 UNIVERSAL::isa($_, __PACKAGE__) ? $_ : clone($_);
311             } @_;
312             }
313              
314             sub atan2 {
315 1     1 0 1 my ($v0, $v1) = @_;
316 1 50       3 if (@$v0 == 2) {
317 0         0 my $dot = $v0->[0] * $v1->[0] + $v0->[1] * $v1->[1];
318 0         0 my $cross = $v0->[0] * $v1->[1] - $v0->[1] * $v1->[0];
319 0         0 return CORE::atan2($cross, $dot);
320             }
321             else {
322 1         2 my $a0 = &abs($v0);
323 1 50       3 return 0 unless $a0;
324 1         3 my $u0 = $v0 / $a0;
325 1         2 my $p = $v1 * $u0;
326 1         2 CORE::atan2(&abs($v1 - $p * $u0), $p);
327             }
328             }
329              
330             sub versor {
331 8     8 1 7 my $self = shift;
332 8         3 my $f = 0;
333 8         20 $f += $_ * $_ for @$self;
334 8 50       13 $f == 0 and croak "Illegal division by zero";
335 8         8 $f = 1/sqrt $f;
336 8         21 bless [map $f * $_, @$self]
337             }
338              
339             sub wrap {
340 0     0 1 0 my ($self, $v) = @_;
341 0         0 &_check_dim;
342              
343 0         0 bless [map { my $s = $self->[$_];
  0         0  
344 0         0 my $c = $v->[$_];
345 0         0 $c - $s * POSIX::floor($c/$s) } (0..$#$self)];
346             }
347              
348             sub first_orthant_reflection {
349 0     0 1 0 my $self = shift;
350 0         0 bless [map CORE::abs, @$self];
351             }
352              
353             sub sum {
354 0 0   0 1 0 ref $_[0] or shift; # works both as a class and as an instance method
355 0         0 my $sum;
356 0 0       0 if (@_) {
357 0         0 $sum = V(@{shift()});
  0         0  
358 0         0 $sum += $_ for @_;
359             }
360 0         0 return $sum;
361             }
362              
363             sub box {
364 0     0 1 0 shift;
365 0 0       0 return unless @_;
366 0         0 my $min = clone(shift);
367 0         0 my $max = clone($min);
368 0         0 my $dim = $#$min;
369 0         0 for (@_) {
370 0         0 for my $ix (0..$dim) {
371 0         0 my $c = $_->[$ix];
372 0 0       0 if ($max->[$ix] < $c) {
    0          
373 0         0 $max->[$ix] = $c;
374             }
375             elsif ($min->[$ix] > $c) {
376 0         0 $min->[$ix] = $c
377             }
378             }
379             }
380 0 0       0 wantarray ? ($min, $max) : $max - $min;
381             }
382              
383             sub nearest_in_box {
384 0     0 1 0 my $p = shift->clone;
385 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
386 0         0 for (0..$#$p) {
387 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
388 0         0 $p->[$_] = $min->[$_];
389             }
390             elsif ($p->[$_] > $max->[$_]) {
391 0         0 $p->[$_] = $max->[$_];
392             }
393             }
394             $p
395 0         0 }
396              
397             sub dist2_to_box {
398 0 0   0 1 0 @_ > 1 or croak 'Usage: $v->dist2_to_box($w0, ...)';
399 0         0 my $p = shift;
400 0         0 my $d2 = 0;
401 0         0 my ($min, $max) = Math::Vector::Real->box(@_);
402 0         0 for (0..$#$p) {
403 0 0       0 if ($p->[$_] < $min->[$_]) {
    0          
404 0         0 my $d = $p->[$_] - $min->[$_];
405 0         0 $d2 += $d * $d;
406             }
407             elsif ($p->[$_] > $max->[$_]) {
408 0         0 my $d = $p->[$_] - $max->[$_];
409 0         0 $d2 += $d * $d;
410             }
411             }
412 0         0 $d2;
413             }
414              
415             sub nearest_in_box_border {
416             # TODO: this method can be optimized
417 0     0 0 0 my $p = shift->clone;
418 0         0 my ($b0, $b1) = Math::Vector::Real->box(@_);
419 0         0 my $in = 0;
420 0         0 for (0..$#$p) {
421 0 0       0 if ($p->[$_] < $b0->[$_]) {
    0          
422 0         0 $p->[$_] = $b0->[$_];
423             }
424             elsif ($p->[$_] > $b1->[$_]) {
425 0         0 $p->[$_] = $b1->[$_];
426             }
427             else {
428 0         0 $in++;
429             }
430             }
431 0 0       0 if ($in == @$p) {
432             # vector was inside the box
433 0         0 my $min_d = 'inf';
434 0         0 my ($comp, $comp_ix);
435 0         0 for my $q ($b0, $b1) {
436 0         0 for (0..$#$p) {
437 0         0 my $d = CORE::abs($p->[$_] - $q->[$_]);
438 0 0       0 if ($min_d > $d) {
439 0         0 $min_d = $d;
440 0         0 $comp = $q->[$_];
441 0         0 $comp_ix = $_;
442             }
443             }
444             }
445 0         0 $p->[$comp_ix] = $comp;
446             }
447 0         0 $p;
448             }
449              
450             sub max_dist2_to_box {
451 0 0   0 1 0 @_ > 1 or croak 'Usage: $v->max_dist2_to_box($w0, ...)';
452 0         0 my $p = shift;
453 0         0 my ($c0, $c1) = Math::Vector::Real->box(@_);
454 0         0 my $d2 = 0;
455 0         0 for (0..$#$p) {
456 0         0 my $d0 = CORE::abs($c0->[$_] - $p->[$_]);
457 0         0 my $d1 = CORE::abs($c1->[$_] - $p->[$_]);
458 0 0       0 $d2 += ($d0 >= $d1 ? $d0 * $d0 : $d1 * $d1);
459             }
460 0         0 return $d2;
461             }
462              
463             sub dist2_between_boxes {
464 0     0 1 0 my ($class, $a0, $a1, $b0, $b1) = @_;
465 0         0 my ($c0, $c1) = $class->box($a0, $a1);
466 0         0 my ($d0, $d1) = $class->box($b0, $b1);
467 0         0 my $d2 = 0;
468 0         0 for (0..$#$c0) {
469 0         0 my $e0 = $d0->[$_] - $c1->[$_];
470 0 0       0 if ($e0 >= 0) {
471 0         0 $d2 += $e0 * $e0;
472             }
473             else {
474 0         0 my $e1 = $c0->[$_] - $d1->[$_];
475 0 0       0 if ($e1 > 0) {
476 0         0 $d2 += $e1 * $e1;
477             }
478             }
479             }
480 0         0 $d2;
481             }
482              
483             *min_dist2_between_boxes = \&dist2_between_boxes;
484              
485             sub max_dist2_between_boxes {
486 0     0 1 0 my ($class, $a0, $a1, $b0, $b1) = @_;
487 0         0 my ($c0, $c1) = $class->box($a0, $a1);
488 0         0 my ($d0, $d1) = $class->box($b0, $b1);
489 0         0 my $d2 = 0;
490 0         0 for (0..$#$c0) {
491 0         0 my $e0 = $d1->[$_] - $c0->[$_];
492 0         0 my $e1 = $d0->[$_] - $c1->[$_];
493 0         0 $e0 *= $e0;
494 0         0 $e1 *= $e1;
495 0 0       0 $d2 += ($e0 > $e1 ? $e0 : $e1);
496             }
497 0         0 $d2;
498             }
499              
500             sub max_component_index {
501 0     0 1 0 my $self = shift;
502 0 0       0 return unless @$self;
503 0         0 my $max = 0;
504 0         0 my $max_ix = 0;
505 0         0 for my $ix (0..$#$self) {
506 0         0 my $c = CORE::abs($self->[$ix]);
507 0 0       0 if ($c > $max) {
508 0         0 $max = $c;
509 0         0 $max_ix = $ix;
510             }
511             }
512 0         0 $max_ix;
513             }
514              
515             sub min_component_index {
516 0     0 0 0 my $self = shift;
517 0 0       0 return unless @$self;
518 0         0 my $min = CORE::abs($self->[0]);
519 0         0 my $min_ix = 0;
520 0         0 for my $ix (1..$#$self) {
521 0         0 my $c = CORE::abs($self->[$ix]);
522 0 0       0 if ($c < $min) {
523 0         0 $min = $c;
524 0         0 $min_ix = $ix
525             }
526             }
527 0         0 $min_ix;
528             }
529              
530             sub decompose {
531 4     4 1 4 my ($u, $v) = @_;
532 4         7 my $p = $u * ($u * $v)/abs2($u);
533 4         7 my $n = $v - $p;
534 4 50       10 wantarray ? ($p, $n) : $n;
535             }
536              
537             sub canonical_base {
538 0     0 1 0 my ($class, $dim) = @_;
539 0         0 my @base = map { bless [(0) x $dim], $class } 1..$dim;
  0         0  
540 0         0 $base[$_][$_] = 1 for 0..$#base;
541 0         0 return @base;
542             }
543              
544             sub rotation_base_3d {
545 4     4 1 4 my $v = shift;
546 4 50       8 @$v == 3 or croak "rotation_base_3d requires a vector with three dimensions";
547 4         6 $v = $v->versor;
548 4         5 my $n = [0, 0, 0];
549 4         5 for (0..2) {
550 7 100       11 if (CORE::abs($v->[$_]) > 0.57) {
551 4         6 $n->[($_ + 1) % 3] = 1;
552 4         6 $n = $v->decompose($n)->versor;
553 4         9 return ($v, $n, $v x $n);
554             }
555             }
556 0         0 die "internal error, all the components where smaller than 0.57!";
557             }
558              
559             sub rotate_3d {
560 3     3 1 4 my $v = shift;
561 3         3 my $angle = shift;
562 3         6 my $c = cos($angle); my $s = sin($angle);
  3         4  
563 3         4 my ($i, $j, $k) = $v->rotation_base_3d;
564 3         4 my $rj = $c * $j + $s * $k;
565 3         5 my $rk = $c * $k - $s * $j;
566 3 50       6 if (wantarray) {
567 0         0 return map { ($_ * $i) * $i + ($_ * $j) * $rj + ($_ * $k) * $rk } @_;
  0         0  
568             }
569             else {
570 3         2 my $a = shift;
571 3         3 return (($a * $i) * $i + ($a * $j) * $rj + ($a * $k) * $rk);
572             }
573             }
574              
575 0     0 1   sub normal_base { __PACKAGE__->complementary_base(@_) }
576              
577             sub complementary_base {
578 0     0 1   shift;
579 0 0         @_ or croak "complementaty_base requires at least one argument in order to determine the dimension";
580 0           my $dim = @{$_[0]};
  0            
581 0 0 0       if ($dim == 2 and @_ == 1) {
582 0           my $u = versor($_[0]);
583 0           @$u = ($u->[1], -$u->[0]);
584 0           return $u;
585             }
586              
587 0           my @v = map clone($_), @_;
588 0           my @base = Math::Vector::Real->canonical_base($dim);
589 0           for my $i (0..$#v) {
590 0           my $u = versor($v[$i]);
591 0           $_ = decompose($u, $_) for @v[$i+1 .. $#v];
592 0           $_ = decompose($u, $_) for @base;
593             }
594              
595 0           my $last = $#base - @v;
596 0 0         return if $last < 0;
597 0           for my $i (0 .. $last) {
598 0           my $max = abs2($base[$i]);
599 0 0         if ($max < 0.3) {
600 0           for my $j ($i+1 .. $#base) {
601 0           my $d2 = abs2($base[$j]);
602 0 0         if ($d2 > $max) {
603 0           @base[$i, $j] = @base[$j, $i];
604 0 0         last unless $d2 < 0.3;
605 0           $max = $d2;
606             }
607             }
608             }
609 0           my $versor = $base[$i] = versor($base[$i]);
610 0           $_ = decompose($versor, $_) for @base[$i+1..$#base];
611             }
612 0 0         wantarray ? @base[0..$last] : $base[0];
613             }
614              
615             sub select_in_ball {
616 0     0 1   my $v = shift;
617 0           my $r = shift;
618 0           my $r2 = $r * $r;
619 0           grep $v->dist2($_) <= $r2, @_;
620             }
621              
622             sub select_in_ball_ref2bitmap {
623 0     0 0   my $v = shift;
624 0           my $r = shift;
625 0           my $p = shift;
626 0           my $r2 = $r * $r;
627 0           my $bm = "\0" x int((@$p + 7) / 8);
628 0           for my $ix (0..$#$p) {
629 0 0         vec($bm, $ix, 1) = 1 if $v->dist2($p->[$ix]) <= $r2;
630             }
631 0           return $bm;
632             }
633              
634             # This is run *after* Math::Vector::Real::XS is loaded!
635             *norm = \&abs;
636             *norm2 = \&abs2;
637             *max = \&max_component;
638             *min = \&min_component;
639             *chebyshev_norm = \&max_component;
640              
641             1;
642             __END__