File Coverage

blib/lib/Math/RungeKutta.pm
Criterion Covered Total %
statement 464 522 88.8
branch 75 100 75.0
condition 12 27 44.4
subroutine 9 12 75.0
pod 6 9 66.6
total 566 670 84.4


line stmt bran cond sub pod time code
1             # Math::RungeKutta.pm
2             #########################################################################
3             # This Perl module is Copyright (c) 2002, Peter J Billam #
4             # c/o P J B Computing, www.pjb.com.au #
5             # #
6             # This module is free software; you can redistribute it and/or #
7             # modify it under the same terms as Perl itself. #
8             #########################################################################
9              
10             package Math::RungeKutta;
11 1     1   656 no strict; no warnings;
  1     1   2  
  1         37  
  1         5  
  1         1  
  1         335  
12             $VERSION = '1.07';
13             # gives a -w warning, but I'm afraid $VERSION .= ''; would confuse CPAN
14             require Exporter;
15             @ISA = qw(Exporter);
16             @EXPORT = qw(rk2 rk4 rk4_auto rk4_auto_midpoint);
17             @EXPORT_OK = qw(rk4_classical rk4_ralston arr2txt);
18             %EXPORT_TAGS = (ALL => [@EXPORT,@EXPORT_OK]);
19              
20             sub new {
21 0     0 0 0 my $arg1 = shift;
22 0   0     0 my $class = ref($arg1) || $arg1; # can be used as class or instance method
23 0         0 my $self = {}; # ref to an empty hash
24 0         0 bless $self, $class;
25 0         0 $self->_initialise();
26 0         0 return $self;
27             }
28              
29 32     32 1 660 sub rk2 { my ($ynref, $dydtref, $t, $dt) = @_;
30 32 50       80 if (ref $dydtref ne 'CODE') {
31 0         0 warn "Math::RungeKutta::rk2: 2nd arg must be a subroutine ref\n";
32 0         0 return ();
33             }
34              
35 32         34 my $gamma = .75; # Ralston's minimisation of error bounds
36 32         41 my $alpha = 0.5/$gamma; my $beta = 1.0-$gamma;
  32         37  
37 32         40 my $alphadt=$alpha*$dt; my $betadt=$beta*$dt; my $gammadt=$gamma*$dt;
  32         34  
  32         34  
38 32 100       79 if (ref $ynref eq 'ARRAY') {
    50          
39 16         27 my $ny = $#$ynref;
40 16         19 my @ynp1; $#ynp1 = $ny;
  16         34  
41 16         17 my @dydtn; $#dydtn = $ny;
  16         28  
42 16         18 my @ynpalpha; $#ynpalpha = $ny; # Gear calls this q
  16         23  
43 16         17 my @dydtnpalpha; $#dydtnpalpha = $ny;
  16         30  
44 16         22 @dydtn = &{$dydtref}($t, @$ynref);
  16         38  
45 1     1   881 my $i; for ($i=$[; $i<=$ny; $i++) {
  1         409  
  1         5270  
  16         222  
  16         63  
46 32         32 $ynpalpha[$i] = ${$ynref}[$i] + $alphadt*$dydtn[$i];
  32         145  
47             }
48 16         27 @dydtnpalpha = &{$dydtref}($t+$alphadt, @ynpalpha);
  16         43  
49 16         243 for ($i=$[; $i<=$ny; $i++) {
50 32         115 $ynp1[$i]
51 32         35 = ${$ynref}[$i]+$betadt*$dydtn[$i]+$gammadt*$dydtnpalpha[$i];
52             }
53 16         107 return ($t+$dt, @ynp1);
54              
55             } elsif (ref $ynref eq 'HASH') {
56 16         17 my %ynp1;
57             my %ynpalpha; # Gear calls this q
58 16         28 my %dydtn = &{$dydtref}($t, %$ynref);
  16         36  
59 16         168 foreach my $i (keys %$ynref) {
60 32         29 $ynpalpha{$i} = ${$ynref}{$i} + $alphadt*$dydtn{$i};
  32         90  
61             }
62 16         38 my %dydtnpalpha = &{$dydtref}($t+$alphadt, %ynpalpha);
  16         35  
63 16         164 foreach my $i (keys %$ynref) {
64 32         94 $ynp1{$i}
65 32         31 = ${$ynref}{$i}+$betadt*$dydtn{$i}+$gammadt*$dydtnpalpha{$i};
66             }
67 16         134 return ($t+$dt, %ynp1);
68             } else {
69 0         0 warn "Math::RungeKutta::rk2: 1st arg must be an arrayref or hashref\n";
70 0         0 return ();
71             }
72             }
73             my @saved_k0; my %saved_k0; my $use_saved_k0 = 0;
74 234     234 1 1346 sub rk4 { my ($ynref, $dydtref, $t, $dt) = @_;
75             # The Runge-Kutta-Merson 5-function-evaluation 4th-order method
76             # in the sine-cosine example, this seems to work as a 7th-order method !
77 234 50       608 if (ref $dydtref ne 'CODE') {
78 0         0 warn "Math::RungeKutta::rk4: 2nd arg must be a subroutine ref\n";
79 0         0 return ();
80             }
81 234 100       521 if (ref $ynref eq 'ARRAY') {
    50          
82 117         189 my $ny = $#$ynref; my $i;
  117         136  
83             # @eta0 = @#ynref;
84 117         518 my @k0; $#k0=$ny;
85 117 100       199 if ($use_saved_k0) { @k0 = @saved_k0;
  66         159  
86 51         71 } else { @k0 = &{$dydtref}($t, @$ynref);
  51         128  
87             }
88 117         1028 for ($i=$[; $i<=$ny; $i++) { $k0[$i] *= $dt; }
  234         589  
89 117         113 my @eta1; $#eta1=$ny;
  117         215  
90 117         383 for ($i=$[; $i<=$ny; $i++) { $eta1[$i] = ${$ynref}[$i] + $k0[$i]/3.0; }
  234         223  
  234         815  
91 117         120 my @k1; $#k1=$ny;
  117         255  
92 117         175 @k1 = &{$dydtref}($t + $dt/3.0, @eta1);
  117         294  
93 117         1826 for ($i=$[; $i<=$ny; $i++) { $k1[$i] *= $dt; }
  234         502  
94 117         120 my @eta2; $#eta2=$ny;
  117         285  
95 117         119 my @k2; $#k2=$ny;
  117         196  
96 117         391 for ($i=$[; $i<=$ny; $i++) {
97 234         228 $eta2[$i] = ${$ynref}[$i] + ($k0[$i]+$k1[$i])/6.0;
  234         834  
98             }
99 117         189 @k2 = &{$dydtref}($t + $dt/3.0, @eta2);
  117         389  
100 117         1698 for ($i=$[; $i<=$ny; $i++) { $k2[$i] *= $dt; }
  234         503  
101 117         114 my @eta3; $#eta3=$ny;
  117         237  
102 117         385 for ($i=$[; $i<=$ny; $i++) {
103 234         225 $eta3[$i] = ${$ynref}[$i] + ($k0[$i]+3.0*$k2[$i])*0.125;
  234         825  
104             }
105 117         120 my @k3; $#k3=$ny;
  117         212  
106 117         174 @k3 = &{$dydtref}($t+0.5*$dt, @eta3);
  117         279  
107 117         1716 for ($i=$[; $i<=$ny; $i++) { $k3[$i] *= $dt; }
  234         526  
108 117         106 my @eta4; $#eta4=$ny;
  117         225  
109 117         391 for ($i=$[; $i<=$ny; $i++) {
110 234         213 $eta4[$i] = ${$ynref}[$i] + ($k0[$i]-3.0*$k2[$i]+4.0*$k3[$i])*0.5;
  234         988  
111             }
112 117         116 my @k4; $#k4=$ny;
  117         203  
113 117         170 @k4 = &{$dydtref}($t+$dt, @eta4);
  117         360  
114 117         1839 for ($i=$[; $i<=$ny; $i++) { $k4[$i] *= $dt; }
  234         493  
115 117         102 my @ynp1; $#ynp1 = $ny;
  117         11538  
116 117         386 for ($i=$[; $i<=$ny; $i++) {
117 234         223 $ynp1[$i] = ${$ynref}[$i] + ($k0[$i]+4.0*$k3[$i]+$k4[$i])/6.0;
  234         904  
118             }
119             # Merson's method for error estimation, see Gear p85, only works
120             # if F is linear, ie F = Ay + bt, so that eta4 has no 4th-order
121             # errors. So in general step-doubling is the only way to do it.
122             # Estimate error terms ...
123             # if ($epsilon) {
124             # my $errmax = 0; my $diff;
125             # for ($i=$[; $i<=$ny; $i++) {
126             # $diff = 0.2 * abs ($ynp1[$i] - $eta4[$i]);
127             # if ($errmax < $diff) { $errmax = $diff; }
128             # }
129             # # print "errmax = $errmax\n"; # not much related to the actual error
130             # }
131 117         988 return ($t+$dt, @ynp1);
132              
133             } elsif (ref $ynref eq 'HASH') {
134 117         115 my %k0;
135 117 100       171 if ($use_saved_k0) { %k0 = %saved_k0;
  66         188  
136 51         100 } else { %k0 = &{$dydtref}($t, %$ynref);
  51         672  
137             }
138 117         726 foreach my $i (keys(%$ynref)) { $k0{$i} *= $dt; }
  234         393  
139 117         202 my %eta1;
140 117         203 foreach my $i (keys(%$ynref)) { $eta1{$i} = ${$ynref}{$i}+$k0{$i}/3.0; }
  234         226  
  234         739  
141 117         327 my %k1 = &{$dydtref}($t + $dt/3.0, %eta1);
  117         362  
142 117         1409 foreach my $i (keys(%$ynref)) { $k1{$i} *= $dt; }
  234         378  
143 117         153 my %eta2;
144 117         205 foreach my $i (keys(%$ynref)) {
145 234         238 $eta2{$i} = ${$ynref}{$i} + ($k0{$i}+$k1{$i})/6.0;
  234         705  
146             }
147 117         252 my %k2 = &{$dydtref}($t + $dt/3.0, %eta2);
  117         347  
148 117         1327 foreach my $i (keys(%$ynref)) { $k2{$i} *= $dt; }
  234         364  
149 117         168 my %eta3;
150 117         201 foreach my $i (keys(%$ynref)) {
151 234         373 $eta3{$i} = ${$ynref}{$i} + ($k0{$i}+3.0*$k2{$i})*0.125;
  234         981  
152             }
153 117         269 my %k3 = &{$dydtref}($t+0.5*$dt, %eta3);
  117         347  
154 117         1215 foreach my $i (keys(%$ynref)) { $k3{$i} *= $dt; }
  234         374  
155 117         156 my %eta4;
156 117         201 foreach my $i (keys(%$ynref)) {
157 234         262 $eta4{$i} = ${$ynref}{$i} + ($k0{$i}-3.0*$k2{$i}+4.0*$k3{$i})*0.5;
  234         749  
158             }
159 117         294 my %k4 = &{$dydtref}($t+$dt, %eta4);
  117         275  
160 117         1278 foreach my $i (keys(%$ynref)) { $k4{$i} *= $dt; }
  234         372  
161 117         164 my %ynp1;
162 117         195 foreach my $i (keys(%$ynref)) {
163 234         321 $ynp1{$i} = ${$ynref}{$i} + ($k0{$i}+4.0*$k3{$i}+$k4{$i})/6.0;
  234         739  
164             }
165 117         1082 return ($t+$dt, %ynp1);
166             } else {
167 0         0 warn "Math::RungeKutta::rk4: 1st arg must be an arrayref or hashref\n";
168 0         0 return ();
169             }
170             }
171             my $t; my $halfdt;
172             my @y2; my %y2; # need to be remembered for the midpoint
173 38     38 1 2265 sub rk4_auto { my $ynref=shift; my $dydtref=shift; $t=shift;
  38         123  
  38         45  
174 38         53 my ($dt, $arg4) = @_;
175 38 50       95 if (ref $dydtref ne 'CODE') {
176 0         0 warn "Math::RungeKutta::rk4_auto: 2nd arg must be a subroutine ref\n";
177 0         0 return ();
178             }
179 38 50       71 if ($dt == 0) { $dt = 0.1; }
  0         0  
180 38 100       95 if (ref $ynref eq 'ARRAY') {
    50          
181 19         24 my @errors; my $epsilon;
182 19 100       37 if (ref $arg4 eq 'ARRAY') {
183 9         21 @errors = @$arg4; undef $epsilon;
  9         34  
184             } else {
185 10         14 $epsilon = abs $arg4; undef @errors;
  10         13  
186 10 50       21 if (! $epsilon) { $epsilon = .0000001; }
  0         0  
187             }
188 19         31 my $ny = $#$ynref; my $i;
  19         23  
189 19         129 my @y1; $#y1 = ny;
190 19         44 $#y2 = ny; %y2 = ();
  19         34  
191 19         19 my @y3; $#y3 = ny;
  19         47  
192 19         50 $#saved_k0 = ny; @saved_k0 = &{$dydtref}($t, @$ynref);
  19         28  
  19         52  
193 19         257 my $resizings = 0;
194 19         23 my $highest_low_error = 0.1E-99; my $highest_low_dt = 0.0;
  19         22  
195 19         18 my $lowest_high_error = 9.9E99; my $lowest_high_dt = 9.9E99;
  19         20  
196 19         23 while (1) {
197 33         40 $halfdt = 0.5 * $dt; my $dummy;
  33         31  
198 33         33 $use_saved_k0 = 1;
199 33         63 ($dummy, @y1) = &rk4($ynref, $dydtref, $t, $dt);
200 33         80 ($dummy, @y2) = &rk4($ynref, $dydtref, $t, $halfdt);
201 33         46 $use_saved_k0 = 0;
202 33         83 ($dummy, @y3) = &rk4(\@y2, $dydtref, $t+$halfdt, $halfdt);
203 33         51 my $relative_error;
204 33 100       80 if ($epsilon) {
    50          
205 13         15 my $errmax = 0; my $diff; my $ymax = 0;
  13         13  
  13         16  
206 13         46 for ($i=$[; $i<=$ny; $i++) {
207 26         47 $diff = abs ($y1[$i]-$y3[$i]);
208 26 100       54 if ($errmax < $diff) { $errmax = $diff; }
  18         23  
209 26 100       26 if ($ymax < abs ${$ynref}[$i]) {$ymax = abs ${$ynref}[$i];}
  26         74  
  17         13  
  17         48  
210             }
211 13         61 $relative_error = $errmax/($epsilon*$ymax);
212             } elsif (@errors) {
213 20         32 $relative_error = 0.0; my $diff;
  20         19  
214 20         75 for ($i=$[; $i<=$ny; $i++) {
215 40         72 $diff = abs ($y1[$i]-$y3[$i]) / abs $errors[$i];
216 40 100       84 if ($relative_error < $diff) { $relative_error = $diff; }
  39         92  
217             }
218 0         0 } else { die
219             "RungeKutta::rk4_auto: \$epsilon & \@errors both undefined\n";
220             }
221             # Gear's correction assumes error is always in 5th-order terms :-(
222             # $y1[$i] = (16.0*$y3{$i] - $y1[$i]) / 15.0;
223 33 100       96 if ($relative_error < 0.60) {
    100          
224 9 50       21 if ($dt > $highest_low_dt) {
225 9         10 $highest_low_error = $relative_error;
226 9         11 $highest_low_dt = $dt;
227             }
228             } elsif ($relative_error > 1.67) {
229 5 50       97 if ($dt < $lowest_high_dt) {
230 5         9 $lowest_high_error = $relative_error;
231 5         7 $lowest_high_dt = $dt;
232             }
233             } else {
234 19         34 last;
235             }
236 14 100 100     58 if ($lowest_high_dt<9.8E99 && $highest_low_dt>1.0E-99) { # interp
237 3         10 my $denom = log ($lowest_high_error/$highest_low_error);
238 3 50 33     28 if ($highest_low_dt==0.0||$highest_low_error==0.0||$denom==0.0){
      33        
239 0         0 $dt = 0.5 * ($highest_low_dt+$lowest_high_dt);
240             } else {
241 3         27 $dt = $highest_low_dt * ( ($lowest_high_dt/$highest_low_dt)
242             ** ((log (1.0/$highest_low_error)) / $denom) );
243             }
244             } else {
245 11         70 my $adjust = $relative_error**(-0.2); # hope error is 5th-order ...
246 11 100       24 if (abs $adjust > 2.0) {
247 4         8 $dt *= 2.0; # prevent infinity if 4th-order is exact ...
248             } else {
249 7         12 $dt *= $adjust;
250             }
251             }
252 14         13 $resizings++;
253 14 50 33     319 if ($resizings>4 && $highest_low_dt>1.0E-99) {
254             # hope a small step forward gets us out of this mess ...
255 0         0 $dt = $highest_low_dt; $halfdt = 0.5 * $dt;
  0         0  
256 0         0 $use_saved_k0 = 1;
257 0         0 ($dummy, @y2) = &rk4($ynref, $dydtref, $t, $halfdt);
258 0         0 $use_saved_k0 = 0;
259 0         0 ($dummy, @y3) = &rk4(\@y2, $dydtref, $t+$halfdt, $halfdt);
260 0         0 last;
261             }
262             }
263 19         121 return ($t+$dt, $dt, @y3);
264              
265             } elsif (ref $ynref eq 'HASH') {
266 19         19 my %errors; my $epsilon;
267 19 100       38 if (ref $arg4 eq 'HASH') {
268 9         24 %errors = %$arg4; undef $epsilon;
  9         16  
269             } else {
270 10         11 $epsilon = abs $arg4; undef %errors;
  10         13  
271 10 50       22 if (! $epsilon) { $epsilon = .0000001; }
  0         0  
272             }
273 19         18 my $i; my %y1; @y2 = (); my %y3;
  19         91  
  19         22  
274 19         34 %saved_k0 = &{$dydtref}($t, %$ynref);
  19         50  
275 19         260 my $resizings = 0;
276 19         22 my $highest_low_error = 0.1E-99; my $highest_low_dt = 0.0;
  19         23  
277 19         20 my $lowest_high_error = 9.9E99; my $lowest_high_dt = 9.9E99;
  19         21  
278 19         18 while (1) {
279 33         37 $halfdt = 0.5 * $dt; my $dummy;
  33         36  
280 33         35 $use_saved_k0 = 1;
281 33         59 ($dummy, %y1) = &rk4($ynref, $dydtref, $t, $dt);
282 33         89 ($dummy, %y2) = &rk4($ynref, $dydtref, $t, $halfdt);
283 33         65 $use_saved_k0 = 0;
284 33         85 ($dummy, %y3) = &rk4(\%y2, $dydtref, $t+$halfdt, $halfdt);
285 33         62 my $relative_error;
286 33 100       105 if ($epsilon) {
    50          
287 13         14 my $errmax = 0; my $diff; my $ymax = 0;
  13         14  
  13         15  
288 13         30 foreach $i (keys(%$ynref)) {
289 26         48 $diff = abs ($y1{$i}-$y3{$i});
290 26 100       60 if ($errmax < $diff) { $errmax = $diff; }
  20         22  
291 26 100       27 if ($ymax < abs ${$ynref}{$i}) {$ymax = abs ${$ynref}{$i};}
  26         82  
  15         15  
  15         31  
292             }
293 13         44 $relative_error = $errmax/($epsilon*$ymax);
294             } elsif (%errors) {
295 20         25 $relative_error = 0.0; my $diff;
  20         22  
296 20         36 foreach $i (keys(%$ynref)) {
297 40         106 $diff = abs ($y1{$i}-$y3{$i}) / abs $errors{$i};
298 40 100       152 if ($relative_error < $diff) { $relative_error = $diff; }
  31         61  
299             }
300 0         0 } else { die
301             "RungeKutta::rk4_auto: \$epsilon & \%errors both undefined\n";
302             }
303             # Gear's correction assumes error is always in 5th-order terms :-(
304             # $y1[$i] = (16.0*$y3{$i] - $y1[$i]) / 15.0;
305 33 100       93 if ($relative_error < 0.60) {
    100          
306 9 50       24 if ($dt > $highest_low_dt) {
307 9         12 $highest_low_error = $relative_error;
308 9         13 $highest_low_dt = $dt;
309             }
310             } elsif ($relative_error > 1.67) {
311 5 50       22 if ($dt < $lowest_high_dt) {
312 5         9 $lowest_high_error = $relative_error;
313 5         6 $lowest_high_dt = $dt;
314             }
315             } else {
316 19         24 last;
317             }
318 14 100 100     52 if ($lowest_high_dt<9.8E99 && $highest_low_dt>1.0E-99) { # interp
319 3         9 my $denom = log ($lowest_high_error/$highest_low_error);
320 3 50 33     28 if ($highest_low_dt==0.0||$highest_low_error==0.0||$denom==0.0){
      33        
321 0         0 $dt = 0.5 * ($highest_low_dt+$lowest_high_dt);
322             } else {
323 3         12 $dt = $highest_low_dt * ( ($lowest_high_dt/$highest_low_dt)
324             ** ((log (1.0/$highest_low_error)) / $denom) );
325             }
326             } else {
327 11         29 my $adjust = $relative_error**(-0.2); # hope error is 5th-order ...
328 11 100       23 if (abs $adjust > 2.0) {
329 4         14 $dt *= 2.0; # prevent infinity if 4th-order is exact ...
330             } else {
331 7         11 $dt *= $adjust;
332             }
333             }
334 14         15 $resizings++;
335 14 50 33     35 if ($resizings>4 && $highest_low_dt>1.0E-99) {
336             # hope a small step forward gets us out of this mess ...
337 0         0 $dt = $highest_low_dt; $halfdt = 0.5 * $dt;
  0         0  
338 0         0 $use_saved_k0 = 1;
339 0         0 ($dummy, %y2) = &rk4($ynref, $dydtref, $t, $halfdt);
340 0         0 $use_saved_k0 = 0;
341 0         0 ($dummy, %y3) = &rk4(\%y2, $dydtref, $t+$halfdt, $halfdt);
342 0         0 last;
343             }
344             }
345 19         132 return ($t+$dt, $dt, %y3);
346              
347 0         0 } else { die
348             "Math::RungeKutta::rk4_auto: 1st arg must be arrayref or hashref\n";
349             # return ();
350             }
351             }
352             sub rk4_auto_midpoint {
353 38 100   38 1 169 if (@y2) { return ($t+$halfdt, @y2); } else { return ($t+$halfdt, %y2); }
  19         60  
  19         83  
354             }
355              
356             # ---------------------- EXPORT_OK routines ----------------------
357              
358 32     32 1 972 sub rk4_ralston { my ($ynref, $dydtref, $t, $dt) = @_;
359 32 50       82 if (ref $dydtref ne 'CODE') {
360 0         0 warn "RungeKutta::rk4_ralston: 2nd arg must be a subroutine ref\n";
361 0         0 return ();
362             }
363             # Ralston's minimisation of error bounds, see Gear p36
364 32 100       81 if (ref $ynref eq 'ARRAY') {
    50          
365 16         22 my $ny = $#$ynref; my $i;
  16         20  
366 16         20 my $alpha1=0.4; my $alpha2 = 0.4557372542; # = .875 - .1875*(sqrt 5);
  16         17  
367 16         17 my @k0; $#k0=$ny;
  16         34  
368 16         24 @k0 = &{$dydtref}($t, @$ynref);
  16         53  
369 16         249 for ($i=$[; $i<=$ny; $i++) { $k0[$i] *= $dt; }
  32         77  
370 16         15 my @k1; $#k1=$ny;
  16         30  
371 16         54 for ($i=$[; $i<=$ny; $i++) { $k1[$i] = ${$ynref}[$i] + 0.4*$k0[$i]; }
  32         33  
  32         113  
372 16         27 @k1 = &{$dydtref}($t + $alpha1*$dt, @k1);
  16         39  
373 16         230 for ($i=$[; $i<=$ny; $i++) { $k1[$i] *= $dt; }
  32         144  
374 16         20 my @k2; $#k2=$ny;
  16         30  
375 16         55 for ($i=$[; $i<=$ny; $i++) {
376 32         33 $k2[$i] = ${$ynref}[$i] + 0.2969776*$k0[$i] + 0.15875966*$k1[$i];
  32         115  
377             }
378 16         28 @k2 = &{$dydtref}($t + $alpha2*$dt, @k2);
  16         41  
379 16         236 for ($i=$[; $i<=$ny; $i++) { $k2[$i] *= $dt; }
  32         71  
380 16         17 my @k3; $#k3=$ny;
  16         33  
381 16         55 for ($i=$[; $i<=$ny; $i++) {
382 32         32 $k3[$i] = ${$ynref}[$i] + 0.21810038*$k0[$i] - 3.0509647*$k1[$i]
  32         133  
383             + 3.83286432*$k2[$i];
384             }
385 16         21 @k3 = &{$dydtref}($t+$dt, @k3);
  16         43  
386 16         248 for ($i=$[; $i<=$ny; $i++) { $k3[$i] *= $dt; }
  32         69  
387 16         16 my @ynp1; $#ynp1 = $ny;
  16         32  
388 16         55 for ($i=$[; $i<=$ny; $i++) {
389 32         32 $ynp1[$i] = ${$ynref}[$i] + 0.17476028*$k0[$i]
  32         126  
390             - 0.55148053*$k1[$i] + 1.20553547*$k2[$i] + 0.17118478*$k3[$i];
391             }
392 16         111 return ($t+$dt, @ynp1);
393              
394             } elsif (ref $ynref eq 'HASH') {
395 16         16 my $i;
396 16         19 my $alpha1=0.4; my $alpha2 = 0.4557372542; # = .875 - .1875*(sqrt 5);
  16         17  
397 16         27 my %k0 = &{$dydtref}($t, %$ynref);
  16         37  
398 16         163 foreach $i (keys(%$ynref)) { $k0{$i} *= $dt; }
  32         56  
399 16         23 my %k1;
400 16         24 foreach $i (keys(%$ynref)) { $k1{$i} = ${$ynref}{$i} + 0.4*$k0{$i}; }
  32         27  
  32         104  
401 16         34 %k1 = &{$dydtref}($t + $alpha1*$dt, %k1);
  16         38  
402 16         167 foreach $i (keys(%$ynref)) { $k1{$i} *= $dt; }
  32         48  
403 16         24 my %k2;
404 16         23 foreach $i (keys(%$ynref)) {
405 32         28 $k2{$i} = ${$ynref}{$i} + 0.2969776*$k0{$i} + 0.15875966*$k1{$i};
  32         89  
406             }
407 16         45 %k2 = &{$dydtref}($t + $alpha2*$dt, %k2);
  16         37  
408 16         163 foreach $i (keys(%$ynref)) { $k2{$i} *= $dt; }
  32         48  
409 16         24 my %k3;
410 16         24 foreach $i (keys(%$ynref)) {
411 32         30 $k3{$i} = ${$ynref}{$i} + 0.21810038*$k0{$i} - 3.0509647*$k1{$i}
  32         101  
412             + 3.83286432*$k2{$i};
413             }
414 16         30 %k3 = &{$dydtref}($t+$dt, %k3);
  16         38  
415 16         165 foreach $i (keys(%$ynref)) { $k3{$i} *= $dt; }
  32         44  
416 16         23 my %ynp1;
417 16         28 foreach $i (keys(%$ynref)) {
418 32         29 $ynp1{$i} = ${$ynref}{$i} + 0.17476028*$k0{$i}
  32         101  
419             - 0.55148053*$k1{$i} + 1.20553547*$k2{$i} + 0.17118478*$k3{$i};
420             }
421 16         113 return ($t+$dt, %ynp1);
422              
423             } else {
424 0         0 warn "Math::RungeKutta::rk4_ralston: 1st arg must be arrayref or hashref\n";
425 0         0 return ();
426             }
427             }
428 32     32 1 921 sub rk4_classical { my ($ynref, $dydtref, $t, $dt) = @_;
429 32 50       77 if (ref $dydtref ne 'CODE') {
430 0         0 warn "RungeKutta::rk4_classical: 2nd arg must be subroutine ref\n";
431 0         0 return ();
432             }
433             # The Classical 4th-order Runge-Kutta Method, see Gear p35
434 32 100       75 if (ref $ynref eq 'ARRAY') {
    50          
435 16         24 my $ny = $#$ynref; my $i;
  16         20  
436 16         37 my @k0; $#k0=$ny;
437 16         21 @k0 = &{$dydtref}($t, @$ynref);
  16         39  
438 16         246 for ($i=$[; $i<=$ny; $i++) { $k0[$i] *= $dt; }
  32         79  
439 16         21 my @eta1; $#eta1=$ny;
  16         32  
440 16         54 for ($i=$[; $i<=$ny; $i++) { $eta1[$i] = ${$ynref}[$i] + 0.5*$k0[$i]; }
  32         27  
  32         113  
441 16         17 my @k1; $#k1=$ny;
  16         32  
442 16         26 @k1 = &{$dydtref}($t+0.5*$dt, @eta1);
  16         43  
443 16         268 for ($i=$[; $i<=$ny; $i++) { $k1[$i] *= $dt; }
  32         69  
444 16         15 my @eta2; $#eta2=$ny;
  16         33  
445 16         53 for ($i=$[; $i<=$ny; $i++) { $eta2[$i] = ${$ynref}[$i] + 0.5*$k1[$i]; }
  32         31  
  32         105  
446 16         17 my @k2; $#k2=$ny;
  16         29  
447 16         23 @k2 = &{$dydtref}($t+0.5*$dt, @eta2);
  16         40  
448 16         230 for ($i=$[; $i<=$ny; $i++) { $k2[$i] *= $dt; }
  32         69  
449 16         14 my @eta3; $#eta3=$ny;
  16         61  
450 16         55 for ($i=$[; $i<=$ny; $i++) { $eta3[$i] = ${$ynref}[$i] + $k2[$i]; }
  32         32  
  32         105  
451 16         15 my @k3; $#k3=$ny;
  16         31  
452 16         38 @k3 = &{$dydtref}($t+$dt, @eta3);
  16         41  
453 16         246 for ($i=$[; $i<=$ny; $i++) { $k3[$i] *= $dt; }
  32         71  
454 16         15 my @ynp1; $#ynp1 = $ny;
  16         32  
455 16         54 for ($i=$[; $i<=$ny; $i++) {
456 32         49 $ynp1[$i] = ${$ynref}[$i] +
  32         133  
457             ($k0[$i] + 2.0*$k1[$i] + 2.0*$k2[$i] + $k3[$i]) / 6.0;
458             }
459 16         148 return ($t+$dt, @ynp1);
460              
461             } elsif (ref $ynref eq 'HASH') {
462 16         28 my %k0 = &{$dydtref}($t, %$ynref);
  16         39  
463 16         164 foreach my $i (keys(%$ynref)) { $k0{$i} *= $dt; }
  32         54  
464 16         21 my %eta1;
465 16         31 foreach $i (keys(%$ynref)) { $eta1{$i} = ${$ynref}{$i} + 0.5*$k0{$i}; }
  32         31  
  32         87  
466 16         34 my %k1 = &{$dydtref}($t+0.5*$dt, %eta1);
  16         34  
467 16         172 foreach $i (keys(%$ynref)) { $k1{$i} *= $dt; }
  32         53  
468 16         19 my %eta2;
469 16         30 foreach $i (keys(%$ynref)) { $eta2{$i} = ${$ynref}{$i} + 0.5*$k1{$i}; }
  32         38  
  32         78  
470 16         36 my %k2 = &{$dydtref}($t+0.5*$dt, %eta2);
  16         41  
471 16         193 foreach $i (keys(%$ynref)) { $k2{$i} *= $dt; }
  32         48  
472 16         23 my %eta3;
473 16         26 foreach $i (keys(%$ynref)) { $eta3{$i} = ${$ynref}{$i} + $k2{$i}; }
  32         31  
  32         75  
474 16         30 my %k3 = &{$dydtref}($t+$dt, %eta3);
  16         38  
475 16         152 foreach $i (keys(%$ynref)) { $k3{$i} *= $dt; }
  32         47  
476 16         21 my %ynp1;
477 16         30 foreach $i (keys(%$ynref)) {
478 32         31 $ynp1{$i} = ${$ynref}{$i} +
  32         108  
479             ($k0{$i} + 2.0*$k1{$i} + 2.0*$k2{$i} + $k3{$i}) / 6.0;
480             }
481 16         124 return ($t+$dt, %ynp1);
482              
483             } else {
484 0           warn "Math::RungeKutta::rk4_classical: 1st arg must be arrayref or hashref\n";
485 0           return ();
486             }
487             }
488              
489             # --------------------- infrastructure ----------------------
490              
491             sub arr2txt { # neat printing of arrays for debug use
492 0     0 0   my @txt; foreach (@_) { push @txt, sprintf('%g',$_); }
  0            
  0            
493 0           return join (' ',@txt)."\n";
494             }
495             my $flag;
496 0     0 0   sub gaussn { my $standdev = $_[$[];
497             # returns normal distribution around 0.0 by the Box-Muller rules
498 0 0         if (! $flag) {
499 0           $a = sqrt(-2.0 * log(rand)); $b = 6.28318531 * rand;
  0            
500 0           $flag = 1; return ($standdev * $a * sin($b));
  0            
501             } else {
502 0           $flag = 0; return ($standdev * $a * cos($b));
  0            
503             }
504             }
505             1;
506              
507             __END__