Euler method

Revision as of 23:55, 12 June 2011 by Util (talk | contribs) (→‎{{header|Perl 6}}: Added Perl 6 solution)

Euler's method numerically approximates solutions of first-order ordinary differential equations (ODEs) with a given initial value. It is an explicit method for solving initial value problems (IVPs), as described in the wikipedia page. The ODE has to be provided in the following form:

Task
Euler method
You are encouraged to solve this task according to the task description, using any language you may know.

with an initial value

To get a numeric solution, we replace the derivative on the LHS with a finite difference approximation:

then solve for :

which is the same as

The iterative solution rule is then:

is the step size, the most relevant parameter for accuracy of the solution. A smaller step size increases accuracy but also the computation cost, so it has always has to be hand-picked according to the problem at hand.

Example: Newton's Cooling Law

Newton's cooling law describes how an object of initial temperature cools down in an environment of temperature :

or

It says that the cooling rate of the object is proportional to the current temperature difference to the surrounding environment.

The analytical solution, which we will compare to the numerical approximation, is

Task

The task is to implement a routine of Euler's method and then to use it to solve the given example of Newton's cooling law with it for three different step sizes of 2 s, 5 s and 10 s and to compare with the analytical solution. The initial temperature shall be 100 °C, the room temperature 20 °C, and the cooling constant 0.07. The time interval to calculate shall be from 0 s to 100 s.

A reference solution (Common Lisp) can be seen below. We see that bigger step sizes lead to reduced approximation accuracy.

Ada

The solution is generic, usable for any floating point type. The package specification: <lang Ada> generic

  type Number is digits <>;

package Euler is

  type Waveform is array (Integer range <>) of Number;
  function Solve
           (  F      : not null access function (T, Y : Number) return Number;
              Y0     : Number;
              T0, T1 : Number;
              N      : Positive
           )  return Waveform;

end Euler; </lang> The function Solve returns the solution of the differential equation for each of N+1 points, starting from the point T0. The implementation: <lang Ada> package body Euler is

  function Solve
           (  F      : not null access function (T, Y : Number) return Number;
              Y0     : Number;
              T0, T1 : Number;
              N      : Positive
           )  return Waveform is
     dT : constant Number := (T1 - T0) / Number (N);
  begin
     return Y : Waveform (0..N) do
        Y (0) := Y0;
        for I in 1..Y'Last loop
           Y (I) := Y (I - 1) + dT * F (T0 + dT * Number (I - 1), Y (I - 1));
        end loop;
     end return;
  end Solve;

end Euler; </lang> The test program: <lang Ada> with Ada.Text_IO; use Ada.Text_IO; with Euler;

procedure Test_Euler_Method is

  package Float_Euler is new Euler (Float);
  use Float_Euler;
  function Newton_Cooling_Law (T, Y : Float) return Float is
  begin
     return -0.07 * (Y - 20.0);
  end Newton_Cooling_Law;
  
  Y : Waveform := Solve (Newton_Cooling_Law'Access, 100.0, 0.0, 100.0, 10);

begin

  for I in Y'Range loop
     Put_Line (Integer'Image (10 * I) & ":" & Float'Image (Y (I)));
  end loop;

end Test_Euler_Method; </lang> Sample output:

 0: 1.00000E+02
 10: 4.40000E+01
 20: 2.72000E+01
 30: 2.21600E+01
 40: 2.06480E+01
 50: 2.01944E+01
 60: 2.00583E+01
 70: 2.00175E+01
 80: 2.00052E+01
 90: 2.00016E+01
 100: 2.00005E+01

ALGOL 68

Translation of: D

Note: This specimen retains the original D coding style.

Works with: ALGOL 68 version Revision 1 - no extensions to language used.
Works with: ALGOL 68G version Any - tested with release 1.18.0-9h.tiny.

<lang algol68># Approximates y(t) in y'(t)=f(t,y) with y(a)=y0 and t=a..b and the step size h.

PROC euler = (PROC(REAL,REAL)REAL f, REAL y0, a, b, h)REAL: (

   REAL y := y0,
        t := a;
   WHILE t < b DO
     printf(($g(-6,3)": "g(-7,3)l$, t, y));
     y +:= h * f(t, y);
     t +:= h
   OD;
   printf($"done"l$);
   y

);

  1. Example: Newton's cooling law #

PROC newton cooling law = (REAL time, t)REAL: (

   -0.07 * (t - 20)

);

main: (

  euler(newton cooling law, 100, 0, 100,  10)

)</lang> Ouput:

 0.000: 100.000
10.000:  44.000
20.000:  27.200
30.000:  22.160
40.000:  20.648
50.000:  20.194
60.000:  20.058
70.000:  20.017
80.000:  20.005
90.000:  20.002
done

C++

Translation of: D

<lang cpp>#include <iomanip>

  1. include <iostream>

typedef double F(double,double);

/* Approximates y(t) in y'(t)=f(t,y) with y(a)=y0 and t=a..b and the step size h.

  • /

void euler(F f, double y0, double a, double b, double h) {

   double y = y0;
   for (double t = a; t < b; t += h)
   {
       std::cout << std::fixed << std::setprecision(3) << t << " " << y << "\n";
       y += h * f(t, y);
   }
   std::cout << "done\n";

}

// Example: Newton's cooling law double newtonCoolingLaw(double, double t) {

   return -0.07 * (t - 20);

}

int main() {

   euler(newtonCoolingLaw, 100, 0, 100,  2);
   euler(newtonCoolingLaw, 100, 0, 100,  5);
   euler(newtonCoolingLaw, 100, 0, 100, 10);

}</lang> Last part of output:

...
0.000 100.000
10.000 44.000
20.000 27.200
30.000 22.160
40.000 20.648
50.000 20.194
60.000 20.058
70.000 20.017
80.000 20.005
90.000 20.002
done

Common Lisp

<lang lisp>;; 't' usually means "true" in CL, but we need 't' here for time/temperature. (defconstant true 'cl:t) (shadow 't)


Approximates y(t) in y'(t)=f(t,y) with y(a)=y0 and t=a..b and the step size h.

(defun euler (f y0 a b h)

 ;; Set the initial values and increments of the iteration variables.
 (do ((t a  (incf t h))
      (y y0 (incf y (* h (funcall f t y)))))
     ;; End the iteration when t reaches the end b of the time interval.
     ((>= t b) 'DONE)
     ;; Print t and y(t) at every step of the do loop.
     (format true "~6,3F  ~6,3F~%" t y)))


Example
Newton's cooling law, f(t,T) = -0.07*(T-20)

(defun newton-cooling (time T) (* -0.07 (- T 20)))

Generate the data for all three step sizes (2,5 and 10).

(euler #'newton-cooling 100 0 100 2) (euler #'newton-cooling 100 0 100 5) (euler #'newton-cooling 100 0 100 10)</lang>

Example output:

 0.000  100.000
10.000  44.000
20.000  27.200
30.000  22.160
40.000  20.648
50.000  20.194
60.000  20.058
70.000  20.017
80.000  20.005
90.000  20.002
DONE

D

<lang d>import std.stdio, std.range;

/** Approximates y(t) in y'(t)=f(t,y) with y(a)=y0 and t=a..b and the step size h.

  • /

void euler(F)(F f, double y0, double a, double b, double h) {

   double y = y0;
   foreach (t; iota(a, b, h)) {
       writefln("%.3f  %.3f", t, y);
       y += h * f(t, y);
   }
   writeln("done");

}

/// Example: Newton's cooling law auto newtonCoolingLaw(double time, double t) {

   return -0.07 * (t - 20);

}

void main() {

   euler(&newtonCoolingLaw, 100, 0, 100,  2);
   euler(&newtonCoolingLaw, 100, 0, 100,  5);
   euler(&newtonCoolingLaw, 100, 0, 100, 10);

}</lang> Last part of the output:

...
0.000  100.000
10.000  44.000
20.000  27.200
30.000  22.160
40.000  20.648
50.000  20.194
60.000  20.058
70.000  20.017
80.000  20.005
90.000  20.002
done

Go

<lang go>package main

import (

   "fmt"
   "math"

)

// fdy is a type for function f used in Euler's method. type fdy func(float64, float64) float64

// eulerStep computes a single new value using Euler's method. // Note that step size h is a parameter, so a variable step size // could be used. func eulerStep(f fdy, x, y, h float64) float64 {

   return y + h*f(x, y)

}

// Definition of cooling rate. Note that this has general utility and // is not specific to use in Euler's method.

// newCoolingRate returns a function that computes cooling rate // for a given cooling rate constant k. func newCoolingRate(k float64) func(float64) float64 {

   return func(deltaTemp float64) float64 {
       return -k * deltaTemp
   }

}

// newTempFunc returns a function that computes the analytical solution // of cooling rate integrated over time. func newTempFunc(k, ambientTemp, initialTemp float64) func(float64) float64 {

   return func(time float64) float64 {
       return ambientTemp + (initialTemp-ambientTemp)*math.Exp(-k*time)
   }

}

// newCoolingRateDy returns a function of the kind needed for Euler's method. // That is, a function representing dy(x, y(x)). // // Parameters to newCoolingRateDy are cooling constant k and ambient // temperature. func newCoolingRateDy(k, ambientTemp float64) fdy {

   crf := newCoolingRate(k)
   // note that result is dependent only on the object temperature.
   // there are no additional dependencies on time, so the x parameter
   // provided by eulerStep is unused.
   return func(_, objectTemp float64) float64 {
       return crf(objectTemp - ambientTemp)
   }

}

func main() {

   k := .07
   tempRoom := 20.
   tempObject := 100.
   fcr := newCoolingRateDy(k, tempRoom)
   analytic := newTempFunc(k, tempRoom, tempObject)
   for _, deltaTime := range []float64{2, 5, 10} {
       fmt.Printf("Step size = %.1f\n", deltaTime)
       fmt.Println(" Time Euler's Analytic")
       temp := tempObject
       for time := 0.; time <= 100; time += deltaTime {
           fmt.Printf("%5.1f %7.3f %7.3f\n", time, temp, analytic(time))
           temp = eulerStep(fcr, time, temp, deltaTime)
       }
       fmt.Println()
   }

}</lang> Output, truncated:

...
 85.0  20.053  20.208
 90.0  20.034  20.147
 95.0  20.022  20.104
100.0  20.014  20.073

Step size = 10.0
 Time Euler's Analytic
  0.0 100.000 100.000
 10.0  44.000  59.727
 20.0  27.200  39.728
 30.0  22.160  29.797
 40.0  20.648  24.865
 50.0  20.194  22.416
 60.0  20.058  21.200
 70.0  20.017  20.596
 80.0  20.005  20.296
 90.0  20.002  20.147
100.0  20.000  20.073

Haskell

<lang Haskell>import Text.Printf

type F = Double -> Double -> Double

euler :: F -> Double -> Double -> Double -> Double -> IO () euler f y0 a b h = do printf "%6.3f %6.3f\n" a y0 if a < b then euler f (y0 + (f a y0) * h) (a + h) b h else putStrLn "DONE"

newtonCooling :: F newtonCooling _ t = -0.07 * (t - 20)

main = euler newtonCooling 100 0 100 10 </lang> Output:

 0.000 100.000
10.000 44.000
20.000 27.200
30.000 22.160
40.000 20.648
50.000 20.194
60.000 20.058
70.000 20.017
80.000 20.005
90.000 20.002
100.000 20.000
DONE

Icon and Unicon

Translation of: Common Lisp

This solution works in both Icon and Unicon. It takes advantage of the proc procedure, which converts a string naming a procedure into a call to that procedure.

<lang Icon> invocable "newton_cooling" # needed to use the 'proc' procedure

procedure euler (f, y0, a, b, h)

 t := a
 y := y0
 until (t >= b) do {
   write (right(t, 4) || " " || left(y, 7))
   t +:= h
   y +:= h * (proc(f) (t, y)) # 'proc' applies procedure named in f to (t, y)
 }
 write ("DONE")

end

procedure newton_cooling (time, T)

 return -0.07 * (T - 20)

end

procedure main ()

 # generate data for all three step sizes [2, 5, 10]
 every (step_size := ![2,5,10]) do 
   euler ("newton_cooling", 100, 0, 100,  step_size)

end </lang>

Sample output:

   0 100    
  10 44.0   
  20 27.2   
  30 22.16  
  40 20.648 
  50 20.1944
  60 20.0583
  70 20.0174
  80 20.0052
  90 20.0015
DONE

J

Solution: <lang j>NB.*euler a Approximates y(t) in y'(t)=f(t,y) with y(a)=y0 and t=a..b and step size h. euler=: adverb define

'Y a b h'=. 4{. y
t=. a
while. b >: tincr=. {:t + h do.
  Y=. Y, (+ h * u) {:Y
  t=. t, tincr
end.
t,.Y  

)

ncl=: _0.07 * -&20 NB. Newton's Cooling Law </lang> Example: <lang j> ncl euler 100 0 100 2 ... NB. output redacted for brevity

  ncl euler 100 0 100 5

... NB. output redacted for brevity

  ncl euler 100 0 100 10
 0     100
10      44
20    27.2
30   22.16
40  20.648
50 20.1944
60 20.0583
70 20.0175
80 20.0052
90 20.0016

100 20.0005</lang>

Java

<lang java> public class Euler {

 private static void euler (Callable f, double y0, int a, int b, int h) {
   int t = a;
   double y = y0;
   while (t < b) {
     System.out.println ("" + t + " " + y);
     t += h;
     y += h * f.compute (t, y);
   }
   System.out.println ("DONE");
 }
 public static void main (String[] args) {
   Callable cooling = new Cooling ();
   int[] steps = {2, 5, 10};
   for (int stepSize : steps) {
     System.out.println ("Step size: " + stepSize);
     euler (cooling, 100.0, 0, 100, stepSize);
   }
 }

}

// interface used so we can plug in alternative functions to Euler interface Callable {

 public double compute (int time, double t);

}

// class to implement the newton cooling equation class Cooling implements Callable {

 public double compute (int time, double t) {
   return -0.07 * (t - 20);
 }

} </lang>

Output for step = 10;

Step size: 10
0 100.0
10 43.99999999999999
20 27.199999999999996
30 22.159999999999997
40 20.648
50 20.194399999999998
60 20.05832
70 20.017496
80 20.0052488
90 20.00157464
DONE

Lua

<lang lua>T0 = 100 TR = 20 k = 0.07 delta_t = { 2, 5, 10 } n = 100

NewtonCooling = function( t ) return -k * ( t - TR ) end


function Euler( f, y0, n, h )

   local y = y0
   for x = 0, n, h do

print( "", x, y )

	y = y + h * f( y )
   end

end


for i = 1, #delta_t do

   print( "delta_t = ", delta_t[i] )
   Euler( NewtonCooling, T0, n, delta_t[i] )

end </lang>

Perl 6

<lang perl6>sub euler ( &f, $y0, $a, $b, $h ) {

   my $y = $y0;
   my @t_y;
   for $a, * + $h ... * > $b -> $t {
       @t_y[$t] = $y;
       $y += $h * f( $t, $y );
   }
   return @t_y;

}

my $COOLING_RATE = 0.07; my $AMBIENT_TEMP = 20; my $INITIAL_TEMP = 100; my $INITIAL_TIME = 0; my $FINAL_TIME = 100;

sub f ( $time, $temp ) {

   return -$COOLING_RATE * ( $temp - $AMBIENT_TEMP );

}

my @e; @e[$_] = euler( &f, $INITIAL_TEMP, $INITIAL_TIME, $FINAL_TIME, $_ ) for 2, 5, 10;

say 'Time Analytic Step2 Step5 Step10 Err2 Err5 Err10';

for $INITIAL_TIME, * + 10 ... * >= $FINAL_TIME -> $t {

   my $exact = $AMBIENT_TEMP + ($INITIAL_TEMP - $AMBIENT_TEMP)
                             * (-$COOLING_RATE * $t).exp;
   my $err = sub { @^a.map: { 100* abs( $_ - $exact ) / $exact } };
   my ( $a, $b, $c ) = map { @e[$_][$t] }, 2, 5, 10;
   say $t.fmt('%4d '), ( $exact, $a, $b, $c )».fmt(' %7.3f'),
                          $err.([$a, $b, $c])».fmt(' %7.3f%%');

}</lang>

Output:

Time Analytic   Step2   Step5  Step10     Err2     Err5    Err10
   0  100.000 100.000 100.000 100.000   0.000%   0.000%   0.000%
  10   59.727  57.634  53.800  44.000   3.504%   9.923%  26.331%
  20   39.728  37.704  34.281  27.200   5.094%  13.711%  31.534%
  30   29.797  28.328  26.034  22.160   4.927%  12.629%  25.629%
  40   24.865  23.918  22.549  20.648   3.808%   9.313%  16.959%
  50   22.416  21.843  21.077  20.194   2.555%   5.972%   9.910%
  60   21.200  20.867  20.455  20.058   1.569%   3.512%   5.384%
  70   20.596  20.408  20.192  20.017   0.912%   1.959%   2.808%
  80   20.296  20.192  20.081  20.005   0.512%   1.057%   1.432%
  90   20.147  20.090  20.034  20.002   0.281%   0.559%   0.721%
 100   20.073  20.042  20.014  20.000   0.152%   0.291%   0.361%

PicoLisp

<lang PicoLisp>(load "@lib/math.l")

(de euler (F Y A B H)

  (while (> B A)
     (prinl (round A) " " (round Y))
     (inc 'Y (*/ H (F A Y) 1.0))
     (inc 'A H) ) )

(de newtonCoolingLaw (A B)

  (*/ -0.07 (- B 20.) 1.0) )

(euler newtonCoolingLaw 100.0 0 100.0 2.0) (euler newtonCoolingLaw 100.0 0 100.0 5.0) (euler newtonCoolingLaw 100.0 0 100.0 10.0)</lang> Output:

...
0.000 100.000
10.000 44.000
20.000 27.200
30.000 22.160
40.000 20.648
50.000 20.194
60.000 20.058
70.000 20.018
80.000 20.005
90.000 20.002

PureBasic

<lang PureBasic>Define.d Prototype.d Func(Time, t)

Procedure.d Euler(*F.Func, y0, a, b, h)

 Protected y=y0, t=a
 While t<=b
   PrintN(RSet(StrF(t,3),7)+" "+RSet(StrF(y,3),7))
   y + h * *F(t,y)
   t + h
 Wend

EndProcedure

Procedure.d newtonCoolingLaw(Time, t)

 ProcedureReturn -0.07*(t-20)

EndProcedure


OpenConsole() Euler(@newtonCoolingLaw(), 100, 0, 100, 2) Euler(@newtonCoolingLaw(), 100, 0, 100, 5) Euler(@newtonCoolingLaw(), 100, 0, 100,10)</lang>

...
 85.000  20.053
 90.000  20.034
 95.000  20.022
100.000  20.014
  0.000 100.000
 10.000  44.000
 20.000  27.200
 30.000  22.160
 40.000  20.648
 50.000  20.194
 60.000  20.058
 70.000  20.017
 80.000  20.005
 90.000  20.002
100.000  20.000

Python

Translation of: Common Lisp

<lang python>def euler(f,y0,a,b,h): t,y = a,y0 while t < b: print "%6.3f %6.3f" % (t,y) t += h y += h * f(t,y)

def newtoncooling(time, temp): return -0.07 * (temp - 20)

euler(newtoncooling,100,0,100,10) </lang> Output:

 0.000 100.000
10.000 44.000
20.000 27.200
30.000 22.160
40.000 20.648
50.000 20.194
60.000 20.058
70.000 20.017
80.000 20.005
90.000 20.002

Ruby

Translation of: Python

<lang ruby>def euler(y0,a,b,h, &block)

 t,y = a,y0
 while t < b
   puts "%6.3f %6.3f" % [t,y]
   t += h
   y += h * block.call(t,y)
 end

end

euler(100,0,100,10) {|time, temp| -0.07 * (temp - 20) } </lang> Output:

 0.000 100.000
10.000 44.000
20.000 27.200
30.000 22.160
40.000 20.648
50.000 20.194
60.000 20.058
70.000 20.017
80.000 20.005
90.000 20.002

Scala

<lang scala> object App{

 def main(args : Array[String]) = {
   
   def cooling( step : Int ) = {
     eulerStep( (step , y) => {-0.07 * (y - 20)} , 
       100.0,0,100,step)
   }
   cooling(10)
   cooling(5)
   cooling(2)
 }
 def eulerStep( func : (Int,Double) => Double,y0 : Double,
   begin : Int, end : Int , step : Int) = {
   
   println("Step size: %s".format(step))
   
   var current : Int = begin
   var y : Double = y0
   while( current <= end){
     println( "%d %.5f".format(current,y))
     current += step
     y += step * func(current,y)
   }
   
   println("DONE")
 }

} </lang>

Output for step = 10;

Step size: 10
0 100.00000
10 44.00000
20 27.20000
30 22.16000
40 20.64800
50 20.19440
60 20.05832
70 20.01750
80 20.00525
90 20.00157
DONE


Tcl

Translation of: C++

<lang tcl>proc euler {f y0 a b h} {

   puts "computing $f over \[$a..$b\], step $h"
   set y [expr {double($y0)}]
   for {set t [expr {double($a)}]} {$t < $b} {set t [expr {$t + $h}]} {

puts [format "%.3f\t%.3f" $t $y] set y [expr {$y + $h * double([$f $t $y])}]

   }
   puts "done"

}</lang> Demonstration with the Newton Cooling Law: <lang tcl>proc newtonCoolingLaw {time temp} {

   expr {-0.07 * ($temp - 20)}

}

euler newtonCoolingLaw 100 0 100 2 euler newtonCoolingLaw 100 0 100 5 euler newtonCoolingLaw 100 0 100 10</lang> End of output:

...
computing newtonCoolingLaw over [0..100], step 10
0.000	100.000
10.000	44.000
20.000	27.200
30.000	22.160
40.000	20.648
50.000	20.194
60.000	20.058
70.000	20.017
80.000	20.005
90.000	20.002
done