File Coverage

blib/lib/Math/GSL/Linalg/SVD.pm
Criterion Covered Total %
statement 15 164 9.1
branch 0 54 0.0
condition 0 24 0.0
subroutine 5 22 22.7
pod n/a
total 20 264 7.5


line stmt bran cond sub pod time code
1             package Math::GSL::Linalg::SVD;
2              
3 1     1   23743 use 5.010000;
  1         3  
  1         34  
4 1     1   5 use strict;
  1         2  
  1         30  
5 1     1   4 use warnings;
  1         5  
  1         48  
6 1     1   5 use Carp;
  1         1  
  1         121  
7              
8             #=fs Config
9              
10             require Exporter;
11 1     1   819 use AutoLoader qw(AUTOLOAD);
  1         1623  
  1         7  
12              
13             our @ISA = qw(Exporter);
14             our %EXPORT_TAGS = ( 'all' => [ qw(
15            
16             ) ] );
17              
18             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
19              
20             our @EXPORT = qw(
21            
22             );
23              
24             #=fe
25              
26             our $VERSION = '0.0.2';
27              
28             #=fs Config
29              
30             require XSLoader;
31             XSLoader::load('Math::GSL::Linalg::SVD', $VERSION);
32              
33             #=fe
34              
35              
36             #////////////////////////////////////////////// Convinience Methods ///////////////////////////////////////////////////
37              
38              
39             =head1 NAME
40              
41             Math::GSL::Linalg::SVD - Perl extension with convenience methods for performing SVD and eigenvector decomp with the gsl C libraries.
42              
43             =cut
44              
45             =head1 SYNOPSIS
46              
47             use Math::GSL::Linalg::SVD;
48              
49             # Create object.
50             my $svd = Math::GSL::Linalg::SVD->new( { verbose => 1 } );
51              
52             my $data = [
53             [qw/ 9.515970281313E-01 1.230695618728E-01 -1.652767938310E-01 /],
54             [qw/ -1.788010086499E-01 3.654739881179E-01 8.526964090247E-02 /],
55             [qw/ 4.156708817272E-02 5.298288357316E-02 7.130047145031E-01 /],
56             ];
57              
58             # Load data.
59             $svd->load_data( { data => $data } );
60              
61             # Perform singular value decomposition using the Golub-Reinsch algorithm (this is the default - see METHODS).
62             # To perform eigen decomposition pass 'eign' as algorithm argument - see METHODS.
63             $svd->decompose( { algorithm => q{gd} } );
64              
65             # Pass results - see METHODS for more details.
66             my ($S_vec_ref, $U_mat_ref, $V_mat_ref, $original_data_ref) = $svd->results;
67              
68             # Print elements of vector S.
69             print qq{\nPrint diagonal elements in vector S\n};
70             for my $s (@{$S_vec_ref}) { print qq{$s, }; }
71              
72             # Print elements of matrix U.
73             print qq{\nPrint matrix U\n};
74             for my $r (0..$#{$U_mat_ref}) {
75             for my $c (0..$#{$U_mat_ref->[$r]}) { print qq{$U_mat_ref->[$r][$c], } }; print qq{\n}; }
76              
77             # Print elements of matrix V.
78             print qq{\nPrint matrix V\n};
79             for my $r (0..$#{$V_mat_ref}) {
80             for my $c (0..$#{$V_mat_ref->[$r]}) { print qq{$V_mat_ref->[$r][$c], } }; print qq{\n}; }
81            
82             =cut
83              
84             =head1 DESCRIPTION
85              
86             The singular value decomposition (SVD) is an important factorization of a rectangular real matrix - see
87             http://en.wikipedia.org/wiki/Singular_value_decomposition. Eigendecomposition is the factorization of a
88             matrix into a canonical form, whereby the matrix is represented in terms of its eigenvalues and
89             eigenvectors - see http://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix. This module implements the
90             SVD and Eigen decomposition routines of the The C GNU Scientific Library (GSL). It provides simple convinience methods in the
91             upper-level Math::GSL::Linalg::SVD namespace to perform these operations. Alternatively, it also provides direct access
92             to the C routines in the Math::GSL::Linalg::SVD::Matrix, Math::GSL::Linalg::SVD::Vector and
93             Math::GSL::Linalg::SVD::Eigen namespaces - see METHODS.
94              
95             =cut
96              
97             =head1 METHODS
98              
99             This is a C-Wrapper for the gsl SVD and Eigen decomp routines. Its provides two means of accessing them. First, a basic
100             OO-interface of convenience methods to allow simple use of the various sdv and eigen routines within the Math::GSL::Linalg::SVD namespaces. Second,
101             it allows you to use the various routines directly using an object interface for the various C structure types. These
102             exist within specific lower-level namespaces for convenience - see below.
103              
104             =head2 Math::GSL::Linalg::SVD
105              
106             =head3 new
107              
108             Create a new Math:GSL::Linalg::SVD object.
109              
110             my $svd = Math::GSL::Linalg::SVD->new();
111             # Pass verbose to turn on minimal messages.
112             my $svd = Math::GSL::Linalg::SVD->new( { verbose => 1 } );
113              
114             =head3 load_data
115            
116             Used for loading data into object. Data is fed as a reference to a LoL within an anonymous hash using the named argument
117             'data'.
118              
119             $svd->load_data( { data => [
120             [qw/ 9.515970281313E-01 1.230695618728E-01 /],
121             [qw/ -1.788010086499E-01 3.654739881179E-01 /],
122             [qw/ 4.156708817272E-02 5.298288357316E-02 /],
123             ] } );
124              
125             =head3 decompose
126              
127             Performs one of several different singular value decomposition algorithms on the loaded matrix (or computes eigenvalues
128             and eigenvectors) depending on argument passed with with 'algorithm' argument. To use the Golub-Reinsch algorithm
129             implemented by C pass 'gd'. To use the modified Golub-Reinsch algorithm implemented by
130             C pass 'mod'. To use the one-sided Jacobi orthogonalization algorithm
131             implemented by C pass 'jacobi'. To perform the eigenvalue and eigenvector calculations
132             implemented by C pass 'eigen'. See
133             http://www.gnu.org/software/gsl/manual/html_node/Singular-Value-Decomposition.html for further details.
134              
135             # Perform svd using the Golub-Reinsch algorithm pass 'gd' or nothing.
136             $svd->decompose();
137             $svd->decompose( { algorithm => q{mod} } );
138             $svd->decompose( { algorithm => q{jacobi} } );
139             $eigen->decompose( { algorithm => q{eigen} } );
140              
141              
142             =head3 results
143              
144             Used to access the results of the analysis. Called in LIST context. For svd an ordered LIST of the LoLs is returned
145             (corresponding to Vector S, Matrix U, Matrix V and Matrix A (see
146             http://www.gnu.org/software/gsl/manual/html_node/Singular-Value-Decomposition.html). See SYNOPSIS.
147              
148             my ($S_vec_ref, $U_mat_ref, $V_mat_ref, $original_data_ref) = $svd->results;
149              
150             For eigen computation an ordered list of LoLs is returned corresponding to unordered eigenvalues, the eigenvectors (in
151             the same order as the eigenvalues) and the original data matrix. See
152             http://www.gnu.org/software/gsl/manual/html_node/Real-Symmetric-Matrices.html.
153            
154             my ($e_val_ref, $e_vec_ref, $original_data_ref) = $eigen->results;
155              
156             # Print eigenvalues along with corresponding eigenvectors.
157             for my $i (0..$#{$e_val_ref}) {
158             print qq{\nEigenvalue: $e_val_ref->[$i], };
159             print qq{\nEigenvector: };
160             for my $vec_component (@{$e_vec_ref->[$i]}) { print qq{$vec_component, }; }; print qq{\n}; }
161              
162             =head2 Math::GSL::Linalg::SVD::Matrix
163              
164             This namespace functions as an interface to the C C-structure typedef.
165              
166             =head3 new
167              
168             Name: new
169             Implements: gsl_matrix_alloc
170             Usage: $gsl_matrix_pointer_as_perl_object = Math::GSL::Linalg::SVD::Matrix->new;
171             Returns: pointer to a gsl_matrix type as Perl object
172              
173             =head3 set_matrix
174              
175             Name: set_matrix
176             Implements: gsl_matrix_set
177             Usage: $gsl_matrix_pointer_as_perl_object->set_matrix($row, $col, $double_number);
178             Returns:
179              
180             =head3 get_matrix
181              
182             Name: matrix_get
183             Implements: gsl_matrix_get
184             Usage: $gsl_matrix_pointer_as_perl_object->set_matrix($row, $col);
185             Returns: scalar value
186              
187             =head3 SV_decomp
188              
189             Name: SV_decomp
190             Implements: gsl_linalg_SV_decomp
191             Usage: $gsl_matrix_pointer_as_perl_object->SV_decomp (...);
192             Returns:
193              
194             =head3 SV_decomp_mod
195              
196             Name: SV_decomp_mod
197             Implements: gsl_linalg_SV_decomp_mod
198             Usage: $gsl_matrix_pointer_as_perl_object->SV_decomp_mod (...);
199             Returns:
200              
201             =head3 SV_decomp_jacobi
202            
203             Name: SV_decomp_jacobi
204             Implements: gsl_linalg_SV_decomp_jacobi
205             Usage: $gsl_matrix_pointer_as_perl_object->SV_decomp_mod (...);
206             Returns:
207              
208             =head3 Eigen_decomp
209              
210             Name: Eigen_decomp
211             Implements: gsl_eigen_symmv
212             Usage: $gsl_matrix_pointer_as_perl_object->Eigen_decomp (...);
213             Returns:
214              
215             =head2 Math::GSL::Linalg::SVD::Vector
216              
217             This namespace functions as an interface to the C C-structure typedef.
218              
219             =head3 new
220              
221             Name: new
222             Implements: gsl_vector_alloc
223             Usage: $gsl_vector_pointer_as_perl_object = Math::GSL::Linalg::SVD::Vector->new;
224             Returns: pointer to gsl_vector as perl object
225              
226              
227             =head3 set_vector
228              
229             Name: vector_set
230             Implements: gsl_vector_set
231             Usage: $gsl_vector_pointer_as_perl_object->set_vector($row, $col, $double_number);
232             Returns:
233              
234             =head3 get_vector
235              
236             Name: vector_get
237             Implements: gsl_vector_get
238             Usage: $gsl_vector_pointer_as_perl_object->set_vector($row, $col)
239             Returns: scalar value
240              
241             =head2 Math::GSL::Linalg::SVD::Eigen
242              
243             This namespace functions as an interface to the C C-structure typedef used as workspace for
244             the eigen decomposition routines of the gsl library.
245              
246             =head3 new
247              
248             Name: new
249             Implements: gsl_eigen_symmv_alloc
250             Usage: $gsl_vector_pointer_as_perl_object = Math::GSL::Linalg::SVD::vector->new;
251             Returns: pointer to gsl_eigen_symmv type as perl object
252              
253             =cut
254              
255             sub new {
256 0     0     my ( $class, $h_ref ) = @_;
257 0 0 0       croak qq{\nArguments must be passed as HASH reference.} if ( ( $h_ref ) && ( ref $h_ref ne q{HASH} ) );
258 0 0 0       my $verbose = 1 if ( ( exists $h_ref->{verbose} ) && ( $h_ref->{verbose} == 1 ) );
259 0           my $self = {};
260 0           $self->{flags}{verbose} = $verbose;
261 0           bless $self, $class;
262 0           return $self;
263             }
264              
265             sub load_data {
266 0     0     my ( $self, $h_ref ) = @_;
267 0 0 0       croak qq{\nArguments must be passed as HASH reference.} if ( ( $h_ref ) && ( ref $h_ref ne q{HASH} ) );
268 0           my $data_dirty = $h_ref->{data};
269 0           &_data_checks($data_dirty);
270 0           my $data = _deep_copy_references($data_dirty);
271              
272 0           my $A_m = scalar ( @{$data} );
  0            
273 0           my $A_n = scalar ( @{$data->[0]} );
  0            
274              
275 0 0         print qq{\nData entry matrix is: m = $A_m * n = $A_n. Feeding data to object.} if $self->{flags}{verbose};
276            
277 0           $self->{data} = $data;
278 0           $self->{A_m} = $A_m;
279 0           $self->{A_n} = $A_n;
280              
281             #y set flag
282 0           $self->{flags}{load} = 1;
283            
284 0           return;
285             }
286              
287             sub _deep_copy_references {
288 0     0     my $ref = shift;
289 0 0         if (!ref $ref) { $ref; }
  0 0          
    0          
290 0           elsif (ref $ref eq q{ARRAY} ) { [ map { _deep_copy_references($_) } @{$ref} ]; }
  0            
  0            
291 0           elsif (ref $ref eq q{HASH} ) { + { map { $_ => _deep_copy_references($ref->{$_}) } (keys %{$ref}) }; }
  0            
  0            
292 0           else { die "what type is $_?" }
293             }
294             # don´t return on this one - kills its before recursion - over-kill. its only operating on a LoL
295              
296             #/ just a sub not a method
297             sub _data_checks {
298 0     0     my $data_a_ref = shift;
299 0           my $rows = scalar ( @{$data_a_ref} );
  0            
300 0 0 0       croak qq{\nI need some data - there are too few rows in your data.\n} if ( !$rows || $rows == 1 );
301 0           my $cols = scalar ( @{$data_a_ref->[0]} );
  0            
302 0 0 0       croak qq{\nI need some data - there are too few columns in your data.\n} if ( !$cols || $cols == 1 );
303 0           for my $row (@{$data_a_ref}) {
  0            
304 0 0         croak qq{\n\nData set must be passed as ARRAY references.\n} if ( ref $row ne q{ARRAY} );
305 0 0         croak qq{\n\nAll rows must have the same number of columns.\n} if ( scalar( @{$row} ) != $cols );
  0            
306             }
307 0           return;
308             }
309              
310             #/////////////////////////////////////////////////////// ANALYSIS /////////////////////////////////////////////////////
311              
312             sub decompose {
313 0     0     my ( $self, $h_ref ) = @_;
314 0 0         croak qq{\nYou need to load some data first.} if ( !exists $self->{flags}{load});
315 0 0 0       croak qq{\nArguments must be passed as HASH reference.} if ( ( $h_ref ) && ( ref $h_ref ne q{HASH} ) );
316             # Golub-Reinsch / modified Golub-Reinsch / one-sided Jacobi orthogonalization
317 0 0         exists $h_ref->{algorithm} || print qq{\nUsing default Golub-Reinsch algorithm for SVD.};
318 0 0         my $sdv = exists $h_ref->{algorithm} ? $h_ref->{algorithm} : q{gd};
319 0 0         if ( $sdv eq q{eigen} ) { $self->_eigen }
  0            
320 0           else { $self->_svd($sdv) };
321 0           return;
322             }
323              
324             sub _svd {
325 0     0     my ( $self, $sdv ) = @_;
326             #y sdv specific check
327 0           $self->_sdv_check();
328            
329             #croak qq{\nI don\x27t recognise that value for the \x27algorithm\x27 option - requires \x27gd\x27, \x27mod\x27 }
330             # . qq{or \x27jacobi\x27 (defaults#to \x27gs\x27 without option).} if ( $sdv !~ /\A(gd|mod|jacobi)\z/xms );
331              
332 0           my $data = $self->{data};
333 0           my $A_m = $self->{A_m};
334 0           my $A_n = $self->{A_n};
335              
336 0           my $A_mat = Math::GSL::Linalg::SVD::Matrix->new($A_m,$A_n);
337              
338             #y only if we have mod algorithm
339             #my $workspace_mat = Math::GSL::Linalg::SVD::Matrix->new($A_n,$A_n) if ( $sdv eq q{mod} );
340            
341 0           my $V_mat = Math::GSL::Linalg::SVD::Matrix->new($A_n,$A_n);
342 0           my $S_vec = Math::GSL::Linalg::SVD::Vector->new($A_n);
343              
344             #y only needed if either gd or mod - thus either put if postfix here or put in twice in the anonysubs below
345 0 0 0       my $workspace_vec = Math::GSL::Linalg::SVD::Vector->new($A_n) if ( ( $sdv eq q{gd} ) || ( $sdv eq q{mod} ) );
346              
347             #$self->{C_objects} = { A => $A_mat, V => $V_mat, S => $S_vec, workspace_vector => $workspace_vec, };
348              
349             #/ load matrix - as private sub not method
350 0           &_load_matrix($A_mat, $data, $A_m, $A_n);
351              
352 0     0     my %algo = ( gd => sub { my $int = $A_mat->SV_decomp( $V_mat, $S_vec, $workspace_vec ); },
353 0 0   0     mod => sub { my $workspace_mat = Math::GSL::Linalg::SVD::Matrix->new($A_n,$A_n) if ( $sdv eq q{mod} );
354 0           my $int = $A_mat->SV_decomp_mod( $workspace_mat, $V_mat, $S_vec, $workspace_vec ); },
355 0     0     jacobi => sub { my $int = $A_mat->SV_decomp_jacobi($V_mat, $S_vec ); },
356 0           );
357            
358 0           my $type = $algo{$sdv};
359 0 0         croak qq{\nI don\x27t recognise that value for the \x27algorithm\x27 option - requires \x27gd\x27, \x27mod\x27 }
360             . qq{or \x27jacobi\x27 (defaults#to \x27gs\x27 without option).} if ( !defined $type );
361             #if ( $sdv !~ /\A(gd|mod|jacobi)\z/xms );
362              
363 0           $type->();
364              
365 0           my $d = [];
366 0           $d = &_get_d ($S_vec, $A_n);
367            
368             #/ no method call on thingy so need to put the data directly into the object here
369 0           $self->{d} = $d;
370              
371             #/ get U that is actually same matrix as A but with U data now - so its just as well we pre-stored the data as the original is gone
372 0           my $U = [];
373 0           $U = &_get_u ($A_mat, $A_m, $A_n);
374 0           $self->{U} = $U;
375              
376             #/ get the V matrix
377 0           my $V = [];
378 0           $V = &_get_u ($V_mat, $A_n, $A_n);
379 0           $self->{V} = $V;
380              
381             #y set flag for results
382 0           $self->{flags}{svd} = 1;
383              
384 0           return;
385             }
386              
387             sub _eigen {
388 0     0     my $self = shift;
389 0           $self->_eigen_check;
390 0           my $data = $self->{data};
391 0           my $A_m = $self->{A_m}; # checked: m == n
392              
393 0           my $Eigen_mat = Math::GSL::Linalg::SVD::Matrix->new($A_m,$A_m);
394 0           my $EV_mat = Math::GSL::Linalg::SVD::Matrix->new($A_m,$A_m);
395 0           my $EV_vec = Math::GSL::Linalg::SVD::Vector->new($A_m);
396 0           my $workpace_eigen = Math::GSL::Linalg::SVD::Eigen->new(4*$A_m);
397              
398 0           &_load_matrix($Eigen_mat, $data, $A_m, $A_m);
399              
400 0           my $int = $Eigen_mat->Eigen_decomp($EV_vec, $EV_mat, $workpace_eigen );
401              
402 0           my $e_vals = &_get_d ($EV_vec, $A_m);
403 0           $self->{eigen_values} = $e_vals;
404            
405             #my $e_vecs = &_get_v ($EV_mat, $A_m);
406 0           my $e_vecs = &_get_u ($EV_mat, $A_m, $A_m);
407 0           $e_vecs = _transpose($e_vecs);
408 0           $self->{eigen_vectors} = $e_vecs;
409            
410             #y set flag for results
411 0           $self->{flags}{eigen} = 1;
412              
413 0           return;
414             }
415              
416             # sub routine not method
417             sub _transpose {
418             #y basic sub
419 0     0     my $a_ref = shift;
420 0           my $done = [];
421 0           for my $col ( 0..$#{$a_ref->[0]} ) {
  0            
422 0           push @{$done}, [ map { $_->[$col] } @{$a_ref} ];
  0            
  0            
  0            
423             }
424 0           return $done;
425             }
426              
427             sub _eigen_check {
428 0     0     my $self = shift;
429 0           my $data = $self->{data};
430 0           my $A_m = $self->{A_m};
431 0           my $A_n = $self->{A_n};
432             # twat:
433             # croak qq{\nEigen decomposition requires a square matrix.} if ( $A_m == $A_n );
434 0 0         croak qq{\nEigen decomposition requires a square matrix.} if ( $A_m != $A_n );
435 0           return;
436             }
437              
438             sub _sdv_check {
439 0     0     my $self = shift;
440 0           my $data = $self->{data};
441 0           my $A_m = $self->{A_m};
442 0           my $A_n = $self->{A_n};
443 0 0         croak qq{\nSVD for matrix MxN with M $A_m); # duh... A_m >= A_n
444 0           return;
445             }
446              
447             sub _load_matrix {
448 0     0     my ( $A_mat, $data, $A_m, $A_n) = @_;
449              
450 0           for my $row ( (0..$A_m-1) ) {
451 0           for my $col ( (0..$A_n-1) ) {
452 0           $A_mat->matrix_set($row,$col,$data->[$row][$col]);
453             }
454             }
455             # return $A_mat;
456 0           return;
457             }
458              
459             sub _get_d {
460 0     0     my ($S_vec, $A_n) = @_;
461 0           my $d = [];
462 0           for my $row ( (0..$A_n-1) ) {
463 0           my $val = $S_vec->vector_get($row);
464             #y nao faz diferenenca aqui...
465             #$d->[$row] = $val;
466 0           push @{$d}, $val;
  0            
467             }
468             #/ as sub we´ll need to return the list and not put it into the object
469             #$self->{d} = $d;
470             # return;
471 0           return $d;
472             }
473              
474             sub _get_u {
475             #/ no need for method just private sub
476             #my ($self, $S_vec, $A_n) = @_;
477             #/ get U that is actually same matrix as A but with U data now - so its just as well we pre-stored the data as the original is gone
478 0     0     my ($A_mat, $A_m, $A_n) = @_;
479            
480 0           my $U = [];
481 0           for my $row ( (0..$A_m-1) ) {
482 0           for my $col ( (0..$A_n-1) ) {
483 0           my $val = $A_mat->matrix_get($row,$col);
484            
485             #/ can´t push - need to use each index individually!
486             #push @{$U}, $val;
487 0           $U->[$row][$col] = $val;
488             }
489             }
490            
491             #/ as sub we´ll need to return the list and not put it into the object
492             #$self->{d} = $d;
493             # return;
494 0           return $U;
495             }
496              
497             sub results {
498 0     0     my $self = shift;
499 0 0         croak qq{\nThis method is called in LIST context.} if !wantarray;
500 0 0 0       if ( ( $self->{flags}{svd} ) && ( $self->{flags}{eigen} ) ) {
    0          
    0          
501 0           croak qq{\nYou performed both SVD and Eigen decomp. Which did you want to do?};
502             }
503             elsif ( $self->{flags}{svd} ) {
504 0 0         print qq{\nPassing SVD results data.} if $self->{flags}{verbose};
505 0           return $self->{d}, $self->{U}, $self->{V}, $self->{data};
506             }
507             # silly, but...
508             elsif ( $self->{flags}{eigen} ) {
509 0 0         print qq{\nPassing Eigen results data.} if $self->{flags}{verbose};
510 0           return $self->{eigen_values}, $self->{eigen_vectors}, $self->{data};
511             }
512             # perlcritic plesure...
513 0           return;
514             }
515              
516              
517             1;
518              
519             __END__