File Coverage

blib/lib/Math/ODE.pm
Criterion Covered Total %
statement 107 119 89.9
branch 23 38 60.5
condition 2 7 28.5
subroutine 14 16 87.5
pod 0 8 0.0
total 146 188 77.6


line stmt bran cond sub pod time code
1             package Math::ODE;
2 7     7   171574 use strict;
  7         15  
  7         188  
3 7     7   36 use warnings;
  7         12  
  7         212  
4              
5 7     7   6584 use Data::Dumper;
  7         74228  
  7         489  
6 7     7   54 use Carp;
  7         15  
  7         11161  
7             my $VERSION = '0.06';
8              
9             $Data::Dumper::Varname = "y";
10             $Data::Dumper::Indent = 1;
11              
12             sub evolve
13             {
14 41     41 0 1221 my $self = shift;
15 41         89 my ($F,$h,$t,$initial,$file) = map{ $self->{$_} } qw(ODE step t0 initial file);
  205         437  
16 41 100 50     336 my $delim = $self->{csv} ? ',' : ($self->{delim} || $self->{delimeter} || " ");
17 41         64 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         102 @$y = @$initial;
23              
24 41 100       97 if( defined $file ){
25 4 50       458 open($fh,'>', $file) or croak "$file: $!";
26             }
27              
28 41         88 $self->_clear_values;
29              
30 41         137 while ( $t < $self->{tf} ){
31             # use Runge Kutta 4th order to step from $t to $t + $h
32 12283         22836 $y = _RK4($self,$t,$y);
33              
34 12283 50       28437 warn "Exiting RK4 with t=$t ," . Dumper($y) . "\n" if( $self->{verbose} > 1 );
35              
36 12283         24358 for my $i ( 0 .. $self->{N}-1 ){
37             # check for under/over flow
38 22900 50       186458 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         16612 $t += $h;
43              
44 12283         18650 my $str = join $delim, map { sprintf "%0.12f", $_ } ($t, @$y);
  35183         143553  
45 12283         20778 chop $str;
46              
47 12283 100       52928 if( defined $file ){
    100          
48 44         189 print $fh "$str\n";
49             } elsif ( ! $self->{keep_values} ) {
50 11         1641 print "$str\n";
51             }
52             }
53 41 100       281 close $fh if defined $file;
54 41         145 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   17115 my ($self,$t,$y) = @_;
64 12283         16238 my $F = $self->{ODE};
65 12283         16153 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         12852 my (@w1,@w2,@w3,@w4,$q);
71 12283         26614 my @indices = ( 0 .. $self->{N} - 1 );
72              
73 12283         17633 map { $w1[$_] = $h * &{ $F->[$_] }($t,$y) } @indices;
  22900         58375  
  22900         50248  
74 12283         58508 map { $q->[$_] = $y->[$_] + 0.5*$w1[$_] } @indices;
  22900         54351  
75              
76 12283         15888 map { $w2[$_] = $h * &{ $F->[$_] }($t + 0.5*$h,$q) } @indices;
  22900         62043  
  22900         50355  
77 12283         54839 map { $q->[$_] = $y->[$_] + 0.5*$w2[$_] } @indices;
  22900         46158  
78              
79 12283         15633 map { $w3[$_] = $h * &{ $F->[$_] }($t + 0.5*$h,$q) } @indices;
  22900         60216  
  22900         51710  
80 12283         52881 map { $q->[$_] = $y->[$_] + $w3[$_] } @indices;
  22900         42543  
81              
82 12283         15418 map { $w4[$_] = $h * &{ $F->[$_] }($t + $h,$q) } @indices;
  22900         58302  
  22900         46766  
83 12283         52058 map { $y->[$_]+= ( $w1[$_] + 2 * $w2[$_] + 2 * $w3[$_] + $w4[$_])/6 } @indices;
  22900         55042  
84              
85 12283         25844 $self->_store_values( $t + $h, $y );
86              
87 12283         37450 return $y;
88             }
89              
90             sub _store_values
91             {
92 12283     12283   15938 my ($self,$t, $y) = @_;
93 12283 100       24199 return unless $self->{keep_values};
94 12261         44342 my $s = sprintf '%0.12f', $t ;
95 12261         13620 push @{ $self->{values}{$s} }, @$y;
  12261         48977  
96             }
97              
98             sub values_at
99             {
100 12230     12230 0 18627 my ($self,$t, %args) = @_;
101 12230 100       21344 if ($self->{keep_values}){
102 12229         11515 return @{ $self->{values}{sprintf('%0.12f',$t)} };
  12229         70367  
103             } else {
104 1         140 warn "Values were not kept because keep_values was set to 0";
105 1         7 return;
106             }
107             }
108             # because Math::ODE implements a 4th order Runge-Kutta method
109 73     73 0 841 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         26 return $self->{file};
119             }
120             }
121              
122             sub step
123             {
124 0     0 0 0 my ($self,$step) = @_;
125 0 0       0 if (defined $step){
126 0 0 0     0 croak "Stepsize must be strictly between zero and one"
127             if ($step <= 0 || $step >= 1);
128              
129 0         0 $self->{step} = $step;
130             }
131 0         0 return $self->{step};
132             }
133              
134             sub initial
135             {
136 0     0 0 0 my ($self, $initial) = @_;
137              
138 0 0       0 croak "not an array ref" unless ref $initial eq "ARRAY";
139              
140 0         0 $self->{initial} = $initial;
141              
142             }
143              
144             sub _clear_values
145             {
146 82     82   142 my $self = shift;
147             $self->{values} = {}
148 82         177 }
149              
150             sub _init
151             {
152 41     41   178 my ($self,%args) = @_;
153              
154             # defaults
155 41         117 $self->{keep_values} = 1;
156 41         91 $self->{verbose} = 1;
157 41         63 $self->{step} = 0.1;
158 41         64 $self->{csv} = 0;
159 41   50     45 $self->{N} = scalar( @{ $args{ODE} } ) || 1;
160              
161 41         253 @$self{keys %args} = values %args;
162              
163 41         141 $self->_clear_values;
164              
165 41 50       70 if( $self->{N} != scalar( @{ $args{initial } }) ){
  41         152  
166 0         0 croak "Must have same number of initial conditions as equations!";
167             }
168              
169 41 50       172 croak "Stepsize must be positive!" if $self->{step} <= 0;
170 41 50       119 croak "\$self->t0 must be less than \$self->tf!" if $self->{t0} >= $self->{tf};
171              
172 41         189 return $self;
173             }
174              
175             sub max_error
176             {
177 36     36 0 246 my ($self, $sols) = @_;
178              
179 36         45 my $max_error = 0;
180              
181 36         46 for my $pt ( sort keys %{$self->{values}} ){
  36         8066  
182 12228         14069 my $k = 0;
183 12228         22087 my @vals = $self->values_at($pt);
184              
185 12228         20817 for my $sol ( @$sols ) {
186 15767         31901 my $res = abs( $vals[$k] - &$sol($pt) );
187 15767 100       2142055 $max_error = $res if ($res > $max_error);
188 15767         28336 $k++;
189             }
190             }
191 36         755 $max_error;
192             }
193              
194             sub new {
195 41     41 0 22698 my $class = shift;
196 41         77 my $self = {};
197 41         80 bless $self, $class;
198 41         130 $self->_init(@_);
199             }
200              
201             42;
202             __END__