I'm trying to solve a system of coupled first-order ODEs:
where Tf for this example is considered constant and Q(t) is given. A plot of Q(t) is shown below. The data file used to create the time vs Q plot is available at here.
My Python code for solving this system for the given Q(t) (designated as qheat
) is:
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import solve_ivp
# Data
time, qheat = np.loadtxt('timeq.txt', unpack=True)
# Calculate Temperatures
def tc_dt(t, tc, ts, q):
rc = 1.94
cc = 62.7
return ((ts - tc) / (rc * cc)) + q / cc
def ts_dt(t, tc, ts):
rc = 1.94
ru = 3.08
cs = 4.5
tf = 298.15
return ((tf - ts) / (ru * cs)) - ((ts - tc) / (rc * cs))
def func(t, y):
idx = np.abs(time - t).argmin()
q = qheat[idx]
tcdt = tc_dt(t, y[0], y[1], q)
tsdt = ts_dt(t, y[0], y[1])
return tcdt, tsdt
t0 = time[0]
tf = time[-1]
sol = solve_ivp(func, (t0, tf), (298.15, 298.15), t_eval=time)
# Plot
fig, ax = plt.subplots()
ax.plot(sol.t, sol.y[0], label='tc')
ax.plot(sol.t, sol.y[1], label='ts')
ax.set_xlabel('Time [s]')
ax.set_ylabel('Temperature [K]')
ax.legend(loc='best')
plt.show()
This produces the plot shown below but unfortunately several oscillations occur in the results. Is there a better method to solve this coupled system of ODEs?
I finally got a reasonable solution for the system of ODEs by providing the Jacobian matrix to the solver. See below for my working solution.
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import solve_ivp
from scipy.interpolate import interp1d
# Data
time, qheat = np.loadtxt('timeq.txt', unpack=True)
# Calculate Temperatures
interp_qheat = interp1d(time, qheat)
def tc_dt(t, tc, ts, q):
"""
dTc/dt = (Ts-Tc)/(Rc*Cc) + Q/Cc
"""
rc = 1.94
cc = 62.7
return ((ts - tc) / (rc * cc)) + q / cc
def ts_dt(t, tc, ts):
"""
dTs/dt = (Tf-Ts)/(Ru*Cs) - (Ts-Tc)/(Rc*Cs)
"""
rc = 1.94
ru = 3.08
cs = 4.5
tf = 298.15
return ((tf - ts) / (ru * cs)) - ((ts - tc) / (rc * cs))
def jacobian(t, y):
"""
Given the following system of ODEs
dTc/dt = (Ts-Tc)/(Rc*Cc) + Q/Cc
dTs/dt = (Tf-Ts)/(Ru*Cs) - (Ts-Tc)/(Rc*Cs)
determine the Jacobian matrix of the right-hand side as
Jacobian matrix = [df1/dTc, df2/dTc]
[df1/dTs, df2/dTs]
where
f1 = (Ts-Tc)/(Rc*Cc) + Q/Cc
f2 = (Tf-Ts)/(Ru*Cs) - (Ts-Tc)/(Rc*Cs)
"""
cc = 62.7
cs = 4.5
rc = 1.94
ru = 3.08
jc = np.array([
[-1 / (cc * rc), 1 / (cs * rc)],
[1 / (cc * rc), -1 / (cs * ru) - 1 / (cs * rc)]
])
return jc
def func(t, y):
"""
Right-hand side of the system of ODEs.
"""
q = interp_qheat(t)
tcdt = tc_dt(t, y[0], y[1], q)
tsdt = ts_dt(t, y[0], y[1])
return tcdt, tsdt
t0 = time[0]
tf = time[-1]
sol = solve_ivp(func, (t0, tf), (298.15, 298.15), method='BDF', t_eval=time, jac=jacobian)
# Plot
fig, ax = plt.subplots(tight_layout=True)
ax.plot(sol.t, sol.y[0], label='tc')
ax.plot(sol.t, sol.y[1], label='ts')
ax.set_xlabel('Time [s]')
ax.set_ylabel('Temperature [K]')
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), frameon=False)
plt.show()
And the generated plot is shown below.
The only advantage to interpolating Q was to speed up the execution of the code by removing the argmin()
in the main function. Otherwise, interpolating Q did not improve the results.