Implicit Euler
Routine Name : Testing Implicit Euler
Author : Kyle Hovey
Language : C++
Description/Purpose :
This code tests the Implicit Euler method.
Input :
The constants and base cases for the test cases.
Output :
This code prints a comparison of the approximated solution (via Implicit Euler) and the exact solution (computed analytically).
Usage/Example :
#include <iostream>
#include <array>
#include "../../euler/src/euler/euler.h"
#include "../../testCases/src/testCases/testCases.h"
int main () {
// Delta t used in Implicit Euler 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 |||||||||" << std :: endl ;
for ( const auto lambda : lambdas ) {
const auto approx = Euler :: genImplicit < 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 |||||||||" << std :: endl ;
for ( const auto Po : Pos ) {
const auto approx = Euler :: genImplicit < 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 |||||||||
=============
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.90044e+09
exact ( 0.4 ) = 2.35385e+18
approx ( 0.4 ) = inf
exact ( 0.6 ) = 1.14201e+27
approx ( 0.6 ) = inf
exact ( 0.8 ) = 5.54062e+35
approx ( 0.8 ) = inf
exact ( 1 ) = 2.68812e+44
approx ( 1 ) = inf
|||||||||| Logistic DiffEQ |||||||||
=============
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 highlight C ++ % }
Implementation/Code:
A Newton method approach is used to solve implicitly for the next value in the iteration as a function of the previous value. Just as in the Explicit method, this method returns a memoized version of the function that closes over a call cache so that the solution is dynamically created.
#include <functional>
#include <cmath>
#include <vector>
namespace Euler {
template < typename T >
using endo = std :: function < T ( const T & ) > ;
template < typename T >
using driver = std :: function < T ( const T & , const T & ) > ;
/**
* Use newton's method to find a local zero of a function given a guess
* @param f Function to find the zero of
* @param guess Initial guess for the location of the zero
* @param tolerance Absolute error away from zero tolerated
* @param dt Time step to use for secant approximation of derivative
* @param maxIterations Maximum iterations before exiting
* @return An input x such that |f(x)| < tolerance
*/
template < typename T >
T newton (
const endo < T >& f ,
const T & guess ,
const T & tolerance = 0.001 ,
const T & dt = 0.001 ,
const unsigned int & maxIter = 100
) {
auto out = guess ;
for ( auto i = 0u ; std :: abs ( f ( out )) > tolerance && i < maxIter ; ++ i ) {
const auto df = ( f ( out + dt ) - f ( out )) / dt ;
out = out - f ( out ) / df ;
}
return out ;
}
/**
* Solve a very basic differential equation using an Explicit Euler 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 > genImplicitEulerSolution (
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 ];
//cache.push_back(lastVal + dt * f(t, lastVal));
cache . push_back ( newton < double > (
[ & ]( const double & nextU ) -> double {
return ( nextU - lastVal ) / dt - f ( t , nextU );
},
lastVal
));
}
}
return cache [ step ];
};
}
};