File Coverage

blib/lib/ICC/Support/nNET.pm
Criterion Covered Total %
statement 21 321 6.5
branch 1 146 0.6
condition 0 66 0.0
subroutine 6 26 23.0
pod 1 11 9.0
total 29 570 5.0


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