3 start include statements
5 my @print_order = ('problem','abbreviated','input','data','msfi','contr','subroutine','model','infn','omega','pk','aesinit','aes','des','error','pred','mix','theta','sigma','simulation','estimation','covariance','nonparametric','table','scatter');
6 my @sde_print_order = ('problem','abbreviated','input','data','msfi','contr','subroutine','model','infn','theta','omega','sigma','pk','aesinit','aes','des','error','pred','mix','simulation','estimation','covariance','nonparametric','table','scatter');
9 # Here we intialize a hash used to find long names for abbreviated
10 # record names. We use the print_order array which contains all
11 # allowed record types.
13 foreach my $record_name( @print_order ){
14 my $uc_short_type = substr(uc($record_name),0,3);;
15 $uc_short_type = $uc_short_type.' ' if ( $record_name eq 'aes' );
16 $uc_short_type = $uc_short_type.'I' if ( $record_name eq 'aesinit' );
17 $abbreviations{$uc_short_type} = $record_name;
19 end include statements
21 # }}} include statements
28 unless ( defined $parm{'problems'} ) {
29 # Parse given problem lines.
30 $this -> _read_records
();
31 delete $this -> {'prob_arr'};
34 # Initialize table file objects (if any)
35 $this -> _read_table_files
( ignore_missing_files
=>
36 $this -> {'ignore_missing_output_files'} );
38 # Initialize extra_data objects (if any)
39 if ( defined $this -> {'extra_data_file_name'} ) {
40 $this -> {'extra_data'} = extra_data
-> new
( filename
=> $this -> {'extra_data_file_name'},
41 header
=> $this -> {'extra_data_header'},
43 ignore_missing_files
=>
44 $this -> {'ignore_missing_files'} );
47 if( $this -> {'cwres'} ){
49 $this -> add_cwres_module
( 'init_data' => { problem
=> $this,
50 nm_version
=> $this -> {'nm_version'},
51 mirror_plots
=> $this -> {'mirror_plots'} } );
60 # {{{ add_marginals_code
62 start add_marginals_code
64 # add_marginals_code takes one argument.
66 # - nomegas which is the number of (diagonal-element)
69 # For each omega, verbatim code is added to make the marginals
70 # available for printing (e.g. to a table file). COM(1) will hold the
71 # nonparametric density, COM(2) the marginal cumulative value for the
72 # first eta, COM(2) the marginal cumulative density for the second eta
74 # The code is added to the $ERROR record
76 my $record_ref = $self -> errors
;
77 if( defined $record_ref and defined $record_ref -> [0] ) {
78 my ( @first_params, @last_params );
79 $last_params[0] = '" COM(1) = DENM';
80 $first_params[0] = '" X ';
83 for( my $i = 1; $i <= $nomegas; $i++ ) {
84 $comma = $i == $nomegas ?
'' : ',';
85 if( not ($i % 4) ) { # break line every fifth omega
87 $first_params[$j] = '" X ';
89 $first_params[$j] = $first_params[$j]."DEN$i$comma";
90 push( @last_params, '" COM('.($i+1).") = DEN$i" );
92 my $first_code = $record_ref -> [0] -> verbatim_first
;
93 push( @
{$first_code}, ( '" COMMON /ROCM18/ DENM,', @first_params,
94 '" DOUBLE PRECISION DENM,', @first_params ) );
95 $record_ref -> [0] -> verbatim_first
( $first_code );
96 my $last_code = $record_ref -> [0] -> verbatim_last
;
97 push( @
{$last_code}, @last_params );
98 $record_ref -> [0] -> verbatim_last
( $last_code );
99 last; # Only insert the code in the first record found (of the ones specified above)
101 'debug' -> warn( level
=> 2,
102 message
=> "No \$ERROR record was found. Can't add verbatim code".
103 " to access nonparametric marginals" );
106 end add_marginals_code
108 # }}} add_marginals_code
110 # {{{ cont_wrap_columns
112 start cont_wrap_columns
114 if ( defined $self -> {'primary_columns'} ) {
115 $wrap_column = scalar @
{$self -> {'primary_columns'}};
116 for ( my $i = 0; $i < $wrap_column ; $i++ ) {
117 $cont_column = ($i+1) if ( $self -> {'primary_columns'}[$i][0] eq 'CONT' );
121 end cont_wrap_columns
123 # }}} cont_wrap_columns
129 if( defined $self -> {'tables'} ){
130 for( my $i = 0; $i < scalar @
{$self -> {'tables'}}; $i++ ) {
131 my $table = $self -> {'tables'}[$i];
132 my $cont_column = $table -> contify
;
133 # I don't know what I had in mind when I wrote this piece of code:
134 # Maybe I'll remember some day, better leave it as it is for now:
137 # for( my $j = 1; $j <= scalar @{$table -> options}; $j++ ) {
138 # my $option = $options[$j-1];
139 # my $name = $option -> name;
140 # last if( $name eq 'BY' or $name eq 'PRINT' or $name eq 'NOPRINT' or
141 # $name eq 'FILE' or $name eq 'NOHEADER' or $name eq 'ONEHEADER' or
142 # $name eq 'FIRSTONLY' or $name eq 'NOFORWARD' or $name eq 'FORWARD' or
143 # $name eq 'APPEND' or $name eq 'NOAPPEND' or $name eq 'UNCONDITIONAL' or
144 # $name eq 'CONDITIONAL' or $name eq 'OMITTED' );
145 # my @col = ( $name, $j, $option -> value );
146 # push( @prim, \@col );
148 if( defined $self -> {'table_files'} and defined $self -> {'table_files'}[$i] ) {
149 # $self -> {'table_files'}[$i] -> primary_columns( \@prim );
150 $self -> {'table_files'}[$i] -> cont_column
( $cont_column );
159 # {{{ dropped_columns
161 start dropped_columns
163 my $inp_ref = $self -> inputs
;
164 if ( defined $inp_ref and defined $inp_ref -> [0] ) {
165 my $input = $inp_ref -> [0];
166 my $opt_ref = $input -> options
;
167 if ( defined $opt_ref ) {
168 my @options = @
{$opt_ref};
169 foreach my $option ( @options ) {
170 my $dropped = ( $option -> value
eq 'DROP' or
171 $option -> value
eq 'SKIP' ) ?
1 : 0;
172 push ( @dropped_columns, $dropped );
179 # }}} dropped_columns
185 my $inp_ref = $self -> inputs
;
186 # Important that the drop_dropped method of the data class is
187 # in sync with this method.
188 if ( defined $inp_ref and defined $inp_ref -> [0] ) {
189 my $input = $inp_ref -> [0];
190 my $opt_ref = $input -> options
;
191 if ( defined $opt_ref ) {
192 my @options = @
{$opt_ref};
194 foreach my $option ( @options ) {
195 push ( @keep, $option ) if ( not ($option -> value
eq 'DROP' or $option -> value
eq 'SKIP') or
196 $option -> name
=~ /DAT(E|1|2|3)/ );
198 $input -> options
( \
@keep );
206 # {{{ extra_data_header
207 start extra_data_header
209 if ( defined $parm ) {
210 if ( defined $self -> {'extra_datas'} and
211 defined $self -> {'extra_datas'} -> [0] ) {
212 $self -> {'extra_datas'} -> [0] -> header
( $parm );
216 end extra_data_header
219 # {{{ extra_data_file_name
221 start extra_data_file_name
223 if ( defined $parm ) {
224 $self -> {'extra_data'} = 'extra_data' -> new
( filename
=> $parm,
226 ignore_missing_files
=>
227 $self -> {'ignore_missing_files'} );
230 end extra_data_file_name
234 # {{{ read_table_files
236 start _read_table_files
238 @
{$self -> {'table_files'}} = ();
239 my ( $table_name_ref, $junk ) = $self -> _option_val_pos
( record_name
=> 'table',
241 if ( defined $table_name_ref and scalar @
{$table_name_ref} >= 0 ) {
242 $self -> {'table_files'} = [];
243 foreach my $table_name ( @
{$table_name_ref} ) {
244 'debug' -> warn( level
=> 2,
245 message
=> "Creating new table_file object from $table_name" );
246 my $new_table = data
-> new
( directory
=> $self -> {'directory'},
247 filename
=> $table_name,
248 ignore_missing_files
=> $ignore_missing_files,
251 push( @
{$self -> {'table_files'}}, $new_table );
255 end _read_table_files
262 # add_records( type => 'subroutine',
263 # record_strings => ['OTHER=get_cov', 'OTHER=read'] )
264 # TODO change name from record to records.
266 # To read add a record, we figure out what its full class name
267 # is. Then we check if we have an accessor for the record type,
268 # if we do then the record is valid and we call the appropriate
269 # contructor. Both record_strings an type are mandatory.
271 my $rec_class = "model::problem::$type";
272 my $accessor = $type.'s';
273 if( $self -> can
($accessor) ){
274 push( @
{$self -> {$accessor}}, $rec_class -> new
( record_arr
=> \
@record_strings ));
277 'debug' -> die( message
=> "Trying to add unknown record: $type" );
287 if( defined $self -> {'covariances'} ) {
288 @records = @
{$self -> {'covariances'}} ;
290 if ( defined $enabled ) {
291 if ( $enabled and $#records < 0 ) {
292 $self -> add_records
( type
=> 'covariance',
293 record_strings
=> [''] );
294 } elsif ( not $enabled and $#records >= 0 ) {
295 $self -> {'covariances'} = undef;
298 if ( $#records >= 0 ) {
312 my ( $print_ref, $position ) = $self -> _option_val_pos
( record_name
=> 'covariance',
314 # print Dumper $print_ref;
315 # print Dumper $position;
317 # if ( defined $enabled ) {
318 # if ( $enabled and scalar @{$print_ref} < 1 ) {
328 my $inp_ref = $self -> inputs
;
329 if ( defined $inp_ref and defined $inp_ref -> [0] ) {
330 my $input = $inp_ref -> [0];
331 my $opt_ref = $input -> options
;
332 if ( defined $opt_ref ) {
333 my @options = @
{$opt_ref};
334 foreach my $option ( @options ) {
335 push ( @header, [$option -> name
, $option -> value
] );
346 # The Indexes method calculates the index for a parameter. Off-diagonal elements
347 # will get a index 'i_j', where i is the row number and j is the column number
350 my $accessor = $parameter_type.'s';
352 if( defined $self -> {$accessor} ){
354 # If we hit a "SAME" parameter we need to remember the
355 # previous parameter size. ( calculated as "row - previous_row" )
357 my $previous_row = 0;
359 foreach my $record ( @
{$self -> $accessor} ) {
360 # If not a block or if the block is of size 1 use normal numbering
362 if( $record -> same
) {
364 if( $previous_row == 0 ){
365 'debug' -> die( message
=> "You can't have an $parameter_type estimate defined as SAME if it's the first estimate" );
367 my $size = $row - $previous_row;
368 $previous_row = $row;
370 push( @indexes, $row++);
373 } elsif ( ( ! defined $record -> size
) or
374 ( $record -> size
< 2 )) {
375 if ( defined $record -> options
) {
376 $previous_row = $row;
377 foreach my $option ( @
{$record -> options
} ) {
378 push( @indexes, $row++ );
382 # ... else use off-diagonal indexing where so is necessary
383 my $size = $record -> size
;
384 for ( my $i = $row; $i <= $row + $size - 1; $i++ ) {
385 for ( my $j = $row; $j <= $i; $j++ ) {
387 push( @indexes, "$i" );
389 push( @indexes, "$i".'_'."$j" );
393 $previous_row = $row;
397 if ( scalar @parameter_numbers > 0 ) {
398 my @part_indexes = ();
399 foreach my $num ( @parameter_numbers ) {
400 if ( $num < 1 or $num > scalar @indexes ) {
401 'debug' -> die( message
=> "$parameter_type number " . $num . " does not exist in this model::problem\n" .
402 "(" . scalar @indexes . " exists)\n" );
404 push( @part_indexes, $indexes[$num -1] );
406 @indexes = @part_indexes;
408 'debug' -> warn( level
=> 2,
409 message
=> "Model::problem -> indexes: parameter_numbers undefined, using all." );
421 foreach my $omega ( @
{$self -> {'omegas'}} ) {
422 my $size = $omega -> size
;
423 if( defined $size ) {
425 # If the record has a size, it is of block form with diagonal of
426 # length given by $size. The actual values in the model file is
427 # then the arithmetic sum: (n*(n+1))/2
429 if( $with_correlations ){
430 $nomegas += ($size*($size+1))/2;
432 # But we really only want the diagonal elements here:
437 $nomegas += scalar @
{$omega -> options
};
449 foreach my $sigma ( @
{$self -> {'sigmas'}} ) {
450 my $size = $sigma -> size
;
451 if( defined $size ) {
453 # If the record has a size, it is of block form with diagonal of
454 # length given by $size. The actual values in the model file is
455 # then the arithmetic sum: (n*(n+1))/2
457 if( $with_correlations ){
459 $nsigmas += ($size*($size+1))/2;
463 # But we really only want the diagonal elements here:
468 $nsigmas += scalar @
{$sigma -> options
};
476 # {{{ primary_columns
478 start primary_columns
480 if ( defined $parm ) {
481 my $inp_ref = $self -> inputs
;
482 if ( defined $inp_ref and defined $inp_ref -> [0] ) {
483 $self -> inputs
-> [0] -> options
( [] );
485 $self -> inputs
( ['input' -> new
()] );
487 foreach my $arr_ref ( @
{$parm} ) {
488 my @col = @
{$arr_ref};
489 # It is important to keep (almost) all primary columns
490 # undropped. if one columns is dropped, all the data in
491 # the same columns in the rows with CONT=1 will also be
492 # dropped. In addition the indexing of EVTREC will be
493 # wrong. The only exception to this are the DAT(E|1|2|3)
494 # columns. The must be dropped because the NONMEM data set
495 # may not include non-digits. To handle this, these
496 # columns will be only include dummy variables in the
497 # secondary_columns attribute.
499 # if( $col[2] eq 'DROP' ) {
504 # print "N: $col[0], V: $col[2]\n";
505 my $value = $col[0] =~ /DAT(E|1|2|3)/ ?
$col[2] : undef;
506 $self -> inputs
-> [0] -> add_option
( init_data
=> { name
=> $col[0],
509 $self -> contify_tables
;
514 # }}} primary_columns
516 # {{{ secondary_columns
518 start secondary_columns
520 if ( defined $parm ) {
521 if ( defined $self -> pks
) {
522 $self -> pks
-> [0] -> secondary_columns
($parm);
524 if ( defined $self -> preds
) {
525 $self -> preds
-> [0] -> secondary_columns
($parm);
527 if ( defined $self -> errors
) {
528 $self -> errors
-> [0] -> secondary_columns
($parm);
530 # This is a lengthy bit of code to create a reasonable header and secondary
531 # columns for the table files. When the data file of the problem is wrapped
532 # using the CONT data item, the rows of the table files will be duplicated (or
533 # at least, only every second or third row will hold the table output).
534 if( defined $self -> {'tables'} ){
535 for( my $i = 0; $i < scalar @
{$self -> {'tables'}}; $i++ ) {
536 if( defined $self -> {'table_files'} and defined $self -> {'table_files'}[$i] ) {
537 # my $table = $self -> {'tables'}[$i]; # $TABLE
538 my $table_file = $self -> {'table_files'}[$i]; # The actual file
539 # my $cont_column = $table_file -> cont_column;
540 # my @options = @{$table -> options};
541 # my @header_options;
542 # for( my $j = 1; $j <= scalar @options; $j++ ) {
543 # my $option = $options[$j-1];
544 # my $name = $option -> name;
545 # last if( $name eq 'BY' or $name eq 'PRINT' or $name eq 'NOPRINT' or
546 # $name eq 'FILE' or $name eq 'NOHEADER' or $name eq 'ONEHEADER' or
547 # $name eq 'FIRSTONLY' or $name eq 'NOFORWARD' or $name eq 'FORWARD' or
548 # $name eq 'APPEND' or $name eq 'NOAPPEND' or $name eq 'UNCONDITIONAL' or
549 # $name eq 'CONDITIONAL' or $name eq 'OMITTED' );
550 # push( @header_options, $option );
552 # my $nopt = scalar @header_options;
555 # if( defined $cont_column and ref( $parm ) eq 'ARRAY' and scalar @{$parm} > 0 ) {
556 # for( my $k = -1; $k < scalar @{$parm}; $k++ ) {
559 # for( my $j = 1; $j <= $nopt; $j++ ) {
560 # my $option = $header_options[$j-1];
561 # my $name = $option -> name;
563 # if( $name ne 'CONT' ) {
564 # $name = $k == -1 ? $name : $name.($k+1);
565 # $glob_pos = (($nopt-1)*($k+1))+$pos++;
566 # push( @header, $name );
568 # next if ( $k == -1 );
569 # my @col = ( $name, $glob_pos, $option -> value );
570 # push( @in_sec, \@col );
572 # unshift( @sec, \@in_sec );
575 $table_file -> header
( [] );
576 # $table_file -> header( \@header );
577 # $table_file -> secondary_columns( \@sec );
582 if ( defined $self -> pks
) {
583 $self -> {'secondary_columns'} = $self -> pks
-> [0] -> secondary_columns
();
585 $self -> {'secondary_columns'} = $self -> preds
-> [0] -> secondary_columns
();
589 end secondary_columns
591 # }}} secondary_columns
597 if( defined $self -> {$record_name . 's'} ){
598 foreach my $record ( @
{$self -> {$record_name . 's'}} ){
599 if( defined $record -> options
){
600 $return_value += @
{$record -> options
};
611 if ( defined $self -> {'thetas'} ) {
612 foreach my $theta ( @
{$self -> {'thetas'}} ) {
613 $theta -> restore_inits
;
616 if ( defined $self -> {'omegas'} ) {
617 foreach my $omega ( @
{$self -> {'omegas'}} ) {
618 $omega -> restore_inits
;
621 if ( defined $self -> {'sigmas'} ) {
622 foreach my $sigma ( @
{$self -> {'sigmas'}} ) {
623 $sigma -> restore_inits
;
630 # {{{ set_random_inits
631 start set_random_inits
633 if ( defined $self -> {'thetas'} ) {
634 foreach my $theta ( @
{$self -> {'thetas'}} ) {
635 $theta -> set_random_inits
( degree
=> $degree );
638 if ( defined $self -> {'omegas'} ) {
639 foreach my $omega ( @
{$self -> {'omegas'}} ) {
640 $omega -> set_random_inits
( degree
=> $degree );
643 if ( defined $self -> {'sigmas'} ) {
644 foreach my $sigma ( @
{$self -> {'sigmas'}} ) {
645 $sigma -> set_random_inits
( degree
=> $degree );
650 # }}} set_random_inits
656 my $rec_class = "model::problem::$type";
657 my $accessor = $type.'s';
658 if( $self -> can
($accessor) ){
659 $self -> {$accessor} = [$rec_class -> new
( record_arr
=> \
@record_strings) ];
661 die "Error in problem -> set_records: Trying to set unknown record: $type\n";
671 my $rec_class = "model::problem::$type";
672 my $accessor = $type.'s';
673 if( $self -> can
($accessor) ){
674 $self -> {$accessor} = undef;
676 die "Error in problem -> remove_records: Trying to remove unknown record: $type\n";
686 my $accessor = $record_name.'s';
687 unless( $self -> can
($accessor) ){
688 'debug' -> die( message
=> "Unknown record name: $record_name" );
690 if( defined $self -> {$accessor} ) {
691 my @records = @
{$self -> {$accessor}};
692 foreach my $record ( @records ) {
693 $record -> add_option
( init_data
=> { name
=> $option_name,
694 value
=> $option_value } );
698 $self -> add_records
( type
=> $record_name,
699 record_strings
=> ["$option_name=$option_value"] );
701 'debug' -> warn( level
=> 2,
702 message
=> "No records of type $accessor and add_option ".
703 "set not to add one" );
715 my $accessor = $record_name.'s';
716 unless( $self -> can
($accessor) ){
717 'debug' -> die( message
=> "Unknown record name: $record_name" );
719 if( defined $self -> {$accessor} ) {
720 my @records = @
{$self -> {$accessor}};
721 foreach my $record ( @records ) {
722 $record -> remove_option
( name
=> $option_name,
723 fuzzy_match
=> $fuzzy_match );
726 'debug' -> warn( level
=> 2,
727 message
=> "No records of type $accessor" );
737 if ( defined $self -> {'thetas'} ) {
738 foreach my $theta ( @
{$self -> {'thetas'}} ) {
739 $theta -> store_inits
;
742 if ( defined $self -> {'omegas'} ) {
743 foreach my $omega ( @
{$self -> {'omegas'}} ) {
744 $omega -> store_inits
;
747 if ( defined $self -> {'sigmas'} ) {
748 foreach my $sigma ( @
{$self -> {'sigmas'}} ) {
749 $sigma -> store_inits
;
756 # {{{ _format_problem
758 start _format_problem
760 # problem::_format_problem()
762 # format_problem will return an array of strings of the
763 # problem in NONMEM modelfile format.
765 # Loop over the print_order array that contains strings of
766 # valid record types in the order they should appear in a
767 # NONMEM modelfile. So if the order of some records are
768 # interchangable and the file from which the object was
769 # initialized has records in an order different from
770 # print_order, the file will still be valid, but will look
771 # different from what it used to.
772 my $record_order = $self -> {'sde'} ? \
@sde_print_order : \
@print_order;
773 foreach my $type ( @
${record_order
} ) {
774 # Create an accessor string for the record being formatted
775 my $accessor = $type.'s';
777 # Se if we have one or more records of the type given in
779 if ( defined $self -> {$accessor} ) {
780 # Loop over all such records and call on the record object
782 foreach my $record ( @
{$self -> {$accessor}} ){
785 _format_record
( nonparametric_code
=> $self -> {'nonparametric_code'},
786 shrinkage_code
=> $self -> {'shrinkage_code'},
787 eigen_value_code
=> $self -> {'eigen_value_code'} ) } );
790 if( $self -> {'shrinkage_module'} -> enabled
and $type eq 'table' ) {
792 @
{$self -> {'shrinkage_module'} -> format_shrinkage_tables
} );
796 if( $self -> {'cwres_modules'} ){
797 $self -> {'cwres_modules'} -> [0] -> post_process
;
803 # }}} _format_problem
809 # Private method, should preferably not be used outside model.pm
810 # The add_if_absent argument tells the method to add an init (theta,omega,sigma)
811 # if the parameter number points to a non-existing parameter with parameter number
812 # one higher than the highest presently included. Only applicatble if
813 # new_values are set. Default value = 0;
815 my $accessor = $parameter_type.'s';
816 unless( $self -> can
($accessor) ){
817 'debug' -> die( message
=> "problem -> _init_attr: Error unknown parameter type: $parameter_type" );
821 if( defined $self -> {$accessor} ){
822 @records = @
{$self -> {$accessor}};
829 # {{{ Check that the size of parameter_numbers and new_values match
831 if ( $#parameter_numbers >= 0 and $#new_values >= 0 ) {
832 if ( $#parameter_numbers == $#new_values ) {
833 for ( my $i = 0; $i <= $#new_values; $i++ ) {
834 $num_val{$parameter_numbers[$i]} = $new_values[$i];
837 die "Model::problem -> _init_attr: The number of specified ".
838 "parameters (@parameter_numbers) and values (@new_values) do not match for parameter $parameter_type".
839 " and attribute $attribute\n";
845 if ( scalar @new_values > 0 ) {
848 # OBS! We are using 'normal' numbering in parameter_numbers, i.e. they begin
851 # Ugly solution to add non-existing options:
853 foreach my $num ( @parameter_numbers) {
854 # print "inpn: $num\n";
858 my @diagnostics = ();
859 foreach my $record ( @records ) {
860 if ( $record -> same
() ) {
861 # SAME == true: Nothing to be done. Just move forward to next $OMEGA but
862 # increase counter first
864 $opt_num += $prev_size;
866 foreach my $option ( @
{$record -> options
} ) {
867 if ( scalar @parameter_numbers > 0 ) {
868 foreach my $num ( @parameter_numbers ) {
869 if ( $num == $opt_num ) {
871 if ( $attribute eq 'init' ) {
873 $option -> check_and_set_init
( new_value
=> $num_val{$num} ) );
874 } elsif( $attribute eq 'fix' and defined $record -> size
() ){
875 # size() tells us this is a block and we must fix on record level.
876 $record -> fix
( $num_val{$num} );
878 $option -> $attribute( $num_val{$num} );
883 if ( $attribute eq 'init' ) {
885 $option -> check_and_set_init
( new_value
=> shift( @new_values ) ) );
886 } elsif( $attribute eq 'fix' and defined $record -> size
() ){
887 # size() tells us this is a block and we must fix on record level.
888 $record -> fix
( shift( @new_values ) );
890 $option -> $attribute( shift( @new_values ) );
895 if( $parameter_type eq 'theta' ){
896 $prev_size = scalar @
{$record -> options
};
898 my $size = $record -> size
;
899 if( defined $size ) {
900 $prev_size = ($size*($size+1))/2;
902 $prev_size = scalar @
{$record -> options
};
907 # If $add_if_absent is set, any parameters that were not found above are
910 my @nums = sort {$a<=>$b} keys %found;
911 my $new_record = "model::problem::$parameter_type" -> new
();
913 foreach my $num ( @nums ) {
914 if ( $add_if_absent and
917 unless($num == $opt_num) {
918 'debug' -> die( message
=> "Attempt to add a parameter with higher number ($num) than the number\n".
919 "of parameters + 1 ($opt_num)\n" );
921 # Get the last record of $parameter_type
922 # my $new_record = $records[$#records];
924 if( $parameter_type eq 'theta' ){
925 $option_class = 'model::problem::record::theta_option';
927 $option_class = 'model::problem::record::init_option';
930 # Push a new option to this last record
931 my $option = $option_class -> new
;
932 if ( $attribute eq 'init' ) {
933 $option -> check_and_set_init
( new_value
=> $num_val{$num} );
934 } elsif( $attribute eq 'fix' and defined $new_record -> size
() ){
936 # size() tells us this is a block and we must fix on
937 # record level. This will never happen, as we can't
938 # add BLOCKS, at least not like this.
940 $new_record -> fix
( $num_val{$num} );
942 $option -> $attribute( $num_val{$num} );
944 push( @
{$new_record -> {'options'}}, $option );
946 # So we've added a parameter. Possible to add more,
947 # lets increase the highest found:
951 if ( $attribute eq 'init' ) {
952 # We're updating but might be returning diagnostics
953 # Use the default return parameter parameter_values for this
954 @parameter_values = @diagnostics;
957 if( $do_add_record ){
958 push( @records, $new_record );
959 $self -> {$accessor} = \
@records ;
964 # {{{ Retrieve values
966 my @prev_values = ();
967 foreach my $record ( @records ) {
968 unless ( $record -> same
() ) {
970 if ( defined $record -> options
) {
971 foreach my $option ( @
{$record -> options
} ) {
972 push( @prev_values, $option -> $attribute );
975 'debug' -> warn( level
=> 1,
976 message
=> "Trying to get attribute $attribute, ".
977 "but no options defined in record ".ref($record) );
979 $prev_size = $record -> size
unless ( $record -> same
);
981 # I am not confortable with the action below, i.e. to just
982 # append the previous value of the attribute if the record
983 # is SAME. An exception is made to labels. Maybe this
984 # should be the default?
985 if( $record -> same
() ) {
986 for( my $i = 0; $i <= $#prev_values; $i++ ) {
987 $prev_values[$i] = undef;
990 push( @parameter_values, @prev_values );
993 if ( scalar @parameter_numbers > 0 ) {
995 foreach my $num ( @parameter_numbers ) {
996 push( @part_vals, $parameter_values[$num -1] );
998 @parameter_values = @part_vals;
1000 'debug' -> warn( level
=> 2,
1001 message
=> "Model::problem -> _init_attr: parameter_numbers undefined, using all." );
1004 # }}} Retrieve values
1015 # my $accessor = $parameter_type.'_name_vals';
1016 # unless( $self -> can($accessor) ){
1017 # die "problem -> _init_attr: Error unknown parameter type: $parameter_type\n";
1019 # print "P: $parameter_type\n" if $self -> {'debug'};
1020 # print "Model::problem -> _init_attr: parameter_type: $parameter_type\n"
1021 # if $self -> {'debug'};
1022 # my @records = @{$self -> {$accessor}};
1025 # my $prev_size = 1;
1026 # # {{{ Retrieve values
1027 # foreach my $record ( @records ) {
1028 # foreach my $option ( @{$record -> options} ) {
1029 # if ( $record -> same ) {
1030 # for ( my $i = 1; $i <= $prev_size*($prev_size+1)/2 ; $i++ ) {
1031 # push( @parameter_values, @{$record -> options}[0] -> $attribute );
1034 # push( @parameter_values, $option -> $attribute );
1037 # $prev_size = $record -> size unless ( $record -> same );
1039 # if ( $#parameter_numbers > 0 ) {
1040 # my @part_vals = ();
1041 # foreach my $num ( @parameter_numbers ) {
1042 # push( @part_vals, $parameter_values[$num -1] );
1044 # @parameter_values = @part_vals;
1046 # print "Model::problem -> _init_attr: parameter_numbers undefined, using all.\n" if
1047 # $self -> {'debug'};
1049 # # }}} Retrieve values
1055 # {{{ _normalize_record_name
1057 start _normalize_record_name
1060 # This code takes a recordname (which likely is uppercase and
1061 # semilong), creates its short uppercase format and looks up the
1062 # long, lowercase, name in the abbreviations hash that was
1063 # initialized in "new". The name is assumed to be valid, if its
1064 # not, an empty string will be returned, but no error produced (
1065 # a warning might be nice though ) (Errorhandling is now done in
1068 my $uc_short_type = substr(uc($record_name),0,3);
1069 $uc_short_type = $uc_short_type.' ' if ( $record_name eq 'aes' );
1070 $uc_short_type = $uc_short_type.'I' if ( $record_name eq 'aesinit' );
1071 $normalized_name = $abbreviations{$uc_short_type};
1073 end _normalize_record_name
1082 # We parse the lines of a problem by looping over the them and
1083 # look for records(lines starting with a $). When a record is
1084 # found we set its index in the array as the end of the previous
1085 # record we found. We then know which lines to send to the
1086 # record object constructor. Then we set the end index of the
1087 # previous record as the start index of the next record. It is
1088 # assumed that the first record starts at line zero. The end of
1089 # the last record is the last line.
1091 my $start_index = 0;
1092 my $record_index = 0;
1096 # It may look like the loop takes one step to much, but its a
1097 # trick that helps parsing the last record.
1098 for( my $i = 0; $i <= @
{$self -> {'prob_arr'}}; $i++ ){
1100 # This if statement makes sure we dont access the array in the
1101 # last iteration of the loop. In all other iterations we need
1102 # a line of code to look for records starting lines.
1104 if( $i <= $#{$self -> {'prob_arr'}} ){
1105 $_ = $self -> {'prob_arr'} -> [$i];
1108 # In this if statement we use the lazy evaluation of logical
1109 # or to make sure we only execute search pattern when we have
1110 # a line to search. Which is all cases but the very last loop
1113 if( $i > $#{$self -> {'prob_arr'}} or /^\s*\$(\w+)/ ){
1116 # The if statement here is only necessary in the first loop
1117 # iteration. When start_index == end_index == 0 we want to
1118 # skip to the next iteration looking for the actual end of
1121 if( $end_index > $start_index and not $first){
1122 # extract lines of code:
1123 my @record_lines = @
{$self -> {'prob_arr'}}[$start_index .. $end_index-1];
1124 # extract the record name and get its long name:
1125 $self -> {'prob_arr'} -> [$record_index] =~ /^\s*\$(\w+)/;
1126 my $record_name = $1;
1127 my $record_type = $self -> _normalize_record_name
( record_name
=> $record_name );
1129 unless( length($record_type) > 0 ){
1130 'debug' -> die( message
=> "Record $record_name is not valid" );
1133 # reset the search for records by moving the record start
1137 # let add_records create the object if appropriate
1139 if( $record_type eq 'table' ) {
1142 my $eta_name = $self -> {'shrinkage_module'} -> eta_tablename
;
1143 my $wres_name = $self -> {'shrinkage_module'} -> wres_tablename
;
1144 foreach my $row ( @record_lines ) {
1145 $et_found++ if( $row =~ /$eta_name/ );
1146 $wr_found++ if( $row =~ /$wres_name/ );
1148 if( $et_found or $wr_found ) {
1149 $self -> {'shrinkage_module'} -> enable
;
1151 $self -> add_records
( record_strings
=> \
@record_lines,
1152 type
=> $record_type );
1155 $self -> add_records
( record_strings
=> \
@record_lines,
1156 type
=> $record_type );
1168 # {{{ _option_val_pos
1170 start _option_val_pos
1173 # _option_val_pos( record_name => 'subroutine',
1178 # _option_val_pos sets, or gets, the value of an option (given
1179 # as the 'name' parameter. Name must be uppercase) in a record
1180 # (given as the 'record_name' parameter. Record name should be
1181 # the record class name in the model diagram.)
1183 my $accessor = $record_name.'s';
1184 unless( $self -> can
($accessor) ){
1185 'debug' -> die( message
=> "Unknown record name: $record_name" );
1189 if( defined $self -> {$accessor} ) {
1190 @records = @
{$self -> {$accessor}} ;
1192 'debug' -> warn( level
=> 2,
1193 message
=> "No records of type $accessor" );
1198 # {{{ Check that the size of instance_numbers and new_values match
1201 if ( $#instance_numbers >= 0 and $#new_values >= 0 ) {
1202 if ( $#instance_numbers == $#new_values ) {
1203 for ( my $i = 0; $i <= $#new_values; $i++ ) {
1204 $num_val{$instance_numbers[$i]} = $new_values[$i];
1207 'debug' -> die( message
=> "Model::problem -> _option_val_pos: The number of specified " .
1208 "parameters " . $#instance_numbers+1 . " and values " .
1209 $#new_values+1 . " do not match" );
1215 if ( scalar @new_values > 0 ) {
1219 foreach my $record ( @records ) {
1220 foreach my $option ( @
{$record -> options
} ) {
1221 my $test_name = $exact_match ?
uc($option -> name
) :
1222 uc(substr($option -> name
,0,length($name)));
1223 if ( $test_name eq $name) {
1224 if ( scalar @instance_numbers > 0 ) {
1225 foreach my $num ( @instance_numbers ) {
1226 $option -> value
( $num_val{$num} ) if $num == $opt_num;
1229 $option -> value
( shift( @new_values ) );
1238 # {{{ Retrieve values
1240 foreach my $record ( @records ) {
1242 if ( defined $record -> options
) {
1243 foreach my $option ( @
{$record -> options
} ) {
1244 my $test_name = $exact_match ?
uc($option -> name
) :
1245 uc(substr($option -> name
,0,length($name)));
1246 if ( $test_name eq $name) {
1247 push( @values, $option -> value
);
1248 push( @positions, $i );
1254 if ( $#instance_numbers > 0 ) {
1257 foreach my $num ( @instance_numbers ) {
1258 push( @part_vals, $values[$num -1] );
1259 push( @part_pos, $positions[$num -1] );
1261 @values = @part_vals;
1262 @positions = @part_pos;
1265 # }}} Retrieve values
1270 # }}} _option_val_pos
1276 @eta_shrinkage = @
{$self -> {'shrinkage_module'} -> eta_shrinkage
};
1282 # {{{ wres_shrinkage
1284 start wres_shrinkage
1286 @wres_shrinkage = @
{$self -> {'shrinkage_module'} -> wres_shrinkage
};
1290 # }}} wres_shrinkage