Compensator Design: Lead, Lag & Loop Shaping

Designing controllers in the frequency domain. Learn gain and phase margins as design specs, the lead compensator (adds phase, speeds up), the lag compensator (raises low-frequency gain, reduces error), lead-lag, loop-shaping rules, bandwidth vs crossover, and a complete lead design computed in Python — the classical techniques behind servo and aerospace control.

When a plain gain cannot give both accuracy and stability, you add dynamics to the controller: a compensator. Lead networks add phase near crossover to improve damping and speed; lag networks add low-frequency gain to reduce steady-state error without disturbing stability.

PID is itself a lead-lag compensator (PI ≈ lag, PD ≈ lead). Understanding loop shaping explains why PID works and how to go beyond it.

An analogy

A lead compensator is like steering a car by looking further ahead: acting on where things are going (phase advance) prevents weaving. A lag compensator is like patient persistence: it slowly removes the remaining error without rushing.

Why compensate?

Raising the gain improves accuracy and speed but reduces phase margin until the loop oscillates. A compensator reshapes the open-loop frequency response L(jω) = C(jω)G(jω) so that you get high gain where you need accuracy and enough phase where the loop crosses 0 dB.

Lead compensation

A lead network has its zero below its pole, adding positive phase (up to ~60° per stage) between them. Placed at the crossover, it increases phase margin (less overshoot) and usually crossover frequency (faster). Downside: it raises high-frequency gain, amplifying sensor noise and saturating actuators.

Lag compensation

A lag network adds gain at low frequency (by β) while leaving the crossover region nearly unchanged if its zero is placed a decade below ω_c. It reduces steady-state error by β without hurting stability, at the cost of a slow 'tail' in the response. PI control is the limit β → ∞ (a pure integrator).

Loop shaping and robustness

Loop shaping specifies |L(jω)|: large at low frequency (disturbance rejection, tracking), crossing 0 dB with about −20 dB/decade slope (good phase margin), and rolling off fast at high frequency (noise, unmodelled dynamics). The sensitivity function S = 1/(1 + L) shows disturbance amplification; its peak M_s ≤ 2 (6 dB) is a common robustness target. H∞ and μ-synthesis formalise these ideas for multivariable systems.

Formulas

Worked example: Lead ratio

Given: Need 50° of phase from one stage

Find: α and gain at ω_m.

Answer: α = 0.132, +8.8 dB at ω_m

Worked example: Max phase of α = 0.1

Given: α = 0.1

Find: φ_max.

Answer: 54.9°

Worked example: Lag for accuracy

Given: Loop has K_v = 2 and good PM at ω_c = 1 rad/s; need K_v = 20

Find: Lag network.

Answer: C(s) = 10(10s + 1)/(100s + 1)

Designing a lead compensator numerically

import math, cmath
# Lead compensator for G(s) = K / (s (s + 1)), goal: Kv = 10, phase margin >= 45 deg
K = 10.0                                   # from Kv = lim s*G = K = 10
def G(w):
    s = 1j * w
    return K / (s * (s + 1))

def margins(L):
    lo, hi = 1e-3, 1e3                       # find gain crossover |L| = 1 by bisection (log)
    for _ in range(200):
        mid = math.sqrt(lo * hi)
        if abs(L(mid)) > 1: lo = mid
        else: hi = mid
    wc = math.sqrt(lo * hi)
    pm = 180 + math.degrees(cmath.phase(L(wc)))
    return wc, pm

wc, pm = margins(G)
print(f"uncompensated: wc = {wc:.2f} rad/s, PM = {pm:.1f} deg")

phi = math.radians(45 - pm + 12)           # extra phase needed + 12 deg safety
alpha = (1 - math.sin(phi)) / (1 + math.sin(phi))
# put the max phase at the new crossover, where |G| = sqrt(alpha)
lo, hi = 1e-3, 1e3
for _ in range(200):
    mid = math.sqrt(lo * hi)
    if abs(G(mid)) > math.sqrt(alpha): lo = mid
    else: hi = mid
wm = math.sqrt(lo * hi)
T = 1 / (wm * math.sqrt(alpha))
C = lambda w: (1 + 1j * w * T) / (1 + 1j * w * alpha * T)
wc2, pm2 = margins(lambda w: C(w) * G(w))
print(f"lead: alpha = {alpha:.3f}, T = {T:.3f} s  ->  C(s) = ({T:.3f}s + 1)/({alpha*T:.4f}s + 1)")
print(f"compensated: wc = {wc2:.2f} rad/s, PM = {pm2:.1f} deg")
# uncompensated: wc = 3.08 rad/s, PM = 18.0 deg
# lead: alpha = 0.227, T = 0.464 s  ->  C(s) = (0.464s + 1)/(0.1053s + 1)
# compensated: wc = 4.53 rad/s, PM = 51.5 deg

The textbook procedure (set gain for accuracy, measure PM, add the missing phase plus a safety margin, place ω_m where the compensated crossover will be) in about 30 lines. The result: phase margin from 18° (≈ 60% overshoot) to 51.5° (≈ 20%) and a faster loop.

Common mistakes

Applications

Key takeaways

Going further