Stability Analysis

A system is stable if its output stays bounded for any bounded input (BIBO stability). In practice: does the output settle to a steady value, or does it oscillate and grow? Stability analysis tools — Routh-Hurwitz, root locus, Bode plots — answer this question without simulation.

Why It Matters

An unstable control loop is dangerous — oscillations grow until something saturates, breaks, or crashes (literally, for a quadcopter). Before deploying any controller, you need to verify stability and know how much margin you have. Phase margin tells you “how close to instability are we?”

Pole-Based Stability

A linear system is stable iff all poles of the closed-loop transfer function have negative real parts (lie in the left half of the s-plane).

        jω
        ↑
        │  ✗ unstable    ✓ stable (underdamped)
        │  (growing osc)  (decaying osc)
  ──────┼──────────────────→ σ
        │  ✗ unstable    ✓ stable (underdamped)
        │
  LEFT HALF = stable    RIGHT HALF = unstable
  On jω axis = marginally stable (constant oscillation)
  • Real poles on negative real axis → exponential decay (no oscillation)
  • Complex conjugate poles with negative real part → decaying oscillation
  • Poles further left → faster decay

Routh-Hurwitz Criterion

Determines if any poles are in the right half plane without computing them. Build a table from the characteristic polynomial coefficients.

For s³ + 3s² + 3s + 1 = 0:

s³ │  1    3
s² │  3    1
s¹ │ (3·3-1·1)/3 = 8/3    0
s⁰ │  1

Rule: if all entries in the first column are positive, all poles are in the left half plane → stable. Any sign change in the first column = one unstable pole.

Bode Plot Analysis

Plot magnitude and phase of the open-loop transfer function G(jω)H(jω) vs frequency:

import numpy as np
import matplotlib.pyplot as plt
 
w = np.logspace(-2, 3, 1000)
s = 1j * w
 
# G(s) = 10 / ((s + 1)(s + 10))
G = 10 / ((s + 1) * (s + 10))
 
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 6))
ax1.semilogx(w, 20 * np.log10(np.abs(G)))
ax1.set_ylabel('Magnitude (dB)')
ax1.grid(True, which='both')
 
ax2.semilogx(w, np.angle(G, deg=True))
ax2.set_ylabel('Phase (deg)')
ax2.set_xlabel('Frequency (rad/s)')
ax2.grid(True, which='both')
plt.tight_layout(); plt.show()

Stability Margins

MarginDefinitionRule of Thumb
Gain marginHow much gain increase beforeG(jω)
Phase marginHow much phase lag before phase = -180° at the gain-crossover frequency (whereG
Magnitude (dB)
     │
  0dB├──────────────╱──── gain crossover frequency (ωgc)
     │            ╱        ↓ phase margin read here
     │
Phase (deg)
     │
-180°├────────────────╱── phase crossover frequency (ωpc)
     │              ╱      ↑ gain margin read here

Phase margin ≈ damping: 45° ≈ ζ = 0.42 (good), 60° ≈ ζ = 0.58 (better), 30° ≈ ζ = 0.3 (oscillatory but stable).

Root Locus

Plots how closed-loop poles move as a gain parameter K varies from 0 to ∞. Useful for tuning controller gain.

As K increases:
  × poles start at open-loop pole locations (K=0)
  × poles move along locus paths
  × poles may cross into right half plane → instability at critical K
  ○ poles end at zero locations or at infinity (K→∞)

Root locus tells you the maximum gain before instability. In Python:

from scipy import signal
import control  # pip install control
 
# Open-loop: G(s) = 1 / (s(s+1)(s+2))
sys = control.tf([1], [1, 3, 2, 0])
control.root_locus(sys)
plt.show()

Stability in Discrete Systems

For digital control (z-domain), stability requires all poles inside the unit circle |z| < 1 (instead of left half plane).

z-plane:
  Continuous s-plane left half ↔ inside unit circle
  jω axis ↔ unit circle boundary
  Right half plane ↔ outside unit circle

Mapping: z = e^(sT) where T is the sampling period. A pole at s = -a maps to z = e^(-aT). As long as a > 0, |z| < 1.

Nyquist Criterion (Brief)

For systems where Bode analysis is ambiguous (open-loop unstable, non-minimum phase), Nyquist plots the full frequency response as a curve in the complex plane. The number of clockwise encirclements of the point (-1, 0) determines closed-loop stability. More advanced than Bode, but handles all linear cases.