0

I'm aiming at making a 2D compressible Euler equations numerical model, and to do so I need to implement a so-called "semi-implicit fourth order runge-kutta scheme" in order to integrate an equation of the form du/dt = f(u) + g(u); where g is a stiff term whereas f is not. Therefore, I've been following the scheme described by the following article https://www.researchgate.net/publication/228655062_Implicit-explicit_Runge-Kutta_method_for_combustion_simulation.enter image description here

After quite some time spent on trying to understand how this works, I'm currently trying to test my newly implemented scheme with simple use cases. Following the example given on the scipy.integrate.solve_ivp documentation page, the Lokta-Volterra case, I'm trying to compare the results from my "SIRK45" scheme to the "RK45" and "Radau" schemes of solve_ivp (since my SIRK45 scheme requires two function, I take the other one as a null function).

While if the time integration step is small enough (~t_end/200) the explicit part of my scheme seems to behave fine. However for the same time step if we use the implicit part to do the integration, it looks like information is lost over time somehow.

enter image description here

The way I approached the implicit integration was to treat f(y + h*sum) as a constant, and rely on scipy.optimize.root function to solve iteratively equations of the type x - cst1 - g(y + dt*sum + dt*cst2*x) = 0. I believe there might be an issue in the way I treat the implicit part, but I can't figure out where or what.


Below is the code for the SIRK45 scheme I implemented (I can also add the test files codes should someone find them relevant).

import numpy as np
from scipy.optimize import root
def SIRK45(F,G,y0,h0,t0,tf,args,method='krylov'):
    #https://www.researchgate.net/publication/228655062_Implicit-explicit_Runge-Kutta_method_for_combustion_simulation
    ### Coefficients
    A = np.array([
        [1/4, 0, 0, 0, 0],
        [0.34114705729739, 1/4, 0, 0, 0],
        [0.80458720789763, -0.07095262154540, 1/4, 0, 0],
        [0.52932607329103, 1.15137638494253, -0.80248263237803, 1/4, 0],
        [0.11933093090075, 0.55125531344927, -0.1216872844994, 0.20110104014943, 1/4],
    ])
    B = np.array([0.11933093090075, 0.55125531344927, -0.1216872844994, 0.20110104014943, 1/4])
    E = np.array([
        [0, 0, 0, 0, 0],
        [0.39098372452428, 0, 0, 0, 0],
        [1.09436646160460, 0.33181504274704, 0, 0, 0],
        [0.14631668003312, 0.69488738277516, 0.46893381306619, 0, 0],
        [-1.33389883143642, 2.90509214801204, -1.06511748457024, 0.27210900509137, 0],
    ])
    
    ### Initialize
    y = y0
    t = t0
    h = h0
    
    ### Predictors
    print('Starting')
    f_k1 = F(y,*args)
    g_k1 = lambda x: x - f_k1 - G(y + h*(A[0,0]*x),*args)
    k1sol = root(g_k1, y, method=method)
    k1 = k1sol.x
    
    #
    print('Checkpoint 1/5')
    f_k2 = F(y + h*(E[1,0]*k1),*args1)
    g_k2 = lambda x: x - f_k2 - G(y + h*(A[1,0]*k1 + A[1,1]*x),*args)
    k2sol = root(g_k2, y, method=method)
    k2 = k2sol.x
    #
    print('Checkpoint 2/5')
    f_k3 = F(y + h*(E[2,0]*k1 + E[2,1]*k2),*args)
    g_k3 = lambda x: x - f_k3 - G(y + h*(A[2,0]*k1 + A[2,1]*k2 + A[2,2]*x),*args)
    k3sol = root(g_k3, y, method=method)
    k3 = k3sol.x
    #
    print('Checkpoint 3/5')
    f_k4 = F(y + h*(E[3,0]*k1 + E[3,1]*k2 + E[3,2]*k3),*args)
    g_k4 = lambda x: x - f_k4 - G(y + h*(A[3,0]*k1 + A[3,1]*k2 + A[3,2]*k3 + A[3,3]*x),*args)
    k4sol = root(g_k4, y,method=method)
    k4 = k4sol.x
    #
    print('Checkpoint 4/5')
    f_k5 = F(y + h*(E[4,0]*k1 + E[4,1]*k2 + E[4,2]*k3 + E[4,3]*k4),*args)            
    g_k5 = lambda x: x - f_k5 - G(y + h*(A[4,0]*k1 + A[4,1]*k2 + A[4,2]*k3 + A[4,3]*k4 + A[4,4]*x),*args)
    k5sol = root(g_k5, y, method=method)
    k5 = k5sol.x
    
    print('Finished, good luck pilot')
    ### Update the state vector
    new_y = y + h*(B[0]*k1 + B[1]*k2 + B[2]*k3 + B[3]*k4 + B[4]*k5)
    t = t+h0
    return t,new_y

EDIT - below the code to retrieve each SIRK45 plots :

def lotkavolterraCP(z, a, b, c, d):
    x, y = z
    return [a*x - b*x*y, -c*y + d*x*y]

### Useful
def zero_func(z, a, b, c, d):
    return np.array([0,0]).ravel()

### Numerical parameters
t = 0
tf = 15
nbr = 300
dt = 15/nbr
args=(1.5, 1, 3, 1)

y0 = np.array([10,5])
q1 = []
q2 = []

while t<tf:
    ### Call the semi-explicit scheme for integration
    # t,y = SIRK45(lotkavolterraCP,zero_func,y0.ravel(),dt,t,tf,args) #Explicit integration
    t,y = SIRK45(zero_func,lotkavolterraCP,y0.ravel(),dt,t,tf,args) #Implicit integration
    
    ### Update the parameters
    y0 = y
    q = y.reshape([2])
    q1.append(q[0])
    q2.append(q[1])

### Plotting
t = np.linspace(0, 15, nbr)
import matplotlib.pyplot as plt
plt.figure()
plt.plot(t, q1)
plt.plot(t, q2)
plt.xlabel('t')
plt.legend(['x', 'y'], shadow=True)
plt.title('Lotka-Volterra System - SIRK45')
plt.show()
Slyphlamen
  • 26
  • 6
  • 1
    Please do provide your initial conditions that gave you the plot above! Debugging numerical simulations is difficult enough as it is :) – Dominik Stańczak Jul 22 '22 at 15:19
  • Sorry, should have thought of it, I use the exact same conditions as the ones on solve_ivp documentation page https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html --> my initial state vector is of y0 = [10,5]; as for the arguments args=(1.5, 1, 3, 1) // I will add the code to retrieve my two plots as an edit. Nevertheless the choice of initial conditions is also something I have trouble understanding in my case (since root requires them), but that might be another topic. – Slyphlamen Jul 22 '22 at 15:39

0 Answers0