As the initial value problem was given and very well specified, the analytical solution can be easily calculated, where $y(t) = (1+2.5t)^{1.4}$ solution of the PVI problem, by the way, $\rho = y$ and $z = t$. With the initial value problem defined, and with the analytical solution in hand, we can solve the ODE numerically as an integrator scipy.integrate.odeint
and plot the errors committed $\epsilon_\text{Absolute}(n) = |\rho_\text{Analytic}-\rho_\text{Numeric}|$ and $\epsilon_\text{Relative}(n) = |\frac{\rho_\text{Analytic}-\rho_\text{Numeric}}{\epsilon_\text{Analytic}}|$, where $n$ is the number of iterations.
Observation: I gave preference to odeint
, because in several examples like the one specified above, odeint
is chosen over solve_ivp
.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint, solve_ivp
# NUMERICAL SOLUTION APPLYING SCIPY'S ODEIN
# ORDINARY DIFFERENTIAL EQUATION dy/dt = f(t,y), t0 = t' and y(t0) = y0
def f(y,t, m, theta):
# rho => y
# z => t
# m = 1.4
# theta = 2.5
return m*theta*y/(1+theta*t)
ti = 0.0
tf = 50.0
N = 1000
t = np.linspace(ti,tf,N)
y0 = 1.0
sol = odeint(f, y0, t, args=(1.4,2.5), rtol=1E-8, atol=1E-8)
sol_numeric = sol[:,0]
# ANALYTICAL SOLUTION OF THE INITIAL VALUE PROBLEM
def f_solution(t):
return 1.0*(1+2.5*t)**(1.4)
sol_analytic = f_solution(t) # Observation, t = np.linspace(ti,tf,N)
# ERROR
erro_absolut = np.abs(sol_analytic-sol_numeric)
erro_relativ = np.abs((sol_analytic-sol_numeric)/sol_analytic)
quantidade_iteracoes = np.linspace(0,N,N)
# PLOTTING OF RESULTS
plt.style.use('dark_background')
plt.figure(figsize = (21,6))
plt.plot(t,sol,'b.',t,f_solution(t),'r-')
plt.title('Comparison between analytical and numerical solution (Odeint)')
plt.xlabel('time')
plt.ylabel('y(t)')
plt.grid(lw = 1.0,color = 'y',linestyle = '-')
plt.show()
plt.figure(figsize = (21,6))
plt.style.use('dark_background')
plt.subplot(1,2,1)
#plt.plot(quantidade_iteracoes,erro_absolut,color = 'red', lw = 3.0)
plt.plot(quantidade_iteracoes,erro_absolut,'r.', lw = 3.0)
plt.title('Absolute error', fontsize=18)
plt.xlabel('num of iteration', fontsize=18)
plt.ylabel(r'$\epsilon_{Absolute}$', fontsize=18)
plt.grid(lw = 0.5,color = 'y',linestyle = '-')
plt.subplot(1,2,2)
#plt.plot(quantidade_iteracoes,erro_relativ,color = 'blue', lw = 3.0)
plt.plot(quantidade_iteracoes,erro_relativ,'b.', lw = 3.0)
plt.title('Relative error', fontsize=18)
plt.xlabel('num of iteration', fontsize=18)
plt.ylabel(r'$\epsilon_{relative}$', fontsize=18)
plt.grid(lw = 0.5,color = 'y',linestyle = '-')
# https://danielmuellerkomorowska.com/2021/02/16/differential-equations-with-scipy-odeint-or-solve_ivp/#:~:text=The%20primary%20advantage%20is%20that,UPDATE%2007.02.
t_eval
out you get the discretization that is actually used internally. I suspect that it is rather sparse with the default tolerances, with nodes for $z>1$ corresponding to the extrema of the error function, either close to the extrema themselves or to the midpoints between them. $\endgroup$