File Coverage

blib/lib/ICC/Support/rbf.pm
Criterion Covered Total %
statement 14 152 9.2
branch 2 108 1.8
condition 1 24 4.1
subroutine 4 12 33.3
pod 1 8 12.5
total 22 304 7.2


line stmt bran cond sub pod time code
1             package ICC::Support::rbf;
2              
3 2     2   81471 use strict;
  2         12  
  2         51  
4 2     2   10 use Carp;
  2         5  
  2         130  
5              
6             our $VERSION = 0.21;
7              
8             # revised 2016-05-17
9             #
10             # Copyright © 2004-2020 by William B. Birkett
11              
12             # inherit from Shared
13 2     2   378 use parent qw(ICC::Shared);
  2         274  
  2         10  
14              
15             # parameter count by function type
16             my @Np = (1, 1, 1, 1, 2, 0, 1);
17              
18             # create new rbf object
19             # parameters: ([ref_to_parameter_array, ref_to_center_array, [ref_to_covariance_matrix]])
20             # returns: (ref_to_object)
21             sub new {
22              
23             # get object class
24 1     1 0 669 my $class = shift();
25            
26             # create empty rbf object
27 1         4 my $self = [
28             {}, # object header
29             [], # parameter array
30             [], # center array
31             [] # inverse covariance matrix
32             ];
33              
34             # if two or three parameters supplied
35 1 50 33     10 if (@_ == 2 || @_ == 3) {
    50          
36            
37             # verify parameter array reference
38 0 0       0 (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
39            
40             # verify function type
41 0 0 0     0 ($_[0][0] == int($_[0][0]) && defined($Np[$_[0][0]])) or croak('invalid function type');
42            
43             # verify number of parameters
44 0 0       0 ($#{$_[0]} == $Np[$_[0][0]]) or croak('wrong number of parameters');
  0         0  
45            
46             # copy parameter array
47 0         0 $self->[1] = [@{shift()}];
  0         0  
48            
49             # verify center array reference
50 0 0       0 (ref($_[0]) eq 'ARRAY') or croak('not an array reference');
51            
52             # copy center array
53 0         0 $self->[2] = [@{shift()}];
  0         0  
54            
55             # if third parameter (covariance matrix) supplied
56 0 0       0 if (@_) {
57            
58             # invert covariance matrix
59 0         0 (my $info, $self->[3]) = ICC::Support::Lapack::inv(shift());
60            
61             # quit on error
62 0 0       0 ($info == 0) or croak("inversion of convariance matrix failed: info= $info");
63            
64             # verify center and covariance are same size
65 0 0       0 ($#{$self->[2]} == $#{$self->[3]}) or croak('center and covariance are different sizes');
  0         0  
  0         0  
66            
67             }
68            
69             } elsif (@_) {
70            
71             # wrong number of parameters
72 0         0 croak('wrong number of parameters');
73            
74             }
75              
76             # bless object
77 1         2 bless($self, $class);
78            
79             # return object reference
80 1         2 return($self);
81              
82             }
83              
84             # compute rbf function
85             # parameters: (ref_to_input_array)
86             # returns: (output_value)
87             sub transform {
88            
89             # get parameters
90 0     0 0   my ($self, $in) = @_;
91              
92             # local variables
93 0           my ($r, $array, $type);
94            
95             # compute radius
96 0           $r = radius($self, $in);
97            
98             # get parameter array reference
99 0           $array = $self->[1];
100            
101             # get function type
102 0           $type = $array->[0];
103            
104             # function type 0 (Gaussian)
105 0 0         if ($type == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
106            
107             # return value
108 0           return(exp(-($array->[1] * $r)**2));
109            
110             # function type 1 (multiquadric)
111             } elsif ($type == 1) {
112            
113             # return value
114 0           return(sqrt(1 + ($array->[1] * $r)**2));
115            
116             # function type 2 (inverse quadratic)
117             } elsif ($type == 2) {
118            
119             # return value
120 0           return(1/(1 + ($array->[1] * $r)**2));
121            
122             # function type 3 (inverse multiquadric)
123             } elsif ($type == 3) {
124            
125             # return value
126 0           return(1/(sqrt(1 + ($array->[1] * $r)**2)));
127            
128             # function type 4 (generalized multiquadric)
129             } elsif ($type == 4) {
130              
131             # return value
132 0           return((1 + ($array->[1] * $r)**2)**$array->[2]);
133              
134             # function type 5 (thin plate spline)
135             } elsif ($type == 5) {
136            
137             # if r is 0
138 0 0         if ($r == 0) {
139            
140             # return value
141 0           return(0);
142            
143             } else {
144            
145             # return value
146 0           return($r**2 * log($r));
147            
148             }
149            
150             # function type 6 (polyharmonic spline)
151             } elsif ($type == 6) {
152              
153             # if k is odd
154 0 0         if ($array->[1] % 2) {
155              
156             # return value
157 0           return($r**$array->[1]);
158              
159             } else {
160            
161             # if r is 0
162 0 0         if ($r == 0) {
163            
164             # return value
165 0           return(0);
166            
167             } else {
168            
169             # return value
170 0           return($r**$array->[1] * log($r));
171            
172             }
173            
174             }
175              
176             } else {
177            
178             # error
179 0           croak('invalid rbf function type');
180            
181             }
182            
183             }
184              
185             # compute rbf Jacobian
186             # parameters: (ref_to_input_array)
187             # returns: (Jacobian_vector, [output_value])
188             sub jacobian {
189            
190             # get parameters
191 0     0 0   my ($self, $in) = @_;
192              
193             # local variables
194 0           my ($r, $jac, $array, $type);
195 0           my ($er, $beta, $k, $derv, $out);
196            
197             # compute radius and radial Jacobian
198 0           ($r, $jac) = _jacobian($self, $in);
199            
200             # get parameter array reference
201 0           $array = $self->[1];
202            
203             # get function type
204 0           $type = $array->[0];
205            
206             # function type 0 (Gaussian)
207 0 0         if ($type == 0) {
    0          
    0          
    0          
    0          
    0          
    0          
208            
209             # compute ɛr
210 0           $er = $array->[1] * $r;
211            
212             # compute dɸ/dr
213 0           $derv = -2 * $array->[1] * $er * exp(-$er**2);
214            
215             # compute output value, if requested
216 0 0         $out = exp(-$er**2) if wantarray;
217            
218             # function type 1 (multiquadric)
219             } elsif ($type == 1) {
220            
221             # compute ɛr
222 0           $er = $array->[1] * $r;
223            
224             # compute dɸ/dr
225 0           $derv = $array->[1] * $er/sqrt(1 + $er**2);
226            
227             # compute output value, if requested
228 0 0         $out = sqrt(1 + $er**2) if wantarray;
229            
230             # function type 2 (inverse quadratic)
231             } elsif ($type == 2) {
232            
233             # compute ɛr
234 0           $er = $array->[1] * $r;
235            
236             # compute dɸ/dr
237 0           $derv = -2 * $array->[1] * $er/(1 + $er**2)**2;
238            
239             # compute output value, if requested
240 0 0         $out = 1/(1 + $er**2) if wantarray;
241            
242             # function type 3 (inverse multiquadric)
243             } elsif ($type == 3) {
244            
245             # compute ɛr
246 0           $er = $array->[1] * $r;
247            
248             # compute dɸ/dr
249 0           $derv = -$array->[1] * $er/(1 + $er**2)**1.5;
250            
251             # compute output value, if requested
252 0 0         $out = 1/(sqrt(1 + $er**2)) if wantarray;
253            
254             # function type 4 (generalized multiquadric)
255             } elsif ($type == 4) {
256            
257             # compute ɛr
258 0           $er = $array->[1] * $r;
259            
260             # get β
261 0           $beta = $array->[2];
262            
263             # compute dɸ/dr
264 0           $derv = 2 * $beta * $array->[1] * $er * (1 + $er**2)**($beta - 1);
265            
266             # compute output value, if requested
267 0 0         $out = (1 + $er**2)**$beta if wantarray;
268            
269             # function type 5 (thin plate spline)
270             } elsif ($type == 5) {
271            
272             # if r is 0
273 0 0         if ($r == 0) {
274            
275             # dɸ/dr = 0
276 0           $derv = 0;
277            
278             # compute output value, if requested
279 0 0         $out = 0 if wantarray;
280            
281             } else {
282            
283             # compute dɸ/dr
284 0           $derv = $r * (2 * log($r) + 1);
285            
286             # compute output value, if requested
287 0 0         $out = $r**2 * log($r) if wantarray;
288            
289             }
290            
291             # function type 6 (polyharmonic spline)
292             } elsif ($type == 6) {
293            
294             # get k
295 0           $k = $array->[1];
296            
297             # if k is odd
298 0 0         if ($k % 2) {
299            
300             # compute dɸ/dr
301 0           $derv = $k * $r**($k - 1);
302            
303             # compute output value, if requested
304 0 0         $out = $r**$k if wantarray;
305            
306             } else {
307            
308             # if r is 0
309 0 0         if ($r == 0) {
310            
311             # dɸ/dr = 0
312 0           $derv = 0;
313            
314             # compute output value, if requested
315 0 0         $out = 0 if wantarray;
316            
317             } else {
318            
319             # compute dɸ/dr
320 0           $derv = $r**($k - 1) * ($k * log($r) + 1);
321            
322             # compute output value, if requested
323 0 0         $out = $r**$k * log($r) if wantarray;
324            
325             }
326            
327             }
328            
329             } else {
330            
331             # error
332 0           croak('invalid rbf function type');
333            
334             }
335            
336             # for each Jacobian element
337 0           for my $i (0 .. $#{$jac}) {
  0            
338            
339             # multiply by dɸ/dr
340 0           $jac->[$i] *= $derv;
341            
342             }
343            
344             # if output requested
345 0 0         if (wantarray) {
346            
347             # return Jacobian and output
348 0           return($jac, $out);
349            
350             } else {
351            
352             # return Jacobian
353 0           return($jac);
354            
355             }
356            
357             }
358              
359             # get/set parameter array
360             # parameters: ([ref_to_parameter_array])
361             # returns: (ref_to_parameter_array)
362             sub array {
363              
364             # get object reference
365 0     0 0   my $self = shift();
366            
367             # local variables
368 0           my ($array, $type);
369            
370             # if parameter
371 0 0         if (@_) {
372            
373             # get array reference
374 0           $array = shift();
375            
376             # verify array reference
377 0 0         (ref($array) eq 'ARRAY') or croak('not an array reference');
378            
379             # get function type
380 0           $type = $array->[0];
381            
382             # verify function type
383 0 0 0       ($type == int($type) && $type >= 0 && defined($Np[$_[0][0]])) or croak('invalid function type');
      0        
384            
385             # verify number of parameters
386 0 0         ($#{$array} == $Np[$type]) or croak('wrong number of parameters');
  0            
387            
388             # set array reference
389 0           $self->[1] = $array;
390            
391             }
392            
393             # return center array reference
394 0           return($self->[1]);
395              
396             }
397              
398             # get/set center
399             # parameters: ([ref_to_center_array])
400             # returns: (ref_to_center_array)
401             sub center {
402              
403             # get object reference
404 0     0 0   my $self = shift();
405            
406             # local variables
407 0           my ($array);
408            
409             # if parameter
410 0 0         if (@_) {
411            
412             # get array reference
413 0           $array = shift();
414            
415             # verify array reference
416 0 0         (ref($array) eq 'ARRAY') or croak('not an array reference');
417            
418             # set array reference
419 0           $self->[2] = $array;
420            
421             }
422            
423             # return center array reference
424 0           return($self->[2]);
425              
426             }
427              
428             # get/set inverse covariance matrix
429             # parameters: ([ref_to_inverse_covariance_matrix])
430             # returns: (ref_to_inverse_covariance_matrix)
431             sub matrix {
432              
433             # get object reference
434 0     0 0   my $self = shift();
435            
436             # local variables
437 0           my ($matrix);
438            
439             # if parameter
440 0 0         if (@_) {
441            
442             # get matrix reference
443 0           $matrix = shift();
444            
445             # verify matrix reference
446 0 0 0       (ref($matrix) eq 'ARRAY' && ref($matrix->[0]) eq 'ARRAY') or croak('not a matrix reference');
447            
448             # set matrix reference
449 0           $self->[3] = $matrix;
450            
451             }
452            
453             # return matrix reference
454 0           return($self->[3]);
455              
456             }
457              
458             # print object contents to string
459             # format is an array structure
460             # parameter: ([format])
461             # returns: (string)
462             sub sdump {
463              
464             # get parameters
465 0     0 1   my ($self, $p) = @_;
466              
467             # local variables
468 0           my ($s, $fmt);
469              
470             # resolve parameter to an array reference
471 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
472              
473             # get format string
474 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
475              
476             # set string to object ID
477 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
478              
479             # return
480 0           return($s);
481              
482             }
483              
484             # compute radius
485             # note: no error checking
486             # parameters: (ref_to_input_vector)
487             # returns: (radius)
488             sub radius {
489            
490             # get parameters
491 0     0 0   my ($self, $in) = @_;
492            
493             # if object has covariance matrix
494 0 0 0       if (defined($self->[3]) && (0 != @{$self->[3]})) {
  0            
495            
496             # return Mahalanobis distance
497 0           return(ICC::Support::Lapack::mahal($in, $self->[2], $self->[3]));
498            
499             } else {
500            
501             # return Euclidean distance
502 0           return(ICC::Support::Lapack::euclid($in, $self->[2]));
503            
504             }
505            
506             }
507              
508             # compute radial Jacobian
509             # note: no error checking
510             # parameters: (ref_to_object, ref_to_input_vector)
511             # returns: (radius, jacobian_vector)
512             sub _jacobian {
513            
514             # get parameters
515 0     0     my ($self, $in) = @_;
516            
517             # local variables
518 0           my ($r, $jac, $wwt);
519            
520             # for each dimension
521 0           for my $i (0 .. $#{$self->[2]}) {
  0            
522            
523             # compute Jacobian element
524 0           $jac->[$i] = ($in->[$i] - $self->[2][$i]);
525            
526             }
527            
528             # if object has covariance matrix
529 0 0 0       if (defined($self->[3]) && @{$self->[3]}) {
  0            
530            
531             # radius is Mahalanobis distance
532 0           $r = ICC::Support::Lapack::mahal($in, $self->[2], $self->[3]);
533            
534             # for each row
535 0           for my $i (0 .. $#{$self->[3]}) {
  0            
536            
537             # for each column
538 0           for my $j (0 .. $#{$self->[3]}) {
  0            
539            
540             # set (W + Wᵀ)/2 matrix element
541 0           $wwt->[$i][$j] = ($self->[3][$i][$j] + $self->[3][$j][$i])/2;
542            
543             }
544            
545             }
546            
547             # multiply Jacobian by inverse covariance matrix
548 0           $jac = ICC::Support::Lapack::vec_xplus($wwt, $jac, {'trans' => 'T'});
549            
550             } else {
551            
552             # radius is Euclidean distance
553 0           $r = ICC::Support::Lapack::euclid($in, $self->[2]);
554            
555             }
556            
557             # if radius is zero
558 0 0         if ($r == 0) {
559            
560             # set Jacobian to all ones
561 0           $jac = [(1) x @{$self->[2]}];
  0            
562            
563             } else {
564            
565             # for each dimension
566 0           for my $i (0 .. $#{$self->[2]}) {
  0            
567            
568             # divide by radius
569 0           $jac->[$i] /= $r;
570            
571             }
572            
573             }
574            
575             # return radius and Jacobian vector
576 0           return($r, $jac);
577            
578             }
579              
580             1;