Array input signal for ODEINT solver in SciPy (nonlinear system)

73 Views Asked by At

I want to pass an array input signal to ordinary differential equations solver 'odeint' instead of defining a function. Much like the linear solver lsim :

tout, yout, xout = lsim(sys, U=input_sig(time), T=time)

Which is much more convenient than defining a input function as it allows to generate various input signal and operate on them (windowing or whatever)

In the following is a modified example from the odeint scipy documentation where i define input_sig as a function of t

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint


def input_sig(t):
    return np.sin(2 * np.pi * 50 * t)

def pend(y, t, b, c):
    theta, omega = y
    dydt = [omega, -b*omega -c*np.sin(theta) -input_sig(t)]
    return dydt


b = 0.25
c = 5.0
y0 = [0, 0.0]
t = np.linspace(0, 10, 101)
sol = odeint(pend, y0, t, args=(b, c))

# what i would like to do is
# sol = odeint(pend, y0, t, input_sig(t), args=(b, c))
# where input_sig(t) is an array

plt.plot(t, sol, label=['theta(t)','omega(t)'])
plt.legend(loc='best')
plt.xlabel('t')
plt.grid()
plt.show()

Any ideas ? i don't mind using other solvers than odeint, i don't know if it's possible... Thank You!

1

There are 1 best solutions below

1
On BEST ANSWER

As mentioned in Lutz Lehman comment a fixed-step solver did the job perfectly (I tried Euler forward and Runge Kutta):

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint


#%% solvers
def rungekutta4(func, y0, t, input_sig, args=()):
    n = len(t)
    y = np.zeros((n, len(y0)))
    y[0] = y0
    for i in range(n - 1):
        h = t[i+1] - t[i]
        k1 = func(y[i], t[i],input_sig[i], *args)
        k2 = func(y[i] + k1 * h / 2., t[i] + h / 2.,input_sig[i], *args)
        k3 = func(y[i] + k2 * h / 2., t[i] + h / 2.,input_sig[i], *args)
        k4 = func(y[i] + k3 * h, t[i] + h,input_sig[i], *args)
        y[i+1] = y[i] + (h / 6.) * (k1 + 2*k2 + 2*k3 + k4)
    return y

def euler_forward(func, y0, t,input_sig, args=()):
    n = len(t)
    y = np.zeros((n, len(y0)))
    y[0] = y0
    for i in range(n - 1):
        y[i+1] = y[i] + (t[i+1] - t[i]) * np.array(func(y[i], t[i],input_sig[i], *args))
    return y

#%% carac pendulum
g = 9.81        # acceleration of gravity
l = 1           # pendulum rope length
k = 0.8         # air resistance coefficient
m = 1           # mass of the pendulum

b = k/m
c = g/l
d = m *l**2

#%% function
def pend(y, t,input_sig, b, c, d):
    theta, omega = y
    if isinstance(input_sig,(list,np.ndarray,float)):
        dydt = [omega, -b * omega - c * np.sin(theta) - input_sig / d]
    else:
        dydt = [omega, -b * omega - c * np.sin(theta) - input_sig(t) / d]
    return np.array(dydt)


#%% input signal carac
f=20
T = 1          # total duration of the simulation
fs = 20000
dt = 1 / fs      # dt
n_pts= fs * T
t = np.arange(0,n_pts)*dt
def input_sig(t):
    return np.sin(2 * np.pi * f * t)
input_sig_array = input_sig(t)

#%% solving
y0 = [0,0]
sol_odeint = odeint(pend, y0, t, args=(input_sig,b, c, d))
sol_e = euler_forward(pend, y0, t,input_sig_array, args=(b,c,d))
sol_r = rungekutta4(pend, y0, t,input_sig_array, args=(b,c,d))

plt.subplot(211)
plt.plot(t, sol_odeint, label=['theta(t)', 'omega(t)'])
plt.plot(t, sol_r,'--',label= ['theta(t)', 'omega(t)'])
plt.plot(t, sol_e,':',label= ['theta(t)', 'omega(t)'])
plt.legend(loc='best')
plt.xlim([0,1/f])
plt.grid()
plt.subplot(212)
plt.plot(t, sol_odeint, label=['theta(t)', 'omega(t)'])
plt.plot(t, sol_r,'--',label= ['theta(t)', 'omega(t)'])
plt.plot(t, sol_e,':',label= ['theta(t)', 'omega(t)'])
plt.legend(loc='best')
plt.xlabel('t, s')
plt.grid()
plt.show()