File Coverage

lib/ICC/Support/nNET2.pm
Criterion Covered Total %
statement 24 308 7.7
branch 1 136 0.7
condition 0 60 0.0
subroutine 7 34 20.5
pod 1 20 5.0
total 33 558 5.9


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