2. First order systems

[1]:
import sympy
import matplotlib.pyplot as plt
import numpy
sympy.init_printing()
%matplotlib inline
[2]:
t, K, tau = sympy.symbols('t, K, tau',real=True, positive=True)
s = sympy.Symbol('s')
[3]:
u = sympy.Heaviside(t)
[4]:
def L(f):
    return sympy.laplace_transform(f, t, s, noconds=True)
def invL(F):
    return sympy.inverse_laplace_transform(F, s, t)
[5]:
U = L(u)
U
[5]:
$$\frac{1}{s}$$

All first order linear differential equations with constant coefficients can be rewritten in the following form:

\[\frac{\mathrm{d}y}{\mathrm{d}t} = ay(t) + bu(t)\]

Where \(y\) is the output and \(u\) is the input or forcing function.

If we Laplace transform this, we end up with

\begin{align} \mathcal{L}\left\{\frac{\mathrm{d}y}{\mathrm{d}t}\right\} &= \mathcal{L}\{ay(t) + bu(t)\} \\ sy(s) &= ay(s) + bu(s) \\ y(s) &= \frac{b}{s - a}u(s) \\ \end{align}

By convention, we usually rewrite the above in the following form, for reasons which will become apparent soon:

[6]:
G = K/(tau*s + 1)
G
[6]:
$$\frac{K}{s \tau + 1}$$

The inverse laplace of a transfer function is its impulse response

[7]:
impulseresponse = invL(G)
impulseresponse
[7]:
$$\frac{K}{\tau} e^{- \frac{t}{\tau}}$$

If \(u(t)\) is the unit step function, \(U(s)=\frac{1}{s}\) and we can obtain the step response as follows:

[8]:
u = 1/s
stepresponse = invL(G*u)
[9]:
stepresponse
[9]:
$$K - K e^{- \frac{t}{\tau}}$$

Ramp response:

[10]:
u = 1/s**2
rampresponse = invL(G*u)
rampresponse
[10]:
$$K t - K \tau + K \tau e^{- \frac{t}{\tau}}$$
[11]:
from ipywidgets import interact
[12]:
evalfimpulse = sympy.lambdify((K, tau, t), impulseresponse, 'numpy')
evalfstep = sympy.lambdify((K, tau, t), stepresponse, 'numpy')
evalframp = sympy.lambdify((K, tau, t), rampresponse, 'numpy')
[13]:
ts = numpy.linspace(0, 10)

def firstorder(tau_in, K_in):
    plt.figure(figsize=(12, 6))
    ax_impulse = plt.subplot2grid((2, 2), (0, 0))
    ax_step = plt.subplot2grid((2, 2), (1, 0))
    ax_complex = plt.subplot2grid((2, 2), (0, 1), rowspan=2)

    ax_impulse.plot(ts, evalfimpulse(K_in, tau_in, ts))
    ax_impulse.set_title('Impulse response')
    ax_impulse.set_ylim(0, 10)

    tau_height = 1 - numpy.exp(-1)
    ax_step.set_title('Step response')
    ax_step.plot(ts, evalfstep(K_in, tau_in, ts))
    ax_step.axhline(K_in)
    ax_step.plot([0, tau_in, tau_in], [K_in*tau_height]*2 + [0], alpha=0.4)
    ax_step.text(0, K_in, '$K=${}'.format(K_in))
    ax_step.text(0, K_in*tau_height, '{:.3}$K$'.format(tau_height))
    ax_step.text(tau_in, 0, r'$\tau={:.3}$'.format(tau_in))
    ax_step.set_ylim(0, 10)

    ax_complex.set_title('Poles plot')
    ax_complex.scatter(-1/tau_in, 0, marker='x', s=30)
    ax_complex.axhline(0, color='black')
    ax_complex.axvline(0, color='black')
    ax_complex.axis([-10, 10, -10, 10])
[14]:
firstorder(1., 10.)
../../_images/1_Dynamics_4_First_and_second_order_system_dynamics_First_order_systems_19_0.png
[15]:
interact(firstorder, tau_in=(0.1, 10.), K_in=(0.1, 10.));

Exploration of the above interaction allows us to see the following:

  • \(K\) scales the response in the \(y\) direction

  • \(\tau\) scales the response in the \(t\) direction

  • The response of the system is always \(0.63K\) when \(t=\tau\)

We get the “magic number” 0.63 by substituting \(t=\tau\) into the response:

[16]:
sympy.N((stepresponse.subs(t, tau)/K).simplify())
[16]:
$$0.632120558828558$$