3. Laplace and Z Transforms
The Laplace and Z transforms turn hard differential/difference-equation problems into simpler algebraic ones, in continuous and discrete time respectively — the essential toolkit before building state-space models.
Lecture videos
Section titled “Lecture videos”Slides
Section titled “Slides”PowerPoint handouts: Laplace Transform (light) · Inverse Laplace Transform (dark) · Inverse Laplace Transform (light) · Z Transform (dark) · Z Transform (light)
Interactive example
Section titled “Interactive example”Basic Laplace transforms
Section titled “Basic Laplace transforms”Using sympy’s symbolic engine, this verifies the basic Laplace-transform pairs derived earlier in the chapter: the exponential e^(-at), a specific exponential e^(-2t), the ramp 2t, and the unit impulse — each matching the closed-form results worked out by hand.
import sympyt, s = sympy.symbols('t, s')a = sympy.symbols('a', real=True, positive=True)
f = sympy.exp(-a * t)F = sympy.laplace_transform(f, t, s, noconds=True)print("L{exp(-a*t)} =", F)
g = sympy.exp(-2 * t)G = sympy.laplace_transform(g, t, s, noconds=True)print("L{exp(-2*t)} =", G)
h = 2 * tH = sympy.laplace_transform(h, t, s, noconds=True)print("L{2*t} =", H)
d = sympy.DiracDelta(t)D = sympy.laplace_transform(d, t, s, noconds=True)print("L{delta(t)} =", D)Basic Laplace transforms (mcimp/codes/laplaceZtransforms/simplelaplace.py)
Click "Run" to execute.
Time-shift property
Section titled “Time-shift property”A time delay in the time domain corresponds to multiplication by e^(-sτ) in the s-domain. This confirms that a unit impulse delayed by 4 seconds, δ(t−4), transforms to e^(−4s), exactly as the shift property predicts.
import sympyt, s = sympy.symbols('t, s')
d = sympy.DiracDelta(t - 4)D = sympy.laplace_transform(d, t, s, noconds=True)print("L{delta(t - 4)} =", D)Time-shift property (mcimp/codes/laplaceZtransforms/laplaceTimeDelay.py)
Click "Run" to execute.
Partial fraction expansion
Section titled “Partial fraction expansion”Breaking F(s) = 32 / [s(s+4)(s+8)] into simple first-order terms lets you read off the inverse Laplace transform term by term from a lookup table, rather than evaluating the inversion integral directly. sympy.apart() confirms the by-hand residues K1=1, K2=-2, K3=1 from the text.
import sympys = sympy.symbols('s')
G = 32 / s / (s + 4) / (s + 8)print("F(s) =", G)print("Partial fraction expansion:", sympy.apart(G))Partial fraction expansion (mcimp/codes/laplaceZtransforms/partial_fraction_expansion.py)
Click "Run" to execute.
Transfer function analysis
Section titled “Transfer function analysis”Builds a second-order transfer function G(s) = (s+2)/(s²+2s+3), reports its poles and zeros, then plots its step response, impulse response, and its response to two different forced inputs (a constant and a sine wave) side by side — illustrating that a transfer function’s poles set the response’s natural modes regardless of which input drives it.
import control as ctimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport numpy as npimport io, base64
num = [1, 2] # Numerator coefficientsden = [1, 2, 3] # Denominator coefficients
sys_tf = ct.tf(num, den)print(sys_tf)
poles = ct.poles(sys_tf)zeros = ct.zeros(sys_tf)print("System Poles =", poles, "\nSystem Zeros =", zeros)
T, yout = ct.step_response(sys_tf)T, yout_i = ct.impulse_response(sys_tf)u1 = np.full(len(T), 2)u2 = np.sin(T)_, yout_u1 = ct.forced_response(sys_tf, T, u1)_, yout_u2 = ct.forced_response(sys_tf, T, u2)
fig, axs = plt.subplots(1, 3, figsize=(12, 3.5))axs[0].plot(T, yout); axs[0].set_title("Step response"); axs[0].grid(True)axs[1].plot(T, yout_i); axs[1].set_title("Impulse response"); axs[1].grid(True)axs[2].plot(T, yout_u1, label="Input 1 (const = 2)")axs[2].plot(T, yout_u2, label="Input 2 (sin)")axs[2].set_title("Forced response"); axs[2].legend(); axs[2].grid(True)for ax in axs: ax.set_xlabel("Time (sec)")plt.tight_layout()
buf = io.BytesIO()plt.savefig(buf, format="png", dpi=100)plot_b64 = base64.b64encode(buf.getvalue()).decode()Transfer function analysis (mcimp/codes/laplaceZtransforms/transfer_fun.py)
Click "Run" to execute.
DC gain
Section titled “DC gain”The DC gain is the steady-state ratio of output to a constant input, equal to G(s) evaluated at s=0 (or, via the Final Value Theorem, lim_(s→0) sG(s)/s). For G(s)=(2s+3)/(4s²+3s+1), that’s b₀/a₀ = 3/1 = 3.
import control as ct
s = ct.tf('s')G = (2 * s + 3) / (4 * s**2 + 3 * s + 1)print("DC gain of G(s) =", ct.dcgain(G))DC gain (mcimp/codes/laplaceZtransforms/CTDCgain.py)
Click "Run" to execute.
DC gain caution: unstable systems
Section titled “DC gain caution: unstable systems”The DC-gain formula is only meaningful for a stable system. H(s)=3/(s−2) has a pole at s=2 (right-half plane), so even though the formula still returns a number (−1.5), the actual step response never converges to it — it diverges, as the growing output samples below make clear.
import control as ct
H = ct.tf([0, 3], [1, -2])print("DC gain of H(s) =", ct.dcgain(H))
T, yout = ct.step_response(H)print("\nH(s) is unstable, so the step response never actually settles at the DC gain:")print(" first 5 samples:", [round(float(v), 3) for v in yout[:5]])print(" last 5 samples: ", [round(float(v), 3) for v in yout[-5:]])print(f" magnitude keeps growing -- by t={T[-1]:.2f}s it reaches", round(float(max(abs(yout))), 1))DC gain caution: unstable systems (mcimp/codes/laplaceZtransforms/CTDCgain_caution.py)
Click "Run" to execute.
Z transform of a geometric sequence
Section titled “Z transform of a geometric sequence”The Z transform of the geometric sequence (a^k) is derived directly from its definition, ∑ a^k z^-k, a geometric series converging to 1/(1−az⁻¹) = z/(z−a) whenever |a/z| < 1. Setting a=1 recovers the Z transform of the discrete-time unit step.
import sympyz, k, a = sympy.symbols('z k a', positive=True)
# Z{a^k} straight from the definition, sum_{k=0}^inf a^k z^-k = 1/(1 - a/z),# valid for |a/z| < 1 -- the same geometric-series convergence condition# used in the text. (Computed via sympy directly rather than lcapy, so this# demo only needs pyodide-native packages.)gamma = a / zF = sympy.simplify(1 / (1 - gamma))print("Z{a^k} =", F)
# Special case a = 1: the discrete-time unit step sequenceprint("Z{1(k)} = Z{a^k}|a=1 =", F.subs(a, 1))Z transform of a geometric sequence (mcimp/codes/laplaceZtransforms/simpleZtransform.py)
Click "Run" to execute.
Z-domain differentiation property
Section titled “Z-domain differentiation property”Multiplying a sequence by k in the time domain corresponds to differentiating its Z transform: Z(k f(k)) = −z dF(z)/dz. Applied to f(k)=a^k and checked at the book’s worked value a=0.5, this reproduces the time-scaled geometric sequence’s transform.
import sympyz, k, a = sympy.symbols('z k a', positive=True)
# Base transform: Z{a^k} = z/(z - a)gamma = a / zF = sympy.simplify(1 / (1 - gamma))print("Z{a^k} =", F)
# Differentiation property: Z{k f(k)} = -z dF(z)/dzF1 = sympy.simplify(-z * sympy.diff(F, z))print("Z{k a^k} =", F1)
print("\nWith a = 0.5 (the book's worked example):")print(" Z{0.5^k} =", F.subs(a, sympy.Rational(1, 2)))print(" Z{k*0.5^k} =", F1.subs(a, sympy.Rational(1, 2)))Z-domain differentiation property (mcimp/codes/laplaceZtransforms/timescaledGeometricSeqZ.py)
Click "Run" to execute.
Discrete-time transfer function
Section titled “Discrete-time transfer function”The discrete-time counterpart of a transfer function needs an explicit sampling time Ts alongside its numerator/denominator coefficients. This builds a sampled second-order system, reports its poles/zeros, and plots its step response as a staircase — reflecting that a discrete-time system’s output only changes at each sampling instant.
import control as ctimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport numpy as npimport io, base64
Ts = 0.1 # sampling timenum = [0.09952, -0.08144]den = [1, -1.792, 0.8187]
sys_tf = ct.tf(num, den, Ts)print(sys_tf)
poles = ct.poles(sys_tf)zeros = ct.zeros(sys_tf)print("System Poles =", poles, "\nSystem Zeros =", zeros)
T, yout = ct.step_response(sys_tf)
fig, ax = plt.subplots(figsize=(6, 4))# a stairs plot shows the discrete-time nature of the response; shifting by# one sample makes the initial one-step delay visible, matching the bookax.step(T, np.append(0, yout[0:-1]))ax.grid(True)ax.set_xlabel("Time (sec)")ax.set_ylabel("y")plt.tight_layout()
buf = io.BytesIO()plt.savefig(buf, format="png", dpi=100)plot_b64 = base64.b64encode(buf.getvalue()).decode()Discrete-time transfer function (mcimp/codes/laplaceZtransforms/transfer_fun_dt.py)
Click "Run" to execute.