File Coverage

lib/ICC/Support/nNET.pm
Criterion Covered Total %
statement 24 324 7.4
branch 1 146 0.6
condition 0 66 0.0
subroutine 7 27 25.9
pod 1 11 9.0
total 33 574 5.7


line stmt bran cond sub pod time code
1             package ICC::Support::nNET;
2              
3 2     2   101951 use strict;
  2         14  
  2         60  
4 2     2   10 use Carp;
  2         4  
  2         151  
5              
6             our $VERSION = 0.31;
7              
8             # revised 2016-05-17
9             #
10             # Copyright © 2004-2019 by William B. Birkett
11              
12             # add development directory
13 2     2   482 use lib 'lib';
  2         677  
  2         14  
14              
15             # inherit from Shared
16 2     2   661 use parent qw(ICC::Shared);
  2         304  
  2         12  
17              
18             # use POSIX math
19 2     2   122 use POSIX ();
  2         3  
  2         40  
20              
21             # enable static variables
22 2     2   10 use feature 'state';
  2         4  
  2         7985  
23              
24             # list of valid kernel types
25             my @types = qw(CODE ICC::Support::rbf);
26              
27             # create new nNET object
28             # hash may contain pointers to header, kernel, matrix, offset or init
29             # kernel is a reference to an array of kernel objects or CODE references
30             # matrix is a 2D array reference or Math::Matrix object
31             # offset is a 1D array reference
32             # hash keys are: ('header', 'kernel', 'matrix', 'offset', 'init')
33             # parameters: ([ref_to_attribute_hash])
34             # returns: (ref_to_object)
35             sub new {
36              
37             # get object class
38 1     1 0 898 my $class = shift;
39              
40             # local variable
41 1         3 my ($code);
42              
43             # create empty nNET object
44 1         4 my $self = [
45             {}, # object header
46             [], # kernel array
47             [], # matrix matrix
48             [] # offset vector
49             ];
50              
51             # if there are parameters
52 1 50       6 if (@_) {
53            
54             # if one parameter, a hash reference
55 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'HASH') {
56            
57             # make new nNET object from attribute hash
58 0         0 _new_from_hash($self, shift());
59            
60             # initialize object (if CODE reference defined)
61 0 0       0 (defined($code = $self->[0]{'init'}) && &$code);
62            
63             } else {
64            
65             # error
66 0         0 croak('nNET parameter must be a hash reference');
67            
68             }
69            
70             }
71              
72             # bless object
73 1         3 bless($self, $class);
74              
75             # return object reference
76 1         3 return($self);
77              
78             }
79              
80             # initialize object
81             # calls 'init' CODE reference, if any
82             # used when retrieving an nNET object using Storable
83             sub init {
84              
85             # get object reference
86 0     0 0   my $self = shift();
87              
88             # local variable
89 0           my ($code);
90              
91             # initialize object (if CODE reference defined)
92 0 0         (defined($code = $self->[0]{'init'}) && &$code);
93              
94             }
95              
96             # fit nNET object to data
97             # determines optimum 'matrix' and 'offset' arrays
98             # kernel nodes are not modified by this method
99             # uses LAPACK dgelsd function to perform a least-squares fit
100             # fitting is done with or without offset, according to offset_flag
101             # fitting is done to output or input-output difference, according to diff_mode_flag
102             # input and output are 2D array references or Math::Matrix objects
103             # parameters: (ref_to_input_array, ref_to_output_array, [offset_flag, [diff_mode_flag]])
104             # returns: (dgelsd_info_value)
105             sub fit {
106              
107             # get parameters
108 0     0 0   my ($self, $in, $out, $oflag, $dflag) = @_;
109              
110             # local variables
111 0           my ($dif, $info, $ab);
112              
113             # resolve offset flag
114 0 0         $oflag = 0 if (! defined($oflag));
115              
116             # verify input array
117 0 0 0       (ref($in) eq 'ARRAY' && ref($in->[0]) eq 'ARRAY' && ! ref($in->[0][0])) || (UNIVERSAL::isa($in, 'Math::Matrix')) or croak('fit input not a 2-D array reference');
      0        
      0        
118              
119             # verify output array
120 0 0 0       (ref($out) eq 'ARRAY' && ref($out->[0]) eq 'ARRAY' && ! ref($out->[0][0])) || (UNIVERSAL::isa($out, 'Math::Matrix')) or croak('fit output not a 2-D array reference');
      0        
      0        
121              
122             # verify array dimensions
123 0 0         ($#{$in} == $#{$out}) or croak('fit input and output arrays have different number of rows');
  0            
  0            
124              
125             # if difference mode
126 0 0         if ($dflag) {
127            
128             # verify array dimensions
129 0 0         ($#{$in->[0]} == $#{$out->[0]}) or croak('fit input and output arrays have different number of columns');
  0            
  0            
130            
131             # for each row
132 0           for my $i (0 .. $#{$in}) {
  0            
133            
134             # for each column
135 0           for my $j (0 .. $#{$in->[0]}) {
  0            
136            
137             # compute output-input difference
138 0           $dif->[$i][$j] = $out->[$i][$j] - $in->[$i][$j];
139            
140             }
141            
142             }
143            
144             }
145              
146             # fit the matrix (hidden values to output or difference values)
147 0 0         ($info, $ab) = ICC::Support::Lapack::nNET_fit(_hidden2($self, $in), $dflag ? $dif : $out, $oflag);
148              
149             # check result
150 0 0         carp('fit failed - bad parameter when calling dgelsd') if ($info < 0);
151 0 0         carp('fit failed - SVD algorithm failed to converge') if ($info > 0);
152              
153             # initialize matrix
154 0           $self->[2] = [];
155              
156             # for each output
157 0           for my $i (0 .. $#{$out->[0]}) {
  0            
158            
159             # for each kernel node
160 0           for my $j (0 .. $#{$self->[1]}) {
  0            
161            
162             # set matrix element (transposing)
163 0           $self->[2][$i][$j] = $ab->[$j][$i];
164            
165             }
166            
167             }
168            
169             # if offset flag
170 0 0         if ($oflag) {
171            
172             # set offset
173 0           $self->[3] = [@{$ab->[$#{$self->[1]} + 1]}];
  0            
  0            
174            
175             } else {
176            
177             # no offset
178 0           undef($self->[3]);
179            
180             }
181            
182             # if difference flag
183 0 0         if ($dflag) {
184            
185             # for each row
186 0           for my $i (0 .. $#{$self->[2]}) {
  0            
187            
188             # for each column
189 0           for my $j (0 .. $#{$self->[2]}) {
  0            
190            
191             # add identity matrix element
192 0 0         $self->[2][$i][$j + $#{$self->[1]} + 1] = ($i == $j) ? 1 : 0;
  0            
193            
194             }
195            
196             }
197            
198             }
199              
200             # return info value
201 0           return($info);
202              
203             }
204              
205             # get/set reference to header hash
206             # parameters: ([ref_to_new_hash])
207             # returns: (ref_to_hash)
208             sub header {
209              
210             # get object reference
211 0     0 0   my $self = shift();
212              
213             # if there are parameters
214 0 0         if (@_) {
215            
216             # if one parameter, a hash reference
217 0 0 0       if (@_ == 1 && ref($_[0]) eq 'HASH') {
218            
219             # set header to new hash
220 0           $self->[0] = {%{shift()}};
  0            
221            
222             } else {
223            
224             # error
225 0           croak('parameter must be a hash reference');
226            
227             }
228            
229             }
230              
231             # return reference
232 0           return($self->[0]);
233              
234             }
235              
236             # get/set kernel array reference
237             # parameters: ([ref_to_array])
238             # returns: (ref_to_array)
239             sub kernel {
240              
241             # get object reference
242 0     0 0   my $self = shift();
243              
244             # if one parameter supplied
245 0 0         if (@_ == 1) {
    0          
246            
247             # get parameter
248 0           my $array = shift;
249            
250             # if an array reference
251 0 0         if (ref($array) eq 'ARRAY') {
252            
253             # initialize array
254 0           $self->[1] = [];
255            
256             # for each array element
257 0           for my $i (0 .. $#{$array}) {
  0            
258            
259             # if array element is a valid kernel type
260 0 0         if (grep {ref($array->[$i]) eq $_} @types) {
  0            
261            
262             # add array element
263 0           $self->[1][$i] = $array->[$i];
264            
265             } else {
266            
267             # wrong data type
268 0           croak('invalid nNET kernel array element');
269            
270             }
271            
272             }
273            
274             } else {
275            
276             # wrong data type
277 0           croak('nNET kernel attribute must be an array reference');
278            
279             }
280            
281             } elsif (@_) {
282            
283             # error
284 0           croak('too many parameters');
285            
286             }
287              
288             # return kernel array reference
289 0           return($self->[1]);
290              
291             }
292              
293             # get/set reference to matrix
294             # parameters: ([ref_to_new_array])
295             # returns: (ref_to_array)
296             sub matrix {
297              
298             # get object reference
299 0     0 0   my $self = shift();
300              
301             # if there are parameters
302 0 0         if (@_) {
303            
304             # if one parameter, a reference to 2D array
305 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY' && ref($_[0][0]) eq 'ARRAY') {
    0 0        
      0        
306            
307             # set object element
308 0           $self->[2] = Storable::dclone(shift());
309            
310             # if one parameter, a reference to Math::Matrix object
311             } elsif (@_ == 1 && UNIVERSAL::isa($_[0], 'Math::Matrix')) {
312            
313             # set object element
314 0           $self->[2] = Storable::dclone([@{shift()}]);
  0            
315            
316             } else {
317            
318             # wrong data type
319 0           croak('nNET matrix attribute must be an array reference or Math::Matrix object');
320            
321             }
322            
323             }
324              
325             # return matrix reference
326 0           return($self->[2]);
327              
328             }
329              
330             # get/set reference to offset array
331             # parameters: ([ref_to_new_array])
332             # returns: (ref_to_array)
333             sub offset {
334              
335             # get object reference
336 0     0 0   my $self = shift();
337              
338             # if there are parameters
339 0 0         if (@_) {
340            
341             # if one parameter, a reference to an array of scalars
342 0 0 0       if (@_ == 1 && ref($_[0]) eq 'ARRAY' && @{$_[0]} == grep {! ref()} @{$_[0]}) {
  0   0        
  0            
  0            
343            
344             # set object element
345 0           $self->[3] = [@{shift()}];
  0            
346            
347             } else {
348            
349             # wrong data type
350 0           croak('nNET offset attribute must be an array reference');
351            
352             }
353            
354             }
355              
356             # return offset reference
357 0           return($self->[3]);
358              
359             }
360              
361             # transform data
362             # supported input types:
363             # parameters: (list, [hash])
364             # parameters: (vector, [hash])
365             # parameters: (matrix, [hash])
366             # parameters: (Math::Matrix_object, [hash])
367             # parameters: (structure, [hash])
368             # returns: (same_type_as_input)
369             sub transform {
370              
371             # set hash value (0 or 1)
372 0 0   0 0   my $h = ref($_[-1]) eq 'HASH' ? 1 : 0;
373              
374             # if input a 'Math::Matrix' object
375 0 0 0       if (@_ == $h + 2 && UNIVERSAL::isa($_[1], 'Math::Matrix')) {
    0 0        
    0          
376            
377             # call matrix transform
378 0           &_trans2;
379            
380             # if input an array reference
381             } elsif (@_ == $h + 2 && ref($_[1]) eq 'ARRAY') {
382            
383             # if array contains numbers (vector)
384 0 0 0       if (! ref($_[1][0]) && @{$_[1]} == grep {Scalar::Util::looks_like_number($_)} @{$_[1]}) {
  0 0 0        
  0            
  0            
385            
386             # call vector transform
387 0           &_trans1;
388            
389             # if array contains vectors (2-D array)
390 0 0         } elsif (ref($_[1][0]) eq 'ARRAY' && @{$_[1]} == grep {ref($_) eq 'ARRAY' && Scalar::Util::looks_like_number($_->[0])} @{$_[1]}) {
  0            
  0            
391            
392             # call matrix transform
393 0           &_trans2;
394            
395             } else {
396            
397             # call structure transform
398 0           &_trans3;
399            
400             }
401            
402             # if input a list (of numbers)
403 0           } elsif (@_ == $h + 1 + grep {Scalar::Util::looks_like_number($_)} @_) {
404            
405             # call list transform
406 0           &_trans0;
407            
408             } else {
409            
410             # error
411 0           croak('invalid transform input');
412            
413             }
414              
415             }
416              
417             # inverse transform
418             # note: number of undefined output values must equal number of defined input values
419             # note: the input and output vectors contain the final solution on return
420             # hash key 'init' specifies initial value vector
421             # parameters: (input_vector, output_vector, [hash])
422             # returns: (RMS_error_value)
423             sub inverse {
424              
425             # get parameters
426 0     0 0   my ($self, $in, $out, $hash) = @_;
427              
428             # local variables
429 0           my ($i, $j, @si, @so, $init);
430 0           my ($int, $jac, $mat, $delta);
431 0           my ($max, $elim, $dlim, $accum, $error);
432              
433             # initialize indices
434 0           $i = $j = -1;
435              
436             # build slice arrays while validating input and output arrays
437 0 0         ((grep {$i++; defined() && push(@si, $i)} @{$in}) == (grep {$j++; ! defined() && push(@so, $j)} @{$out})) or croak('wrong number of undefined values');
  0 0          
  0 0          
  0            
  0            
  0            
  0            
438              
439             # get init array
440 0           $init = $hash->{'init'};
441              
442             # for each undefined output value
443 0           for my $i (@so) {
444            
445             # set to supplied initial value or 0.5
446 0 0         $out->[$i] = defined($init->[$i]) ? $init->[$i] : 0.5;
447            
448             }
449              
450             # set maximum loop count
451 0   0       $max = $hash->{'inv_max'} || 10;
452              
453             # loop error limit
454 0   0       $elim = $hash->{'inv_elim'} || 1E-6;
455              
456             # set delta limit
457 0   0       $dlim = $hash->{'inv_dlim'} || 0.5;
458              
459             # create empty solution matrix
460 0           $mat = Math::Matrix->new([]);
461              
462             # compute initial transform values
463 0           ($jac, $int) = jacobian($self, $out, $hash);
464              
465             # solution loop
466 0           for (1 .. $max) {
467            
468             # for each input
469 0           for my $i (0 .. $#si) {
470            
471             # for each output
472 0           for my $j (0 .. $#so) {
473            
474             # copy Jacobian value to solution matrix
475 0           $mat->[$i][$j] = $jac->[$si[$i]][$so[$j]];
476            
477             }
478            
479             # save residual value to solution matrix
480 0           $mat->[$i][$#si + 1] = $in->[$si[$i]] - $int->[$si[$i]];
481            
482             }
483            
484             # solve for delta values
485 0           $delta = $mat->solve;
486            
487             # for each output value
488 0           for my $i (0 .. $#so) {
489            
490             # add delta (limited using hyperbolic tangent)
491 0           $out->[$so[$i]] += POSIX::tanh($delta->[$i][0]/$dlim) * $dlim;
492            
493             }
494            
495             # compute updated transform values
496 0           ($jac, $int) = jacobian($self, $out, $hash);
497            
498             # initialize error accumulator
499 0           $accum = 0;
500            
501             # for each input
502 0           for my $i (0 .. $#si) {
503            
504             # accumulate delta squared
505 0           $accum += ($in->[$si[$i]] - $int->[$si[$i]])**2;
506            
507             }
508            
509             # compute RMS error
510 0           $error = sqrt($accum/@si);
511            
512             # if error less than limit
513 0 0         last if ($error < $elim);
514            
515             }
516              
517             # update input vector with final values
518 0           @{$in} = @{$int};
  0            
  0            
519              
520             # return
521 0           return($error);
522              
523             }
524              
525             # compute Jacobian matrix
526             # parameters: (input_vector, [hash])
527             # returns: (Jacobian_matrix, [output_vector])
528             sub jacobian {
529              
530             # get parameters
531 0     0 0   my ($self, $in, $hash) = @_;
532              
533             # local variables
534 0           my ($jac, $out);
535              
536             # check if ICC::Support::Lapack module is loaded
537 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
538              
539             # compute hidden Jacobian and output
540 0           ($jac, $out) = _hidden3($self, $in);
541              
542             # if ICC::Support::Lapack module is loaded
543 0 0         if ($lapack) {
544            
545             # if output values wanted
546 0 0         if (wantarray) {
547            
548             # return Jacobian and output
549 0           return(bless(ICC::Support::Lapack::mat_xplus($self->[2], $jac), 'Math::Matrix'), ICC::Support::Lapack::matf_vec_trans($out, $self->[2], $self->[3]));
550            
551             } else {
552            
553             # return Jacobian only
554 0           return(bless(ICC::Support::Lapack::mat_xplus($self->[2], $jac), 'Math::Matrix'));
555            
556             }
557            
558             } else {
559            
560 0           croak('method not yet implemented');
561            
562             }
563            
564             }
565              
566             # print object contents to string
567             # format is an array structure
568             # parameter: ([format])
569             # returns: (string)
570             sub sdump {
571              
572             # get parameters
573 0     0 1   my ($self, $p) = @_;
574              
575             # local variables
576 0           my ($s, $fmt);
577              
578             # resolve parameter to an array reference
579 0 0         $p = defined($p) ? ref($p) eq 'ARRAY' ? $p : [$p] : [];
    0          
580              
581             # get format string
582 0 0 0       $fmt = defined($p->[0]) && ! ref($p->[0]) ? $p->[0] : 'undef';
583              
584             # set string to object ID
585 0           $s = sprintf("'%s' object, (0x%x)\n", ref($self), $self);
586              
587             # return
588 0           return($s);
589              
590             }
591              
592             # transform list
593             # parameters: (object_reference, list, [hash])
594             # returns: (list)
595             sub _trans0 {
596              
597             # local variables
598 0     0     my ($self, $hash, @out);
599              
600             # get object reference
601 0           $self = shift();
602              
603             # get optional hash
604 0 0         $hash = pop() if (ref($_[-1]) eq 'HASH');
605              
606             # compute output using '_trans1'
607 0           @out = @{_trans1($self, \@_, $hash)};
  0            
608              
609             # return
610 0           return(@out);
611              
612             }
613              
614             # transform vector
615             # parameters: (object_reference, vector, [hash])
616             # returns: (vector)
617             sub _trans1 {
618              
619             # get parameters
620 0     0     my ($self, $in, $hash) = @_;
621              
622             # local variables
623 0           my ($out);
624              
625             # check if ICC::Support::Lapack module is loaded
626 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
627              
628             # if ICC::Support::Lapack module is loaded
629 0 0         if ($lapack) {
630            
631             # call the BLAS dgemv function
632 0           return(ICC::Support::Lapack::matf_vec_trans(_hidden($self, $in), $self->[2], $self->[3]));
633            
634             } else {
635            
636 0           croak('method not yet implemented');
637            
638             }
639            
640             }
641              
642             # transform matrix (2-D array -or- Math::Matrix object)
643             # parameters: (object_reference, matrix, [hash])
644             # returns: (matrix)
645             sub _trans2 {
646              
647             # get parameters
648 0     0     my ($self, $in, $hash) = @_;
649              
650             # local variables
651 0           my ($out);
652              
653             # check if ICC::Support::Lapack module is loaded
654 0           state $lapack = defined($INC{'ICC/Support/Lapack.pm'});
655              
656             # if ICC::Support::Lapack module is loaded
657 0 0         if ($lapack) {
658            
659             # call the BLAS dgemm function
660 0           $out = ICC::Support::Lapack::matf_mat_trans(_hidden2($self, $in), $self->[2], $self->[3]);
661            
662             } else {
663            
664 0           croak('method not yet implemented');
665            
666             }
667            
668             # return
669 0 0         return(UNIVERSAL::isa($in, 'Math::Matrix') ? bless($out, 'Math::Matrix') : $out);
670            
671             }
672              
673             # transform structure
674             # parameters: (object_reference, structure, [hash])
675             # returns: (structure)
676             sub _trans3 {
677              
678             # get parameters
679 0     0     my ($self, $in, $hash) = @_;
680              
681             # transform the array structure
682 0           _crawl($self, $in, my $out = [], $hash);
683              
684             # return
685 0           return($out);
686              
687             }
688              
689             # recursive transform
690             # array structure is traversed until scalar arrays are found and transformed
691             # parameters: (ref_to_object, ref_to_input_array, ref_to_output_array, hash)
692             sub _crawl {
693              
694             # get parameters
695 0     0     my ($self, $in, $out, $hash) = @_;
696              
697             # if input is a vector (reference to a scalar array)
698 0 0         if (@{$in} == grep {! ref()} @{$in}) {
  0            
  0            
  0            
699            
700             # transform input vector and copy to output
701 0           @{$out} = @{_trans1($self, $in, $hash)};
  0            
  0            
702            
703             } else {
704            
705             # for each input element
706 0           for my $i (0 .. $#{$in}) {
  0            
707            
708             # if an array reference
709 0 0         if (ref($in->[$i]) eq 'ARRAY') {
710            
711             # transform next level
712 0           _crawl($self, $in->[$i], $out->[$i] = [], $hash);
713            
714             } else {
715            
716             # error
717 0           croak('invalid transform input');
718            
719             }
720            
721             }
722            
723             }
724            
725             }
726              
727             # compute hidden node output vector
728             # parameters: (ref_to_object, ref_to_input_vector)
729             # returns: (ref_to_output_vector)
730             sub _hidden {
731              
732             # get parameters
733 0     0     my ($self, $in) = @_;
734              
735             # local variables
736 0           my ($array, $node, $out);
737              
738             # get kernel array
739 0           $array = $self->[1];
740              
741             # for each node
742 0           for my $i (0 .. $#{$array}) {
  0            
743            
744             # get node
745 0           $node = $array->[$i];
746            
747             # if a code reference
748 0 0         if (ref($node) eq 'CODE') {
749            
750             # call subroutine
751 0           $out->[$i] = &$node($in);
752            
753             # else a kernel object
754             } else {
755            
756             # call transform method
757 0           $out->[$i] = $node->transform($in);
758            
759             }
760            
761             }
762              
763             # if array rows < matrix columns (difference mode)
764 0 0         if ($#{$array} < $#{$self->[2][0]}) {
  0            
  0            
765            
766             # append input values
767 0           push(@{$out}, @{$in});
  0            
  0            
768            
769             }
770              
771             # return
772 0           return($out);
773              
774             }
775              
776             # compute hidden node output matrix
777             # parameters: (ref_to_object, ref_to_array_of_input_vectors)
778             # returns: (ref_to_array_of_output_vectors)
779             sub _hidden2 {
780              
781             # get parameters
782 0     0     my ($self, $in) = @_;
783              
784             # local variables
785 0           my ($array, $node, $out);
786              
787             # get kernel array
788 0           $array = $self->[1];
789              
790             # initialize output array
791 0           $out = [];
792              
793             # for each input row
794 0           for my $i (0 .. $#{$in}) {
  0            
795            
796             # for each node
797 0           for my $j (0 .. $#{$array}) {
  0            
798            
799             # get node
800 0           $node = $array->[$j];
801            
802             # if a code reference
803 0 0         if (ref($node) eq 'CODE') {
804            
805             # call subroutine
806 0           $out->[$i][$j] = &$node($in->[$i]);
807            
808             # else a kernel object
809             } else {
810            
811             # call transform method
812 0           $out->[$i][$j] = $node->transform($in->[$i]);
813            
814             }
815            
816             }
817            
818             # if array rows < matrix columns (difference mode)
819 0 0         if ($#{$array} < $#{$self->[2][0]}) {
  0            
  0            
820            
821             # append input values
822 0           push(@{$out->[$i]}, @{$in->[$i]});
  0            
  0            
823            
824             }
825            
826             }
827              
828             # return
829 0           return($out);
830              
831             }
832              
833             # compute hidden node Jacobian matrix
834             # parameters: (ref_to_object, ref_to_input_vector)
835             # returns: (ref_to_Jacobian_matrix, [ref_to_output_vector])
836             sub _hidden3 {
837              
838             # get parameters
839 0     0     my ($self, $in) = @_;
840              
841             # local variables
842 0           my ($array, $node, $jac, $out);
843              
844             # get kernel array
845 0           $array = $self->[1];
846              
847             # for each node
848 0           for my $i (0 .. $#{$array}) {
  0            
849            
850             # get node
851 0           $node = $array->[$i];
852            
853             # if a code reference
854 0 0         if (ref($node) eq 'CODE') {
855            
856             # if output requested
857 0 0         if (wantarray) {
858            
859             # compute numerical Jacobian
860 0           $jac->[$i] = _numjac($node, $in);
861            
862             # call subroutine
863 0           $out->[$i] = &$node($in);
864            
865             } else {
866            
867             # compute numerical Jacobian
868 0           $jac->[$i] = _numjac($node, $in);
869            
870             }
871            
872             # else a kernel object
873             } else {
874            
875             # if output requested
876 0 0         if (wantarray) {
877            
878             # call jacobian method
879 0           ($jac->[$i], $out->[$i]) = $node->jacobian($in);
880            
881             } else {
882            
883             # call jacobian method
884 0           $jac->[$i] = $node->jacobian($in);
885            
886             }
887            
888             }
889            
890             }
891            
892             # if array rows < matrix columns (difference mode)
893 0 0         if ($#{$array} < $#{$self->[2][0]}) {
  0            
  0            
894            
895             # for each row
896 0           for my $i (0 .. $#{$self->[2]}) {
  0            
897            
898             # for each column
899 0           for my $j (0 .. $#{$self->[2]}) {
  0            
900            
901             # add identity matrix element
902 0 0         $jac->[$i + $#{$self->[1]} + 1][$j] = $i == $j ? 1 : 0;
  0            
903            
904             }
905            
906             }
907            
908             }
909            
910             # if output vector requested
911 0 0         if (wantarray) {
912            
913             # if array rows < matrix columns (difference mode)
914 0 0         if ($#{$array} < $#{$self->[2][0]}) {
  0            
  0            
915            
916             # append input values
917 0           push(@{$out}, @{$in});
  0            
  0            
918            
919             }
920            
921             # return
922 0           return($jac, $out);
923            
924             } else {
925            
926             # return
927 0           return($jac);
928            
929             }
930            
931             }
932              
933             # compute numerical Jacobian
934             # parameters: (code_reference, input_vector)
935             # output: (Jacobian_vector)
936             sub _numjac {
937              
938             # get parameters
939 0     0     my ($node, $in) = @_;
940              
941             # local variables
942 0           my ($delta, $ind, $out, $jac);
943              
944             # set delta value
945 0           $delta = 1E-12;
946              
947             # compute nominal output
948 0           $out = &$node($in);
949              
950             # for each input
951 0           for my $i (0 .. $#{$in}) {
  0            
952            
953             # copy input values
954 0           $ind = [@{$in}];
  0            
955            
956             # add input delta
957 0           $ind->[$i] += $delta;
958            
959             # compute slope
960 0           $jac->[$i] = (&$node($ind) - $out)/$delta;
961            
962             }
963              
964             # return Jacobian
965 0           return($jac);
966              
967             }
968              
969             # make new nNET object from attribute hash
970             # hash may contain pointers to header, kernel, matrix, offset or init
971             # hash keys are: ('header', 'kernel', 'matrix', 'offset', 'init')
972             # object elements not specified in the hash are unchanged
973             # parameters: (ref_to_object, ref_to_attribute_hash)
974             sub _new_from_hash {
975              
976             # get parameters
977 0     0     my ($self, $hash) = @_;
978              
979             # local variables
980 0           my ($array, $code);
981              
982             # for each attribute
983 0           for my $attr (keys(%{$hash})) {
  0            
984            
985             # if 'header'
986 0 0         if ($attr eq 'header') {
    0          
    0          
    0          
    0          
987            
988             # if reference to hash
989 0 0         if (ref($hash->{$attr}) eq 'HASH') {
990            
991             # set object element
992 0           $self->[0] = {%{$hash->{$attr}}};
  0            
993            
994             } else {
995            
996             # wrong data type
997 0           croak('nNET header attribute must be a hash reference');
998            
999             }
1000            
1001             # if 'kernel'
1002             } elsif ($attr eq 'kernel') {
1003            
1004             # if an array reference
1005 0 0         if (ref($hash->{$attr}) eq 'ARRAY') {
1006            
1007             # get array
1008 0           $array = $hash->{$attr};
1009              
1010             # for each array element
1011 0           for my $i (0 .. $#{$array}) {
  0            
1012            
1013             # if array element is a valid kernel type
1014 0 0         if (grep {ref($array->[$i]) eq $_} @types) {
  0            
1015            
1016             # add array element
1017 0           $self->[1][$i] = $array->[$i];
1018            
1019             } else {
1020            
1021             # wrong data type
1022 0           croak('invalid nNET kernel array element');
1023            
1024             }
1025            
1026             }
1027            
1028             } else {
1029            
1030             # wrong data type
1031 0           croak('nNET kernel attribute must be an array reference');
1032            
1033             }
1034            
1035             # if 'matrix'
1036             } elsif ($attr eq 'matrix') {
1037            
1038             # if reference to 2D array
1039 0 0 0       if (ref($hash->{$attr}) eq 'ARRAY' && ref($hash->{$attr}[0]) eq 'ARRAY') {
    0          
1040            
1041             # set object element
1042 0           $self->[2] = Storable::dclone($hash->{$attr});
1043            
1044             # if reference to Math::Matrix object
1045             } elsif (UNIVERSAL::isa($hash->{$attr}, 'Math::Matrix')) {
1046            
1047             # set object element
1048 0           $self->[2] = Storable::dclone([@{$hash->{$attr}}]);
  0            
1049            
1050             } else {
1051            
1052             # wrong data type
1053 0           croak('nNET matrix attribute must be a 2-D array reference or Math::Matrix object');
1054            
1055             }
1056            
1057             # if 'offset'
1058             } elsif ($attr eq 'offset') {
1059            
1060             # if reference to an array of scalars
1061 0 0 0       if (ref($hash->{$attr}) eq 'ARRAY' && @{$hash->{$attr}} == grep {! ref()} @{$hash->{$attr}}) {
  0            
  0            
  0            
1062            
1063             # set object element
1064 0           $self->[3] = [@{$hash->{$attr}}];
  0            
1065            
1066             } else {
1067            
1068             # wrong data type
1069 0           croak('nNET offset attribute must be an array reference');
1070            
1071             }
1072            
1073             # if 'init'
1074             } elsif ($attr eq 'init') {
1075            
1076             # if a CODE reference
1077 0 0         if (ref($hash->{$attr}) eq 'CODE') {
1078            
1079             # set object element
1080 0           $self->[0]{'init'} = $hash->{$attr};
1081            
1082             } else {
1083            
1084             # wrong data type
1085 0           croak('nNET init attribute must be a CODE reference');
1086            
1087             }
1088            
1089             }
1090            
1091             }
1092            
1093             }
1094              
1095             1;