File Coverage

blib/lib/ICC/Support/nNET2.pm
Criterion Covered Total %
statement 21 305 6.8
branch 1 136 0.7
condition 0 60 0.0
subroutine 6 33 18.1
pod 1 20 5.0
total 29 554 5.2


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