File Coverage

lib/ICC/Support/rbf.pm
Criterion Covered Total %
statement 17 155 10.9
branch 2 108 1.8
condition 1 24 4.1
subroutine 5 13 38.4
pod 1 8 12.5
total 26 308 8.4


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