File Coverage

blib/lib/Math/ODE.pm
Criterion Covered Total %
statement 101 112 90.1
branch 22 36 61.1
condition 2 7 28.5
subroutine 12 14 85.7
pod 0 7 0.0
total 137 176 77.8


line stmt bran cond sub pod time code
1             package Math::ODE;
2 7     7   183751 use strict;
  7         17  
  7         235  
3              
4 7     7   7099 use Data::Dumper;
  7         95847  
  7         630  
5 7     7   88 use Carp;
  7         19  
  7         10617  
6             my $VERSION = '0.05_01';
7              
8             $Data::Dumper::Varname = "y";
9             $Data::Dumper::Indent = 1;
10              
11             sub evolve
12             {
13 41     41 0 1999 my $self = shift;
14 41         85 my ($F,$h,$t,$initial,$file) = map{ $self->{$_} } qw(ODE step t0 initial file);
  205         458  
15 41 100 50     931 my $delim = $self->{csv} ? ',' : ($self->{delim} || $self->{delimeter} || " ");
16 41         62 my $y;
17             my $fh;
18              
19             # don't clobber the initial condition in case we want to do multiple runs
20             # with the same object
21 41         105 @$y = @$initial;
22              
23 41 100       112 if( defined $file ){
24 4 50       524 open($fh,'>', $file) or croak "$file: $!";
25             }
26              
27 41         94 $self->_clear_values;
28              
29 41         134 while ( $t < $self->{tf} ){
30             # use Runge Kutta 4th order to step from $t to $t + $h
31 12283         23269 $y = _RK4($self,$t,$y);
32              
33 12283 50       28730 warn "Exiting RK4 with t=$t ," . Dumper($y) . "\n" if( $self->{verbose} > 1 );
34              
35 12283         22614 for my $i ( 0 .. $self->{N}-1 ){
36             # check for under/over flow
37 22900 50       208918 next unless $y->[$i] =~ qr/nan|infinity/i;
38 0 0       0 warn "Bailing out, over/under flow at t=$t,y->[$i] = $y->[$i]" if $self->{verbose};
39 0         0 return undef;
40             }
41 12283         17202 $t += $h;
42              
43 12283         17982 my $str = join $delim, map { sprintf "%0.12f", $_ } ($t, @$y);
  35183         147359  
44 12283         18693 chop $str;
45              
46 12283 100       54385 if( defined $file ){
    100          
47 44         167 print $fh "$str\n";
48             } elsif ( ! $self->{keep_values} ) {
49 11         1539 print "$str\n";
50             }
51             }
52 41 100       377 close $fh if defined $file;
53 41         145 return $self;
54             }
55              
56             sub _RK4
57             {
58             # $t = dependent variable
59             # $y = $N - vector of independent variables
60             # $h = step size
61             # $F = arrayref of coderefs of the equations to solve
62 12283     12283   16204 my ($self,$t,$y) = @_;
63 12283         15759 my $F = $self->{ODE};
64 12283         18067 my $h = $self->{step};
65              
66             ## w vectors hold constants for equations
67             ## each $q holds a modified $y vector to feed to the next
68             ## for loop ( ie $y + $w1/2 , etc ... )
69 12283         12290 my (@w1,@w2,@w3,@w4,$q);
70 12283         27338 my @indices = ( 0 .. $self->{N} - 1 );
71              
72 12283         17696 map { $w1[$_] = $h * &{ $F->[$_] }($t,$y) } @indices;
  22900         70294  
  22900         49701  
73 12283         59313 map { $q->[$_] = $y->[$_] + 0.5*$w1[$_] } @indices;
  22900         55109  
74              
75 12283         15095 map { $w2[$_] = $h * &{ $F->[$_] }($t + 0.5*$h,$q) } @indices;
  22900         63909  
  22900         53152  
76 12283         57953 map { $q->[$_] = $y->[$_] + 0.5*$w2[$_] } @indices;
  22900         54091  
77              
78 12283         13657 map { $w3[$_] = $h * &{ $F->[$_] }($t + 0.5*$h,$q) } @indices;
  22900         60669  
  22900         47794  
79 12283         54845 map { $q->[$_] = $y->[$_] + $w3[$_] } @indices;
  22900         43070  
80              
81 12283         13226 map { $w4[$_] = $h * &{ $F->[$_] }($t + $h,$q) } @indices;
  22900         60510  
  22900         46445  
82 12283         77181 map { $y->[$_]+= ( $w1[$_] + 2 * $w2[$_] + 2 * $w3[$_] + $w4[$_])/6 } @indices;
  22900         60379  
83              
84 12283         26046 $self->_store_values( $t + $h, $y );
85              
86 12283         39877 return $y;
87             }
88              
89             sub _store_values
90             {
91 12283     12283   15377 my ($self,$t, $y) = @_;
92 12283 100       28732 return unless $self->{keep_values};
93 12261         46669 my $s = sprintf '%0.12f', $t ;
94 12261         12613 push @{ $self->{values}{$s} }, @$y;
  12261         57222  
95             }
96              
97             sub values_at
98             {
99 12230     12230 0 18242 my ($self,$t, %args) = @_;
100 12230 100       22333 if ($self->{keep_values}){
101 12229         11572 return @{ $self->{values}{sprintf('%0.12f',$t)} };
  12229         76312  
102             } else {
103 1         151 warn "Values were not kept because keep_values was set to 0";
104 1         6 return;
105             }
106             }
107             # because Math::ODE implements a 4th order Runge-Kutta method
108 73     73 0 923 sub error { $_[0]->{step} ** 4 }
109              
110             sub step
111             {
112 0     0 0 0 my ($self,$step) = @_;
113 0 0       0 if (defined $step){
114 0 0 0     0 croak "Stepsize must be strictly between zero and one"
115             if ($step <= 0 || $step >= 1);
116              
117 0         0 $self->{step} = $step;
118             }
119 0         0 return $self->{step};
120             }
121              
122             sub initial
123             {
124 0     0 0 0 my ($self, $initial) = @_;
125              
126 0 0       0 croak "not an array ref" unless ref $initial eq "ARRAY";
127              
128 0         0 $self->{initial} = $initial;
129              
130             }
131              
132             sub _clear_values
133             {
134 82     82   107 my $self = shift;
135 82         177 $self->{values} = {}
136             }
137              
138             sub _init
139             {
140 41     41   207 my ($self,%args) = @_;
141              
142             # defaults
143 41         114 $self->{keep_values} = 1;
144 41         81 $self->{verbose} = 1;
145 41         72 $self->{step} = 0.1;
146 41         81 $self->{csv} = 0;
147 41   50     52 $self->{N} = scalar( @{ $args{ODE} } ) || 1;
148              
149 41         257 @$self{keys %args} = values %args;
150              
151 41         147 $self->_clear_values;
152              
153 41 50       66 if( $self->{N} != scalar( @{ $args{initial } }) ){
  41         133  
154 0         0 croak "Must have same number of initial conditions as equations!";
155             }
156              
157 41 50       190 croak "Stepsize must be positive!" if $self->{step} <= 0;
158 41 50       135 croak "\$self->t0 must be less than \$self->tf!" if $self->{t0} >= $self->{tf};
159              
160 41         151 return $self;
161             }
162              
163             sub max_error
164             {
165 36     36 0 274 my ($self, $sols) = @_;
166              
167 36         43 my $max_error = 0;
168              
169 36         40 for my $pt ( sort keys %{$self->{values}} ){
  36         9158  
170 12228         13659 my $k = 0;
171 12228         21371 my @vals = $self->values_at($pt);
172              
173 12228         19180 for my $sol ( @$sols ) {
174 15767         34818 my $res = abs( $vals[$k] - &$sol($pt) );
175 15767 100       83546 $max_error = $res if ($res > $max_error);
176 15767         35639 $k++;
177             }
178             }
179 36         1355 $max_error;
180             }
181              
182             sub new {
183 41     41 0 27304 my $class = shift;
184 41         77 my $self = {};
185 41         117 bless $self, $class;
186 41         135 $self->_init(@_);
187             }
188              
189             42;
190             __END__