Runge Kutta Methods
Routine Name : Runge Kutta Methods
Author : Kyle Hovey
Language : C++
Description/Purpose :
This code tests the Runge Kutta Methods of second and fourth order as defined in the book.
Input :
The constants and base cases for the test cases.
Output :
This code prints a comparison of the approximated solution (via Runge Kutta) and the exact solution (computed analytically).
Usage/Example :
#include <iostream>
#include <array>
#include "rungeKutta/rungeKutta.h"
#include "../../testCases/src/testCases/testCases.h"
int main () {
// Delta t used in Runge Kutta method
const auto dt = 0.00001 ;
// Variables for evaluation
const std :: tuple < double , double > domain = { 0.0 , 1.0 };
const unsigned int steps = 5 ;
// Test cases for u' = λu
const auto alpha = 10.0 ;
const std :: array < double , 3 > lambdas = { 1 , - 1 , 100 };
// Test cases for Logistic Equation
const auto gamma = 0.1 ;
const auto beta = 0.0001 ;
const std :: array < double , 2 > Pos = { 25 , 40000 };
std :: cout << "|||||||||| Lambda DiffEQ (second order) |||||||||" ;
std :: cout << std :: endl ;
for ( const auto lambda : lambdas ) {
const auto approx = RungeKutta :: genOrderTwoSolution < double > (
[ = ]( const double & t , const double & u ) -> double {
( void ) t ;
return lambda * u ;
},
dt ,
alpha
);
const auto exact = TestCases :: genLambdaSolution < double > ( lambda , alpha );
std :: cout << std :: endl << "=============" << std :: endl ;
std :: cout << "Solving with lambda = " << lambda << std :: endl ;
printComparison < double > ( exact , approx , domain , steps );
}
std :: cout << "|||||||||| Lambda DiffEQ (fourth order) |||||||||" ;
std :: cout << std :: endl ;
for ( const auto lambda : lambdas ) {
const auto approx = RungeKutta :: genOrderFourSolution < double > (
[ = ]( const double & t , const double & u ) -> double {
( void ) t ;
return lambda * u ;
},
dt ,
alpha
);
const auto exact = TestCases :: genLambdaSolution < double > ( lambda , alpha );
std :: cout << std :: endl << "=============" << std :: endl ;
std :: cout << "Solving with lambda = " << lambda << std :: endl ;
printComparison < double > ( exact , approx , domain , steps );
}
std :: cout << std :: endl ;
std :: cout << "|||||||||| Logistic DiffEQ (second order) |||||||||" ;
std :: cout << std :: endl ;
for ( const auto Po : Pos ) {
const auto approx = RungeKutta :: genOrderTwoSolution < double > (
[ = ]( const double & t , const double & P ) -> double {
( void ) t ;
return gamma * P - beta * P * P ;
},
dt ,
Po
);
const auto exact = TestCases :: genLogisticSolution ( beta , gamma , Po );
std :: cout << std :: endl << "=============" << std :: endl ;
std :: cout << "Solving with Po = " << Po << std :: endl ;
printComparison < double > ( exact , approx , domain , steps );
}
std :: cout << std :: endl ;
std :: cout << "|||||||||| Logistic DiffEQ (fourth order) |||||||||" ;
std :: cout << std :: endl ;
for ( const auto Po : Pos ) {
const auto approx = RungeKutta :: genOrderFourSolution < double > (
[ = ]( const double & t , const double & P ) -> double {
( void ) t ;
return gamma * P - beta * P * P ;
},
dt ,
Po
);
const auto exact = TestCases :: genLogisticSolution ( beta , gamma , Po );
std :: cout << std :: endl << "=============" << std :: endl ;
std :: cout << "Solving with Po = " << Po << std :: endl ;
printComparison < double > ( exact , approx , domain , steps );
}
return EXIT_SUCCESS ;
}
Output:
` |||||||||| Lambda DiffEQ ( second order ) |||||||||
=============
Solving with lambda = 1
exact ( 0 ) = 10
approx ( 0 ) = 10
exact ( 0.2 ) = 12.214
approx ( 0.2 ) = 12.214
exact ( 0.4 ) = 14.9182
approx ( 0.4 ) = 14.9182
exact ( 0.6 ) = 18.2212
approx ( 0.6 ) = 18.2212
exact ( 0.8 ) = 22.2554
approx ( 0.8 ) = 22.2554
exact ( 1 ) = 27.1828
approx ( 1 ) = 27.1825
=============
Solving with lambda = - 1
exact ( 0 ) = 10
approx ( 0 ) = 10
exact ( 0.2 ) = 8.18731
approx ( 0.2 ) = 8.18731
exact ( 0.4 ) = 6.7032
approx ( 0.4 ) = 6.7032
exact ( 0.6 ) = 5.48812
approx ( 0.6 ) = 5.48812
exact ( 0.8 ) = 4.49329
approx ( 0.8 ) = 4.49329
exact ( 1 ) = 3.67879
approx ( 1 ) = 3.67883
=============
Solving with lambda = 100
exact ( 0 ) = 10
approx ( 0 ) = 10
exact ( 0.2 ) = 4.85165e+09
approx ( 0.2 ) = 4.85164e+09
exact ( 0.4 ) = 2.35385e+18
approx ( 0.4 ) = 2.35384e+18
exact ( 0.6 ) = 1.14201e+27
approx ( 0.6 ) = 1.142e+27
exact ( 0.8 ) = 5.54062e+35
approx ( 0.8 ) = 5.54055e+35
exact ( 1 ) = 2.68812e+44
approx ( 1 ) = 2.68539e+44
|||||||||| Lambda DiffEQ ( fourth order ) |||||||||
=============
Solving with lambda = 1
exact ( 0 ) = 10
approx ( 0 ) = 10
exact ( 0.2 ) = 12.214
approx ( 0.2 ) = 12.214
exact ( 0.4 ) = 14.9182
approx ( 0.4 ) = 14.9183
exact ( 0.6 ) = 18.2212
approx ( 0.6 ) = 18.2212
exact ( 0.8 ) = 22.2554
approx ( 0.8 ) = 22.2555
exact ( 1 ) = 27.1828
approx ( 1 ) = 27.1827
=============
Solving with lambda = - 1
exact ( 0 ) = 10
approx ( 0 ) = 10
exact ( 0.2 ) = 8.18731
approx ( 0.2 ) = 8.18732
exact ( 0.4 ) = 6.7032
approx ( 0.4 ) = 6.70321
exact ( 0.6 ) = 5.48812
approx ( 0.6 ) = 5.48813
exact ( 0.8 ) = 4.49329
approx ( 0.8 ) = 4.49331
exact ( 1 ) = 3.67879
approx ( 1 ) = 3.67885
=============
Solving with lambda = 100
exact ( 0 ) = 10
approx ( 0 ) = 10
exact ( 0.2 ) = 4.85165e+09
approx ( 0.2 ) = 4.9004e+09
exact ( 0.4 ) = 2.35385e+18
approx ( 0.4 ) = 2.40139e+18
exact ( 0.6 ) = 1.14201e+27
approx ( 0.6 ) = 1.17677e+27
exact ( 0.8 ) = 5.54062e+35
approx ( 0.8 ) = 5.76666e+35
exact ( 1 ) = 2.68812e+44
approx ( 1 ) = 2.82307e+44
|||||||||| Logistic DiffEQ ( second order ) |||||||||
=============
Solving with Po = 25
exact ( 0 ) = 25
approx ( 0 ) = 25
exact ( 0.2 ) = 25.4922
approx ( 0.2 ) = 25.4922
exact ( 0.4 ) = 25.9937
approx ( 0.4 ) = 25.9937
exact ( 0.6 ) = 26.5049
approx ( 0.6 ) = 26.5049
exact ( 0.8 ) = 27.0259
approx ( 0.8 ) = 27.0259
exact ( 1 ) = 27.5568
approx ( 1 ) = 27.5568
=============
Solving with Po = 40000
exact ( 0 ) = 40000
approx ( 0 ) = 40000
exact ( 0.2 ) = 22570.2
approx ( 0.2 ) = 22570.2
exact ( 0.4 ) = 15815.2
approx ( 0.4 ) = 15815.2
exact ( 0.6 ) = 12228
approx ( 0.6 ) = 12228
exact ( 0.8 ) = 10003.8
approx ( 0.8 ) = 10003.8
exact ( 1 ) = 8490.15
approx ( 1 ) = 8490.22
|||||||||| Logistic DiffEQ ( fourth order ) |||||||||
=============
Solving with Po = 25
exact ( 0 ) = 25
approx ( 0 ) = 25
exact ( 0.2 ) = 25.4922
approx ( 0.2 ) = 25.4922
exact ( 0.4 ) = 25.9937
approx ( 0.4 ) = 25.9937
exact ( 0.6 ) = 26.5049
approx ( 0.6 ) = 26.5049
exact ( 0.8 ) = 27.0259
approx ( 0.8 ) = 27.0259
exact ( 1 ) = 27.5568
approx ( 1 ) = 27.5568
=============
Solving with Po = 40000
exact ( 0 ) = 40000
approx ( 0 ) = 40000
exact ( 0.2 ) = 22570.2
approx ( 0.2 ) = 22570.4
exact ( 0.4 ) = 15815.2
approx ( 0.4 ) = 15815.4
exact ( 0.6 ) = 12228
approx ( 0.6 ) = 12228.2
exact ( 0.8 ) = 10003.8
approx ( 0.8 ) = 10004
exact ( 1 ) = 8490.15
approx ( 1 ) = 8490.32
Implementation/Code:
The Runge Kutta method uses trial steps along the way between each iteration to cancel out lower-order errors. This results in a method that typically converges must faster than other techniques.
#include <functional>
#include <cmath>
#include <vector>
namespace RungeKutta {
template < typename T >
using endo = std :: function < T ( const T & ) > ;
template < typename T >
using driver = std :: function < T ( const T & , const T & ) > ;
/**
* Solve a very basic differential equation using a Runge Kutta technique.
* u' = f(t, u)
* @param {f} RHS of differential equation (utilizes uPrime)
* @param {dt} Differential of time between samples
* (output function will round to these)
* @param {uInit} Initial value of u(0)
* @return Function that gives you the output at time t
*/
template < typename T >
endo < T > genOrderTwoSolution (
const driver < T >& f ,
const T & dt ,
const T & uInit
) {
// Memoization cache
std :: vector < T > cache = { };
cache . push_back ( uInit );
return [ = ]( const T & t ) mutable -> T {
const auto step = std :: floor ( t / dt );
if ( step >= cache . size ()) {
const auto size = cache . size ();
for ( auto i = size ; i <= step ; ++ i ) {
const auto lastVal = cache [ i - 1 ];
const auto kOne = dt * f ( t , lastVal );
const auto kTwo = dt * f ( t + dt / 2 , lastVal + kOne / 2 );
cache . push_back ( lastVal + kTwo );
}
}
return cache [ step ];
};
}
/**
* Solve a very basic differential equation using a Runge Kutta technique.
* u' = f(t, u)
* @param {f} RHS of differential equation (utilizes uPrime)
* @param {dt} Differential of time between samples
* (output function will round to these)
* @param {uInit} Initial value of u(0)
* @return Function that gives you the output at time t
*/
template < typename T >
endo < T > genOrderFourSolution (
const driver < T >& f ,
const T & dt ,
const T & uInit
) {
// Memoization cache
std :: vector < T > cache = { };
cache . push_back ( uInit );
return [ = ]( const T & t ) mutable -> T {
const auto step = std :: floor ( t / dt );
if ( step >= cache . size ()) {
const auto size = cache . size ();
for ( auto i = size ; i <= step ; ++ i ) {
const auto lastVal = cache [ i - 1 ];
const auto kOne = dt * f ( t , lastVal );
const auto kTwo = dt * f ( t + dt / 2 , lastVal + kOne / 2 );
const auto kThree = dt * f ( t + dt / 2 , lastVal + kTwo / 2 );
const auto kFour = dt * f ( t + dt , lastVal + kThree );
cache . push_back ( lastVal + kFour );
}
}
return cache [ step ];
};
}
};