PID Tuning with Relay Feedback (Åström–Hägglund)
April 28, 2026 · Updated April 29, 2026
Introduction
The relay feedback method (Åström & Hägglund) lets you measure a process’s critical gain and ultimate period without pushing the plant into sustained oscillations with a proportional controller. Instead, you close the loop with an on–off relay and record the limit-cycle amplitude that results.
Compared to PID tuning from an open-loop step response, relay feedback is:
- Safer: the amplitude of oscillation is bounded by relay hysteresis.
- Online: can be performed while the process is under closed-loop control.
- Model-free: does not require fitting , , first.
Prerequisites
- Familiarity with FOLPD models and basic PID tuning concepts (see Getting Started)
python-control≥ 0.10.1 andscipy- The
control_utils.pytoolkit used in Getting Started
Theory
Relay in the Feedback Loop
Replace the PID controller with a relay that switches between and based on the sign of error:
For a low-pass process plus time-delay, the relay induces a stable limit cycle. By describing-function analysis, the first harmonic of the relay output has amplitude:
At the ultimate frequency , the loop gain is , so the plant magnitude at satisfies:
Ziegler–Nichols Frequency-Domain Rules
Once are known, the ZN tuning rules are (PID form):
| Parameter | Formula |
|---|---|
Which translate to standard gains:
Experiment Setup
The Process
We use a standard FOLPD plant:
Parameters: , , .
Relay Controller
In python-control≥0.10, a static nonlinear controller is built with ct.nlsys:
import numpy as np
import control as ct
def relay_output(t, xc, uc, params):
e = uc[0]
d = params['b'] # relay amplitude
return [np.sign(e) * d]
relay = ct.nlsys(
lambda t, xc, uc, params: [0], # no internal states
relay_output,
inputs=('e'), outputs=('u'),
states=0, params={'b': 5.0},
name='relay', dt=0
)
# Summing junction: e = r - y
sumblk = ct.summing_junction(inputs=['r', '-y'], output='e')
# Close the loop
T_ry = ct.interconnect(
[G, relay, sumblk],
inplist='r', outlist='y'
)
Simulation
A unit step reference () forces the relay to switch continuously once the transients decay:
T_sim, dt = 200, 0.01
timepts = np.arange(0, T_sim, dt)
r = np.ones(len(timepts))
resp_y = ct.input_output_response(T_ry, timepts, r)
Measuring the Limit Cycle
After discarding the initial transient, identify peaks and valleys of the steady-state oscillation:
from scipy.signal import find_peaks
idx_start = int(120 / dt) # after t = 120 s
y_ss = np.asarray(resp_y.outputs[idx_start:]).flatten()
mean_y = float(np.mean(y_ss))
peaks, _ = find_peaks(y_ss, height=mean_y, distance=int(2.0 / dt))
valleys, _ = find_peaks(-y_ss, height=-mean_y, distance=int(2.0 / dt))
# Oscillation amplitude
a = (np.mean(y_ss[peaks]) - np.mean(y_ss[valleys])) / 2.0
# Ultimate period
periods = [
resp_y.time[peaks[i+1]] - resp_y.time[peaks[i]]
for i in range(len(peaks) - 1)
]
Tu = float(np.mean(periods))
For this example, we measure:
| Quantity | Value | Units |
|---|---|---|
| Relay amplitude | 5 | — |
| Limit-cycle amplitude | 8.60 | process units |
| Ultimate period | 3.82 | s |
| Ultimate gain | 0.74 | — |
Verification: ct.margin(G) reports a gain margin of at (). The relay estimate is within of the true linear-system value, which is typical for describing-function approximations.
Experimental Waveform

The limit cycle establishes within ~20 s after the step; the steady-state amplitude and period are read from this waveform. The first 120 s are discarded when computing the average to avoid Pade startup transients.
Computing PID Gains
Plug the measured into the ZN frequency-domain formulas:
Ku = 4 * d / (np.pi * a) # ≈ 0.74
Tu = 3.82
Kp = 0.60 * Ku # ≈ 0.444
Ki = 1.2 * Ku / Tu # ≈ 0.233
Kd = 0.075 * Ku * Tu # ≈ 0.212
Comparison: Three ZN Frequency-Domain Variants
The Wikipedia Ziegler–Nichols method page lists several PID variants based on the same data. Below we focus on the three most commonly used forms: Classic, Some overshoot, and No overshoot.
Two Equivalent PID Representations
PID controllers can be written using either time-constant form or standard gain form . Both are equivalent—choose whichever matches your control-system implementation.
Time-constant form (ISA/PID standard):
Standard gain form:
Conversion formulas:
ZN Formulas (Time-Constant Form)
| Variant | |||
|---|---|---|---|
| Classic PID | |||
| Some overshoot | |||
| No overshoot |
Key insight: all three variants share the same integral time and integral gain . The “some overshoot” and “no overshoot” variants reduce (less proportional action) and increase (more derivative time constant) relative to Classic — but because , the resulting standard derivative gain is higher for “some overshoot” but paradoxically lower for “no overshoot”.
Standard Gains for This Plant
From and s:
| Method | (s) | (s) | |||
|---|---|---|---|---|---|
| Classic | 0.444 | 1.91 | 0.478 | 0.233 | 0.212 |
| Some overshoot | 0.247 | 1.91 | 1.273 | 0.129 | 0.314 |
| No overshoot | 0.148 | 1.91 | 1.273 | 0.078 | 0.189 |
Notice: “Some overshoot” achieves the strongest derivative action () because its moderately low is multiplied by the maximum . Conversely, “No overshoot” drops so far that ends up weaker than Classic (). This explains the counterintuitive simulation result.
Response Comparison

Closed-Loop Performance
All controllers use the same PIDController implementation (anti-windup, derivative-on-measurement, derivative filtering) in discrete time:
| Method | Overshoot | Peak Time | Settling (±2%) | IAE | Notes |
|---|---|---|---|---|---|
| Classic | ≈ 60% | 3.0 s | ~11 s | 3.2 | Quarter-wave decay; fast but rings |
| Some overshoot | ≈ 42% | 6.7 s | ~24 s | 4.8 | Reduced aggressiveness vs. Classic |
| No overshoot | ≈ 44% | 7.9 s | ~30 s | 6.3 | Most conservative rule; still nonzero OS |
Understanding the names: “Some overshoot” and “No overshoot” are historical names for these ZN modifications — they indicate the rules are tuned to reduce overshoot compared to the Classic quarter-wave target, but they do not guarantee low overshoot on every process. On plants with significant time delay (like our example with ), these rules still produce substantial overshoot because the fixed structure of ZN rules does not explicitly compensate for phase lag.
For reference: the getting-started tutorial shows similar results — ZN-tuned loops typically show 60–90% overshoot on FOLPD processes with . Ziegler and Nichols designed these rules for “quarter-wave decay” (aggressive, responsive control), not tight damping. The “some overshoot” and “no overshoot” variants lower and raise relative to Classic, but the effect is modest; true low-overshoot tuning requires model-based methods (IMC) or iterative refinement.
Observations:
- Classic gives the fastest rise but heaviest overshoot (~60%). This is the traditional quarter-wave-decay target — aggressive by design.
- Some overshoot reduces proportional gain and raises derivative time compared to Classic, producing a slower but somewhat smoother response. The overshoot drops from ~60% to ~42%.
- No overshoot uses the lowest proportional gain and a moderate derivative time. Despite the name, overshoot remains ~44% because the derivative gain () is actually lower than “Some overshoot” (). This is counterintuitive: one might expect “No overshoot” to have the strongest derivative damping, but the Wikipedia-quoted rule does not. A purely delay-dominant process benefits more from high derivative action; reducing without sufficient can leave the oscillatory mode underdamped.
| Rule | What it actually does | Result on this plant |
| Classic | Maximum , minimum | Fastest, ~60% OS |
| Some overshoot | Moderate , maximum | Moderate, ~42% OS |
| No overshoot | Minimum , moderate | Slowest, ~44% OS |
Key finding: on this plant (large process gain with ), “Some overshoot” actually achieves the lowest peak overshoot, not “No overshoot”. The name “No overshoot” reflects the intended design philosophy (even more conservative than “Some overshoot”), but it does not translate to guaranteed low overshoot on all processes.
When to use each variant
| Classic | Some overshoot | No overshoot | |
|---|---|---|---|
| Intended use | Quarter-wave decay | Moderately damped | Most conservative |
| Rise time | Fastest | Moderate | Slowest |
| When to use | Tolerant processes, fast tracking | Balanced speed / smoothness | Most conservative starting point |
| Actual OS on this plant | ~60% | ~42% | ~44% |
| Tip | Expect aggressive tuning | Good first refinement | Lower gains, but not guaranteed low OS |
Practical Tips
| Tip | Why It Matters |
|---|---|
| Discard ≥ 5 time constants before measuring and | Non-stationary startup from Pade approximation corrupts early cycles |
| Choose so that measurement noise | Low SNR makes peak detection unreliable |
| Add small hysteresis to the relay | Prevents chattering from zero-crossing noise |
| For integrating processes, bias the relay | Prevents output drift while still exciting the critical mode |
Extensions
- Modified relay methods: a relay with hysteresis () lets you target phase margins directly rather than just the ultimate point. See Åström & Hägglund (2006) §4.2.
- On-line re-tuning: run the relay experiment briefly, compute gains, then switch back to PID automatically — no manual intervention.
- Model-based refinement: if you also want a FOLPD model, fit and back to and then apply IMC rules for explicit robustness–performance trade-off.
Reproducibility
The complete plotting script used to generate the figures in this tutorial:
- Download
generate_relay_feedback_plots.py(Python script, ~400 lines)
Dependencies: python-control ≥ 0.10.1, scipy, matplotlib, and the local control_utils.py module. Run it directly to reproduce all figures. The script is also available in the python-control GitHub repository.
References
- Åström, K.J., & Hägglund, T. (2006). Advanced PID Control. ISA.
Try It Yourself
- Experiment with the relay amplitude (larger → bigger oscillation → easier to measure, but more disturbance)
- Try all three ZN variants on your own process and compare the response smoothness
- Return to the Getting Started with PID Tuning tutorial for open-loop step-response methods