File Coverage

blib/lib/Math/Orthonormalize.pm
Criterion Covered Total %
statement 73 73 100.0
branch 22 34 64.7
condition 1 3 33.3
subroutine 10 10 100.0
pod 5 5 100.0
total 111 125 88.8


line stmt bran cond sub pod time code
1            
2             =head1 NAME
3            
4             Math::Orthonormalize - Gram-Schmidt Orthonormalization of vectors
5            
6             =head1 SYNOPSIS
7            
8             use Math::Orthonormalize qw(:all);
9            
10             my @base_of_r_2 = (
11             [2, 1],
12             [1, 3]
13             );
14             my $vector = [1, 2, 3];
15            
16             my @orthonormalized = orthonormalize(@base_of_r_2);
17             my @orthogonalized = orthogonalize(@base_of_r_2);
18            
19             my $normalized = normalize($vector);
20             my $scaled = scale(2, $vector);
21             my $scalar = scalar_product($vector1, $vector2);
22            
23             =head1 DESCRIPTION
24            
25             Math::Orthonormalize offers subroutines to compute normalized or
26             non-normalized orthogonal bases of Euclidean vector spaces. That means:
27             Given a vector base of R^n, it computes a new base of R^n whose individual
28             vectors are all orthogonal. If those new base vectors all have a length of
29             1, the base is orthonormalized.
30            
31             The module uses the Gram-Schmidt Algorithm.
32            
33             =head2 EXPORT
34            
35             No subroutines are exported by default, but the standart Exporter semantics are
36             in place, including the ':all' tag that imports all of the exportable
37             subroutines which are listed below.
38            
39             =cut
40            
41             package Math::Orthonormalize;
42            
43 6     6   15991 use 5.006;
  6         20  
  6         243  
44 6     6   38 use strict;
  6         10  
  6         207  
45 6     6   29 use warnings;
  6         13  
  6         209  
46            
47 6     6   1854 use Math::Symbolic qw/parse_from_string/;
  6         545818  
  6         531  
48 6     6   55 use Carp;
  6         11  
  6         5737  
49            
50             our $VERSION = '1.00';
51            
52             require Exporter;
53             our @ISA = qw(Exporter);
54             our %EXPORT_TAGS = ( 'all' => [ qw(
55             orthonormalize
56             orthogonalize
57             scalar_product
58             normalize
59             scale
60             ) ] );
61             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
62             our @EXPORT = ();
63            
64             =head1 SUBROUTINES
65            
66             =head2 orthonormalize
67            
68             Takes any number (>1) of vectors (array refs of vector components) as argument
69             which form a base (that is, they are linearly independent) and returns
70             an orthogonalized and normalized base of the same vector space
71             (that is, n new array references).
72            
73             =cut
74            
75             sub orthonormalize {
76 3     3 1 2175 my @vectors = @_;
77 3 50       8 croak "No arguments to orthogonalize"
78             if not @vectors;
79 5         13 croak "Arguments to orthogonalize must be array refs (vectors)"
80 3 50       5 if grep {ref($_) ne 'ARRAY'} @vectors;
81 3         3 my $dim = @{$vectors[0]};
  3         4  
82 5         13 croak "Vectors must have same dimension for orthogonalization"
83 3 50       5 if grep {@$_ != $dim} @vectors;
84            
85 3         6 my @base = orthogonalize(@vectors);
86 3         3 @base = map {normalize($_)} @base;
  5         9  
87 3         8 return @base;
88             }
89            
90            
91             =head2 orthogonalize
92            
93             Takes any number (>1) of vectors (array refs of vector components) as argument
94             which form a base (that is, they are linearly independent) and returns
95             an orthogonalized base of the same vector space (that is, n new array
96             references).
97            
98             =cut
99            
100             sub orthogonalize {
101 6     6 1 2539 my @vectors = @_;
102 6 50       17 croak "No arguments to orthogonalize"
103             if not @vectors;
104 10         27 croak "Arguments to orthogonalize must be array refs (vectors)"
105 6 50       9 if grep {ref($_) ne 'ARRAY'} @vectors;
106 6         7 my $dim = @{$vectors[0]};
  6         9  
107 10         25 croak "Vectors must have same dimension for orthogonalization"
108 6 50       6 if grep {@$_ != $dim} @vectors;
109            
110 6         8 my @newbase;
111 6         9 push @newbase, $vectors[0];
112 6         8 my @squares;
113 6 100       19 push @squares, scalar_product($vectors[0], $vectors[0]) if @vectors > 1;
114 6         13 foreach my $i (1..$#vectors) {
115 4         5 my $new = $vectors[$i];
116 4         7 foreach my $j (0..$#newbase) {
117 4         8 my $vec = scale(
118             scalar_product($vectors[$i], $newbase[$j])
119             / $squares[$j],
120             $newbase[$j]
121             );
122 4         8 @$new = map {$_ -= shift @$vec} @$new;
  8         25  
123             }
124 4         22 push @newbase, $new;
125 4 50       13 push @squares, scalar_product($new, $new) if $i < $#vectors;
126             }
127 6         21 return @newbase;
128             }
129            
130             =head2 normalize
131            
132             Normalizes a vector. That is, it changes the vector length to 1 without
133             changing the vector's direction.
134            
135             Takes an array reference with the vector components as argument and returns
136             a new array reference containing the normalized vector components.
137            
138             =cut
139            
140             sub normalize {
141 9     9 1 2919 my $v = shift;
142 9 50       32 croak "Argument to normalize() must be an array ref (vector)"
143             if not ref($v) eq 'ARRAY';
144 9 50       22 croak "Cannot normalize 0-dimensional vectors"
145             if @$v == 0;
146 9         32 my $sum = $v->[0]**2;
147 9         35 $sum += $v->[$_]**2 for 1..$#$v;
148 9         31 $sum = sqrt($sum);
149 9 100       278 croak "Cannot normalize 0-vector"
150             if $sum == 0;
151 8         11 return [map {$_ / $sum} @$v];
  17         47  
152             }
153            
154             =head2 scale
155            
156             Takes a scalar and a vector (array reference of vector components) as
157             arguments. Multiplies every component of the vector by the specified
158             scalar and returns a new array reference containing the scaled vector
159             components.
160            
161             =cut
162            
163             sub scale {
164 8 50 33 8 1 20848 croak "scale() takes a scalar and a vector (ary ref) as arguments"
165             if not @_ == 2 or not ref($_[1]) eq 'ARRAY';
166 8         26 croak "Cannot handle 0-dimensional vectors"
167 8 50       11 if @{$_[1]} == 0;
168 8         12 my $new = [];
169 8         12 foreach (@{$_[1]}) {
  8         21  
170 19         144 push @$new, $_[0] * $_;
171             }
172 8         85 return $new;
173             }
174            
175             =head2 scalar_product
176            
177             Computes the scalar product of two vectors. Expects two array references
178             with vector components (same number of components) as argument and
179             returns their scalar product.
180            
181             =cut
182            
183             sub scalar_product {
184 17 50   17 1 204153 croak "Invalid number of arguments to scalar_product()"
185             if not @_ == 2;
186 17         31 my ($v1, $v2) = @_;
187 17 100       576 croak "Cannot compute the scalar product of vectors of different ",
188             "dimensions"
189             if @$v1 != @$v2;
190 14 100       236 croak "Cannot deal with 0-dimensional vectors"
191             if @$v1 == 0;
192 13         156 my $sum = $v1->[0] * $v2->[0];
193 13 100       154 return $sum if @$v1 == 1;
194            
195 12         57 $sum += $v1->[$_] * $v2->[$_] for 1..$#$v1;
196 12         540 return $sum;
197             }
198            
199            
200             1;
201             __END__