File Coverage

blib/lib/Math/Vector/BestRotation.pm
Criterion Covered Total %
statement 138 165 83.6
branch 23 44 52.2
condition n/a
subroutine 23 25 92.0
pod 14 14 100.0
total 198 248 79.8


line stmt bran cond sub pod time code
1             package Math::Vector::BestRotation;
2              
3 5     5   182368 use warnings;
  5         14  
  5         173  
4 5     5   32 use strict;
  5         11  
  5         342  
5              
6 5     5   150 use 5.008008;
  5         25  
  5         254  
7              
8 5     5   33 use Carp;
  5         11  
  5         465  
9 5     5   32 use List::Util qw(sum max);
  5         9  
  5         594  
10 5     5   7299 use Math::MatrixReal;
  5         158953  
  5         11552  
11              
12             =head1 NAME
13              
14             C - best rotation to match two vector sets
15              
16             =head1 VERSION
17              
18             Version 0.009
19              
20             =cut
21              
22             our $VERSION = '0.009';
23              
24              
25             ###########################################################################
26             # #
27             # Init Process #
28             # #
29             ###########################################################################
30              
31             sub new {
32 9     9 1 18169 my ($class, @args) = @_;
33              
34 9         39 my $self = bless {}, $class;
35 9         57 $self->init(@args);
36 9         32 return $self;
37             }
38              
39             sub init {
40 9     9 1 26 my ($self, %args) = @_;
41              
42 9         59 $self->{matrix_r} = [0, 0, 0, 0, 0, 0, 0, 0, 0];
43              
44 9         55 foreach(keys %args) {
45 0         0 my $meth = $_;
46 0 0       0 if($self->can($meth)) { $self->$meth($args{$meth}) }
  0         0  
47 0         0 else { carp "Unrecognized init parameter $meth.\n" }
48             }
49             }
50              
51             ###########################################################################
52             # #
53             # Accessors #
54             # #
55             ###########################################################################
56              
57             sub matrix_r {
58 59     59 1 13838 my ($self, @args) = @_;
59              
60 59 50       178 croak "Attribute matrix_r is readonly.\n" if(@args);
61 59         585 return Math::MatrixReal->new_from_rows
62             ([[$self->{matrix_r}->[0],
63             $self->{matrix_r}->[1],
64             $self->{matrix_r}->[2]],
65             [$self->{matrix_r}->[3],
66             $self->{matrix_r}->[4],
67             $self->{matrix_r}->[5]],
68             [$self->{matrix_r}->[6],
69             $self->{matrix_r}->[7],
70             $self->{matrix_r}->[8]]]);
71             }
72              
73             sub matrix_u {
74 11     11 1 7888 my ($self, @args) = @_;
75              
76 11 100       54 croak "Attribute matrix_u is readonly.\n" if(@args);
77 10         38 return $self->{matrix_u};
78             }
79              
80             ###########################################################################
81             # #
82             # Retrieve Data #
83             # #
84             ###########################################################################
85              
86             sub _compute_bases {
87 27     27   53 my ($self) = @_;
88 27         90 my $r = $self->matrix_r;
89 27         16741 my $rtr = (~$r) * $r;
90 27         5863 my ($mu, $a) = $rtr->sym_diagonalize;
91              
92 27         12226 my $sorted = 0;
93 27         102 while(!$sorted) {
94 62         883 $sorted = 1;
95 62 100       175 if(abs($mu->element(1, 1)) < abs($mu->element(2, 1))) {
96 32         612 $sorted = 0;
97 32         122 $mu->swap_row(1, 2);
98 32         735 $a->swap_col(1, 2);
99             }
100 62 100       1551 if(abs($mu->element(2, 1)) < abs($mu->element(3, 1))) {
101 18         338 $sorted = 0;
102 18         62 $mu->swap_row(2, 3);
103 18         391 $a->swap_col(2, 3);
104             }
105             }
106              
107             # make sure, eigensystem is right-handed
108 27         596 my $a3 = $a->column(1)->vector_product($a->column(2));
109 27         3614 $a->assign(1, 3, $a3->element(1, 1));
110 27         660 $a->assign(2, 3, $a3->element(2, 1));
111 27         608 $a->assign(3, 3, $a3->element(3, 1));
112              
113 27 50       614 if($mu->element(1, 1) < 1e-12) {
114 0         0 carp("The largest eigenvalue of R^t*R is smaller than ".
115             "1e-12. The code will try to go on, but may die with ".
116             "division by zero later. In any case, numerical ".
117             "instability of the result has to be expected.\n");
118             }
119              
120 27         310 my $b = []; # matrix of column vectors b_k
121 27         53 my $v; # vector buffer
122             my $l; # length buffer
123 0         0 my $col; # column matrix buffer
124              
125             # determine b_1
126 27         80 $col = $r * $a->column(1);
127 27         7707 $v = [map { $col->element($_, 1) } (1, 2, 3)];
  81         1615  
128 27         516 $l = sqrt(sum(map { $v->[$_]**2 } (0, 1, 2)));
  81         518  
129              
130             # If length is zero all b's are arbitrary. Otherwise normalize.
131 27 50       203 if($l == 0) {
132 0         0 carp("R is (too close to) the nullmatrix. ".
133             "The result will be arbitrary.\n");
134 0         0 $v = [1, 0, 0];
135             }
136 27         99 else { @$v = map { $_ / $l } @$v }
  81         181  
137 27         117 @$b[0, 3, 6] = @$v;
138              
139             # determine b_2
140 27         98 $col = $r * $a->column(2);
141 27         3236 $v = [map { $col->element($_, 1) } (1, 2, 3)];
  81         693  
142 27         335 $l = sqrt(sum(map { $v->[$_]**2 } (0, 1, 2)));
  81         217  
143              
144             # if length is zero, we choose a vector perpendicular to b_1
145 27 50       73 if($l == 0) {
146 0         0 carp("The two smaller eigenvalues are 0. The result is not ".
147             "unique.\n");
148              
149 0 0       0 if(abs($b->[0]) < abs($b->[3])) {
150 0 0       0 if(abs($b->[0]) < abs($b->[6])) {
151 0         0 $b->[1] = 0;
152 0         0 $b->[4] = -$b->[6];
153 0         0 $b->[7] = $b->[3];
154             }
155             else {
156 0         0 $b->[1] = -$b->[3];
157 0         0 $b->[4] = $b->[0];
158 0         0 $b->[7] = 0;
159             }
160             }
161             else {
162 0 0       0 if(abs($b->[3]) < abs($b->[6])) {
163 0         0 $b->[1] = -$b->[6];
164 0         0 $b->[4] = 0;
165 0         0 $b->[7] = $b->[0];
166             }
167             else {
168 0         0 $b->[1] = -$b->[3];
169 0         0 $b->[4] = $b->[0];
170 0         0 $b->[7] = 0;
171             }
172             }
173             }
174             # otherwise we normalize carefully
175             else {
176 27         61 @$v = map { $_ / $l } @$v;
  81         179  
177              
178             # $l could have been very small, though.
179             # Therefore, we make b_2 orthogonal to b_1 and normalize again.
180 27         115 my $p = $v->[0] * $b->[0] + $v->[1] * $b->[3] + $v->[2] * $b->[6];
181 27         46 @$v = map { $v->[$_] - $p * $b->[3*$_] } (0, 1, 2);
  81         263  
182 27         58 $l = sqrt(sum(map { $v->[$_]**2 } (0, 1, 2)));
  81         167  
183 27         49 @$b[1, 4, 7] = map { $_ / $l } @$v;
  81         196  
184             }
185              
186             # determine b_3 as by cross product
187 27         119 $b->[2] = $b->[3] * $b->[7] - $b->[6] * $b->[4];
188 27         84 $b->[5] = $b->[6] * $b->[1] - $b->[0] * $b->[7];
189 27         70 $b->[8] = $b->[0] * $b->[4] - $b->[3] * $b->[1];
190              
191 27         210 return($a, Math::MatrixReal->new_from_rows
192             ([[@$b[0, 1, 2]], [@$b[3, 4, 5]], [@$b[6, 7, 8]]]));
193             }
194              
195             sub _matrix_a_to_b {
196 27     27   52 my ($self, $a, $b) = @_;
197              
198 27         82 return($b * (~$a));
199             }
200              
201             sub best_orthogonal {
202 9     9 1 1503 my ($self) = @_;
203 9         34 my ($a, $b) = $self->_compute_bases;
204              
205 9 100       5396 if($self->matrix_r->det < 0) {
206 2         1761 $b = $b * Math::MatrixReal->new_from_rows
207             ([[1, 0, 0], [0, 1, 0], [0, 0, -1]]);
208             }
209              
210 9         7318 $self->{matrix_u} = $self->_matrix_a_to_b($a, $b);
211 9         1849 return $self->{matrix_u};
212             }
213              
214             sub best_rotation {
215 13     13 1 22533 my ($self) = @_;
216 13         47 my ($a, $b) = $self->_compute_bases;
217              
218 13         14280 $self->{matrix_u} = $self->_matrix_a_to_b($a, $b);
219 13         2890 return $self->{matrix_u};
220             }
221              
222 0     0 1 0 sub best_proper_rotation { shift(@_)->best_rotation(@_) }
223              
224             sub best_improper_rotation {
225 5     5 1 15 my ($self) = @_;
226 5         18 my ($a, $b) = $self->_compute_bases;
227              
228 5         3462 $b = $b * Math::MatrixReal->new_diag([1, 1, -1]);
229              
230 5         1027 $self->{matrix_u} = $self->_matrix_a_to_b($a, $b);
231 5         1124 return $self->{matrix_u};
232             }
233              
234 5     5 1 29235 sub best_flipped_rotation { shift(@_)->best_improper_rotation(@_) }
235              
236             sub rotation_axis {
237 3     3 1 17 my ($self, @args) = @_;
238 3 50       15 my $matrix = @args > 0 ? $args[0] : $self->matrix_u;
239              
240 3 50       9 croak("Unable to find the rotation axis of an undefined matrix. ".
241             "Has any rotation been calculated, yet?.\n")
242             if(!defined($matrix));
243             croak("Wrong type of matrix given to rotation_matrix.\n")
244 3 50       7 if(!eval { $matrix->isa('Math::MatrixReal') } );
  3         24  
245 3 50       14 croak("The calculation of a rotation axis of an improper rotation ".
246             "is not supported.\n")
247             if($matrix->det < 0);
248              
249 3         870 my $unity = Math::MatrixReal->new_diag([1, 1, 1]);
250 3         139 my $m = $matrix + ~$matrix - ($matrix->trace - 1) * $unity;
251              
252 3         1163 my @col_lengths = map { $m->col($_)->length } (1, 2, 3);
  9         384  
253              
254 3         185 my $axis;
255 3 100       24 if($col_lengths[0] >= max($col_lengths[1], $col_lengths[2])) {
    50          
256 2     6   9 $axis = $m->col(1)->each(sub { $_[0] / $col_lengths[0] });
  6         303  
257             }
258             elsif($col_lengths[1] >= max($col_lengths[2], $col_lengths[0])) {
259 0     0   0 $axis = $m->col(2)->each(sub { $_[0] / $col_lengths[1] });
  0         0  
260             }
261             else {
262 1     3   3 $axis = $m->col(3)->each(sub { $_[0] / $col_lengths[2] });
  3         130  
263             }
264            
265 3         46 return $axis;
266             }
267              
268             sub rotation_angle {
269 3     3 1 15 my ($self, @args) = @_;
270 3 50       16 my $matrix = @args > 0 ? $args[0] : $self->matrix_u;
271              
272 3 50       11 croak("Unable to find the rotation angle of an undefined matrix. ".
273             "Has any rotation been calculated, yet?.\n")
274             if(!defined($matrix));
275             croak("Wrong type of matrix given to rotation_matrix.\n")
276 3 50       6 if(!eval { $matrix->isa('Math::MatrixReal') } );
  3         18  
277 3 50       13 croak("The calculation of a rotation angle of an improper rotation ".
278             "is not supported.\n")
279             if($matrix->det < 0);
280              
281 3         791 my $cos = ($matrix->trace - 1) / 2;
282              
283 3         78 return abs(atan2(sqrt(1 - $cos**2), $cos));
284             }
285              
286             ###########################################################################
287             # #
288             # Change Data #
289             # #
290             ###########################################################################
291              
292             sub add_pair {
293 40     40 1 63608 my ($self, $x, $y) = @_;
294 40         84 my $matrix_r = $self->{matrix_r};
295              
296 40         138 for(my $i=0;$i<3;$i++) {
297 120         289 for(my $j=0;$j<3;$j++) {
298 360         1147 $matrix_r->[3*$i+$j] += $y->[$i] * $x->[$j];
299             }
300             }
301             }
302              
303             sub add_many_pairs {
304 8     8 1 23168 my ($self, $x, $y) = @_;
305 8         22 my $matrix_r = $self->{matrix_r};
306              
307 8         41 for(my $n=0;$n<@$x;$n++) {
308 23         57 for(my $i=0;$i<3;$i++) {
309 69         137 for(my $j=0;$j<3;$j++) {
310 207         671 $matrix_r->[3*$i+$j] += $y->[$n]->[$i] * $x->[$n]->[$j];
311             }
312             }
313             }
314             }
315              
316             sub clear {
317 20     20 1 29479696 my ($self) = @_;
318              
319 20         110 $self->{matrix_r} = [0, 0, 0, 0, 0, 0, 0, 0, 0];
320 20         204 $self->{matrix_u} = undef;
321             }
322              
323              
324             1;
325              
326             __END__