File Coverage

blib/lib/Math/ODE.pm
Criterion Covered Total %
statement 110 122 90.1
branch 25 40 62.5
condition 2 7 28.5
subroutine 15 17 88.2
pod 0 9 0.0
total 152 195 77.9


line stmt bran cond sub pod time code
1             package Math::ODE;
2 7     7   167641 use strict;
  7         15  
  7         181  
3 7     7   35 use warnings;
  7         13  
  7         191  
4              
5 7     7   6537 use Data::Dumper;
  7         71855  
  7         431  
6 7     7   44 use Carp;
  7         15  
  7         10874  
7             my $VERSION = '0.07';
8              
9             $Data::Dumper::Varname = "y";
10             $Data::Dumper::Indent = 1;
11              
12             sub evolve
13             {
14 41     41 0 1343 my $self = shift;
15 41         98 my ($F,$h,$t,$initial,$file) = map{ $self->{$_} } qw(ODE step t0 initial file);
  205         472  
16 41 100 50     379 my $delim = $self->{csv} ? ',' : ($self->{delim} || $self->{delimeter} || " ");
17 41         58 my $y;
18             my $fh;
19              
20             # don't clobber the initial condition in case we want to do multiple runs
21             # with the same object
22 41         110 @$y = @$initial;
23              
24 41 100       98 if( defined $file ){
25 4 50       531 open($fh,'>', $file) or croak "$file: $!";
26             }
27              
28 41         101 $self->_clear_values;
29              
30 41         150 while ( $t < $self->{tf} ){
31             # use Runge Kutta 4th order to step from $t to $t + $h
32 12283         22622 $y = _RK4($self,$t,$y);
33              
34 12283 50       28815 warn "Exiting RK4 with t=$t ," . Dumper($y) . "\n" if( $self->{verbose} > 1 );
35              
36 12283         25887 for my $i ( 0 .. $self->{N}-1 ){
37             # check for under/over flow
38 22900 50       194960 next unless $y->[$i] =~ qr/nan|infinity/i;
39 0 0       0 warn "Bailing out, over/under flow at t=$t,y->[$i] = $y->[$i]" if $self->{verbose};
40 0         0 return undef;
41             }
42 12283         15994 $t += $h;
43              
44 12283         18650 my $str = join $delim, map { sprintf $self->{format}, $_ } ($t, @$y);
  35183         159577  
45 12283         21390 chop $str;
46              
47 12283 100       56470 if( defined $file ){
    100          
48 44         218 print $fh "$str\n";
49             } elsif ( ! $self->{keep_values} ) {
50 11         97 print "$str\n";
51             }
52             }
53 41 100       319 close $fh if defined $file;
54 41         152 return $self;
55             }
56              
57             sub _RK4
58             {
59             # $t = dependent variable
60             # $y = $N - vector of independent variables
61             # $h = step size
62             # $F = arrayref of coderefs of the equations to solve
63 12283     12283   17788 my ($self,$t,$y) = @_;
64 12283         16202 my $F = $self->{ODE};
65 12283         16529 my $h = $self->{step};
66              
67             ## w vectors hold constants for equations
68             ## each $q holds a modified $y vector to feed to the next
69             ## for loop ( ie $y + $w1/2 , etc ... )
70 12283         13503 my (@w1,@w2,@w3,@w4,$q);
71 12283         28512 my @indices = ( 0 .. $self->{N} - 1 );
72              
73 12283         18907 map { $w1[$_] = $h * &{ $F->[$_] }($t,$y) } @indices;
  22900         61080  
  22900         51945  
74 12283         60733 map { $q->[$_] = $y->[$_] + 0.5*$w1[$_] } @indices;
  22900         54463  
75              
76 12283         16129 map { $w2[$_] = $h * &{ $F->[$_] }($t + 0.5*$h,$q) } @indices;
  22900         62670  
  22900         50095  
77 12283         54919 map { $q->[$_] = $y->[$_] + 0.5*$w2[$_] } @indices;
  22900         45225  
78              
79 12283         16522 map { $w3[$_] = $h * &{ $F->[$_] }($t + 0.5*$h,$q) } @indices;
  22900         62666  
  22900         49155  
80 12283         53383 map { $q->[$_] = $y->[$_] + $w3[$_] } @indices;
  22900         43926  
81              
82 12283         15117 map { $w4[$_] = $h * &{ $F->[$_] }($t + $h,$q) } @indices;
  22900         58400  
  22900         48696  
83 12283         53497 map { $y->[$_]+= ( $w1[$_] + 2 * $w2[$_] + 2 * $w3[$_] + $w4[$_])/6 } @indices;
  22900         58164  
84              
85 12283         27787 $self->_store_values( $t + $h, $y );
86              
87 12283         40282 return $y;
88             }
89              
90             sub _store_values
91             {
92 12283     12283   16970 my ($self,$t, $y) = @_;
93 12283 100       25636 return unless $self->{keep_values};
94 12261         50544 my $s = sprintf $self->{format}, $t ;
95 12261         14299 push @{ $self->{values}{$s} }, @$y;
  12261         53018  
96             }
97              
98             sub values_at
99             {
100 12230     12230 0 19167 my ($self,$t, %args) = @_;
101 12230 100       22477 if ($self->{keep_values}){
102 12229         12252 return @{ $self->{values}{sprintf($self->{format},$t)} };
  12229         76329  
103             } else {
104 1         62 warn "Values were not kept because keep_values was set to 0";
105 1         6 return;
106             }
107             }
108             # because Math::ODE implements a 4th order Runge-Kutta method
109 73     73 0 987 sub error { $_[0]->{step} ** 4 }
110              
111             sub file
112             {
113 1     1 0 9 my ($self,$target) = @_;
114              
115 1 50       4 if ($target) {
116 0         0 $self->{file} = $target;
117             } else {
118 1         28 return $self->{file};
119             }
120             }
121              
122             sub format
123             {
124 4     4 0 8 my ($self,$format) = @_;
125              
126 4 100       19 $format ? $self->{format} = $format : return $self->{format};
127             }
128              
129             sub step
130             {
131 0     0 0 0 my ($self,$step) = @_;
132 0 0       0 if (defined $step){
133 0 0 0     0 croak "Stepsize must be strictly between zero and one"
134             if ($step <= 0 || $step >= 1);
135              
136 0         0 $self->{step} = $step;
137             }
138 0         0 return $self->{step};
139             }
140              
141             sub initial
142             {
143 0     0 0 0 my ($self, $initial) = @_;
144              
145 0 0       0 croak "not an array ref" unless ref $initial eq "ARRAY";
146              
147 0         0 $self->{initial} = $initial;
148              
149             }
150              
151             sub _clear_values
152             {
153 82     82   123 my $self = shift;
154             $self->{values} = {}
155 82         203 }
156              
157             sub _init
158             {
159 41     41   211 my ($self,%args) = @_;
160              
161             # defaults
162 41         122 $self->{keep_values} = 1;
163 41         79 $self->{verbose} = 1;
164 41         77 $self->{step} = 0.1;
165 41         75 $self->{csv} = 0;
166 41   50     48 $self->{N} = scalar( @{ $args{ODE} } ) || 1;
167 41         88 $self->{format} = '%.12f';
168              
169 41         280 @$self{keys %args} = values %args;
170              
171 41         168 $self->_clear_values;
172              
173 41 50       78 if( $self->{N} != scalar( @{ $args{initial } }) ){
  41         158  
174 0         0 croak "Must have same number of initial conditions as equations!";
175             }
176              
177 41 50       175 croak "Stepsize must be positive!" if $self->{step} <= 0;
178 41 50       123 croak "\$self->t0 must be less than \$self->tf!" if $self->{t0} >= $self->{tf};
179              
180 41         187 return $self;
181             }
182              
183             sub max_error
184             {
185 36     36 0 286 my ($self, $sols) = @_;
186              
187 36         50 my $max_error = 0;
188              
189 36         53 for my $pt ( sort keys %{$self->{values}} ){
  36         8922  
190 12228         13758 my $k = 0;
191 12228         23468 my @vals = $self->values_at($pt);
192              
193 12228         21448 for my $sol ( @$sols ) {
194 15767         33354 my $res = abs( $vals[$k] - &$sol($pt) );
195 15767 100       1967137 $max_error = $res if ($res > $max_error);
196 15767         30097 $k++;
197             }
198             }
199 36         821 $max_error;
200             }
201              
202             sub new {
203 41     41 0 25882 my $class = shift;
204 41         90 my $self = {};
205 41         91 bless $self, $class;
206 41         147 $self->_init(@_);
207             }
208              
209             42;
210             __END__