# 03 — Survey of Tuning Methods This document surveys the tuning methods that `pidtune` will implement, records the exact rule formulas that the implementations must reproduce, states the applicability and limitations of each method, and maps every method onto the tuner API defined in [04 — API Specification](04_api_specification.md). All methods produce gains in **parallel form** (`PIDGains`, see `pidtune.controllers.gains`); rules that are naturally stated in standard form (Kp, Ti, Td) are converted via `PIDGains.from_standard`. --- ## 0. Notation | Symbol | Meaning | |---|---| | `K` | Process steady-state gain | | `τ` (tau) | Dominant process time constant (FOPDT) | | `θ` (theta, also `L`) | Apparent dead time / delay | | `r = θ/τ` | Dead-time ratio | | `τ_n = θ/(θ+τ)` | Normalized dead time (0…1) | | `a = Kθ/τ` | Reaction-curve "slope-intercept" parameter | | `Ku` | Ultimate gain (gain at which a P-only loop oscillates with constant amplitude) | | `Pu` | Ultimate period (period of that oscillation) | | `Kp, Ti, Td` | Standard (ISA/ideal) form gains: `u = Kp·(e + (1/Ti)∫e dt + Td·de/dt)` | | `λ` (lambda) | Desired closed-loop time constant (IMC/lambda tuning) | | `Ms` | Maximum sensitivity, `max_ω |1/(1+G(jω)C(jω))|` | Unless stated otherwise, rules assume a **stable, self-regulating FOPDT** process: ``` G(s) = K · e^(−θs) / (τs + 1) ``` --- ## 1. Ziegler–Nichols, open loop (reaction curve) **Class:** `pidtune.tuners.ziegler_nichols.ZieglerNicholsOpenLoop` **Input:** an `FOPDTModel` (typically obtained from `pidtune.identification.step_response.fit_fopdt`). **History:** Ziegler & Nichols (1942), restated in terms of FOPDT parameters. With `a = Kθ/τ`: | Controller | Kp | Ti | Td | |---|---|---|---| | P | `1/a` | — | — | | PI | `0.9/a` | `3.33·θ` | — | | PID | `1.2/a` | `2·θ` | `0.5·θ` | **Validity window (enforced, see §8):** `0.1 ≤ θ/τ ≤ 1.0`. Outside this window the tuner emits a `TuningResult.warnings` entry; below `θ/τ < 0.01` it raises, because the rule degenerates (`Kp → ∞`). **Character:** aggressive; designed for quarter-amplitude damping, which implies overshoot of roughly 50 % and poor robustness (`Ms` often > 2.5). We implement it because it is the universal baseline, and clearly document AMIGO/IMC as the recommended defaults. --- ## 2. Ziegler–Nichols, closed loop (ultimate cycle) and variants **Class:** `pidtune.tuners.ziegler_nichols.ZieglerNicholsClosedLoop` **Input:** `(Ku, Pu)` — either supplied directly, computed analytically from a process model's frequency response (phase crossover), or measured by the relay experiment (§6). Rule table, selected by the `ZNRule` enum: | Rule (`ZNRule`) | Type | Kp | Ti | Td | |---|---|---|---|---| | `CLASSIC` | P | `0.50·Ku` | — | — | | `CLASSIC` | PI | `0.45·Ku` | `Pu/1.2` | — | | `CLASSIC` | PID | `0.60·Ku` | `Pu/2` | `Pu/8` | | `SOME_OVERSHOOT` | PID | `0.33·Ku` | `Pu/2` | `Pu/3` | | `NO_OVERSHOOT` | PID | `0.20·Ku` | `Pu/2` | `Pu/3` | | `TYREUS_LUYBEN` | PI | `Ku/3.2` | `2.2·Pu` | — | | `TYREUS_LUYBEN` | PID | `Ku/2.2` | `2.2·Pu` | `Pu/6.3` | **Notes:** - Tyreus–Luyben (1992) is markedly less oscillatory and is the recommended variant when ultimate-cycle data is used. - When the input is a `ProcessModel`, `Ku`/`Pu` are obtained from the phase crossover frequency `ω_180` (where `arg G(jω) = −180°`): `Ku = 1/|G(jω_180)|`, `Pu = 2π/ω_180`. Models without a phase crossover (e.g. pure first order with no delay) raise `TuningError`. --- ## 3. Cohen–Coon **Class:** `pidtune.tuners.cohen_coon.CohenCoonTuner` **Input:** `FOPDTModel`. Designed for processes with larger dead time than ZN tolerates; derived for quarter-amplitude damping with dead-time compensation. With `r = θ/τ`: | Type | Kp | Ti | Td | |---|---|---|---| | P | `(1/K)·(1/r)·(1 + r/3)` | — | — | | PI | `(1/K)·(1/r)·(0.9 + r/12)` | `θ·(30 + 3r)/(9 + 20r)` | — | | PD | `(1/K)·(1/r)·(1.25 + r/6)` | — | `θ·(6 − 2r)/(22 + 3r)` | | PID | `(1/K)·(1/r)·(4/3 + r/4)` | `θ·(32 + 6r)/(13 + 8r)` | `4θ/(11 + 2r)` | **Validity window:** `0.1 ≤ r ≤ 4.0` (warn outside). Like ZN it targets quarter-amplitude damping and is oscillatory; documented as a legacy/comparison method. --- ## 4. AMIGO (Approximate M-constrained Integral Gain Optimization) **Class:** `pidtune.tuners.amigo.AmigoTuner` **Input:** `FOPDTModel`. Åström & Hägglund (2004); derived by fitting simple formulas to MIGO optimization results over a large process test batch, with a robustness constraint of `Ms ≈ 1.4`. **This is the library's recommended default rule** for FOPDT models. PI: ``` Kp = (1/K) · ( 0.15 + ( 0.35 − (θ·τ)/(θ+τ)² ) · (τ/θ) ) Ti = 0.35·θ + (13·θ·τ²) / (τ² + 12·θ·τ + 7·θ²) ``` PID (with derivative filter assumed, `N ≈ 10`): ``` Kp = (1/K) · ( 0.2 + 0.45·(τ/θ) ) Ti = θ · (0.4·θ + 0.8·τ) / (θ + 0.1·τ) Td = (0.5·θ·τ) / (0.3·θ + τ) ``` **Validity window:** `0.02 ≤ τ_n ≤ 0.95` (essentially all of FOPDT space); the PID rule is documented as conservative for lag-dominant processes (`τ_n < 0.2`). The tuner additionally sets recommended setpoint weighting: `b = 0` for `τ_n < 0.5`, `b = 1` otherwise — surfaced via `TuningResult.metadata["setpoint_weight_b"]`. --- ## 5. IMC / lambda tuning (including SIMC variant) **Class:** `pidtune.tuners.imc.IMCTuner` **Input:** `FOPDTModel` or `SOPDTModel`; one user-meaningful knob, `λ` (`lambda_c`), the desired closed-loop time constant. Default if not given: `λ = max(0.5·θ, 0.1·τ)` (a common moderately-aggressive compromise; recorded in `TuningResult.metadata["lambda_c"]`). **FOPDT, PI (Rivera/Morari IMC with delay neglected in Ti):** ``` Kp = τ / (K·(λ + θ)) Ti = τ ``` **FOPDT, PID (first-order Padé approximation of the delay):** ``` Kp = (τ + θ/2) / (K·(λ + θ/2)) Ti = τ + θ/2 Td = (τ·θ) / (2τ + θ) ``` **SOPDT, PID:** ``` Kp = (τ1 + τ2) / (K·(λ + θ)) Ti = τ1 + τ2 Td = (τ1·τ2) / (τ1 + τ2) ``` **SIMC variant** (`variant="simc"`, Skogestad 2003), which dramatically improves disturbance rejection for lag-dominant processes by detuning the integral time: ``` Kp = τ / (K·(λ + θ)) Ti = min(τ, 4·(λ + θ)) ``` **Guidance encoded in docs and metadata:** `λ ≈ θ` → robust (`Ms ≈ 1.6`), `λ ≈ 0.5·θ` → fast, `λ ≥ 3·θ` → very conservative. Raises `TuningError` for `λ ≤ 0`. --- ## 6. Relay autotuning (Åström–Hägglund relay feedback) **Class:** `pidtune.tuners.relay.RelayAutotuner` Unlike the rule tuners, this is an **experiment** that runs against a `pidtune.simulation.plant.Plant` (which may wrap a model *or* be a user adapter around real hardware). It excites a limit cycle with a relay nonlinearity and extracts `(Ku, Pu)` without driving the loop to true instability. **Procedure:** 1. Apply relay of amplitude `d` (with optional hysteresis `ε`) around the operating point: output switches between `u₀+d` and `u₀−d` when the measurement crosses `y₀ ∓ ε`. 2. Wait for a sustained limit cycle (cycle-to-cycle period and amplitude converged within `tolerance`, default 5 %, over `min_cycles`, default 4). 3. Measure oscillation amplitude `A` and period `Pu`. 4. Describing-function estimate of the ultimate gain: - ideal relay: `Ku = 4d / (π·A)` - relay with hysteresis: `Ku = 4d / (π·√(A² − ε²))` 5. Apply a rule to `(Ku, Pu)` — by default Tyreus–Luyben; any `ZieglerNicholsClosedLoop` rule selectable via the `rule` argument. **Outputs:** a `RelayExperimentResult` (Ku, Pu, A, d, ε, full `t/u/y` traces, number of cycles, convergence flag) embedded in `TuningResult.metadata`, plus the final gains. **Failure modes handled explicitly:** no limit cycle within `max_duration` (raises `TuningError`), amplitude below noise floor (warn; recommend larger `d`), asymmetric oscillation indicating nonlinearity (warn). Accuracy caveat documented: the describing function gives `Ku` typically within 5–20 %; the hysteresis variant is recommended in the presence of noise. --- ## 7. Optimization-based tuning **Class:** `pidtune.tuners.optimization.OptimizationTuner` Tunes by directly minimizing a closed-loop performance index computed by the simulation engine (`pidtune.simulation.loop`). The most flexible method, and the only one that handles arbitrary `TransferFunctionModel`s, constraints, and mixed objectives. **Objectives** (computed over a configurable scenario — setpoint step by default, optional load-disturbance step): | Key | Index | |---|---| | `"iae"` | `∫ |e| dt` | | `"ise"` | `∫ e² dt` | | `"itae"` | `∫ t·|e| dt` (default; punishes slow tails, tolerates fast transients) | | `"itse"` | `∫ t·e² dt` | | callable | user objective `f(SimulationResult) -> float` | **Constraints (soft, via penalty terms; all optional):** - `max_overshoot` (fraction of step size), - `max_sensitivity` (`Ms`, computed from the model's frequency response when the plant is model-backed), - `max_control_effort` (`∫ |du/dt| dt` bound), - output saturation respected automatically because the actual `PIDController` (with anti-windup) is simulated. **Algorithms** (via `scipy.optimize`): - `"nelder-mead"` (default): local, derivative-free, started from a seed — by default the AMIGO result when the model is FOPDT, otherwise IMC, otherwise a unit guess. - `"differential-evolution"`: global, for nonconvex landscapes or when no seed rule applies; bounds derived from the seed (×/÷ 50) or user-supplied. Optimization variables are `log(Kp), log(Ti), log(Td)` (log-space keeps gains positive and conditions the search). Non-convergence raises `ConvergenceError` carrying the best iterate found so far. --- ## 8. Cross-cutting conventions for all tuners 1. **Single entry point:** every tuner implements `Tuner.tune(model_or_data, controller_type=...) -> TuningResult` (see doc 04). 2. **Applicability checking:** each tuner declares `supported_models` and a validity window; out-of-window inputs produce structured warnings in `TuningResult.warnings`, impossible inputs raise `TuningError` with the offending parameter named. 3. **Reproducibility:** `TuningResult.metadata` records every intermediate quantity (e.g. `a`, `Ku`, `Pu`, `lambda_c`, optimizer trace summary) so a result can be audited and a paper formula checked by hand. 4. **No hidden unit handling:** all times share one unit (the user's); formulas are unit-consistent by construction. 5. **Recommended-default policy:** the top-level convenience function `pidtune.tune(model)` dispatches to AMIGO for FOPDT, IMC for SOPDT, and `OptimizationTuner` for arbitrary transfer functions, and says so in the returned metadata. ## 9. References - J. G. Ziegler, N. B. Nichols, "Optimum Settings for Automatic Controllers," *Trans. ASME*, 64, 1942. - G. H. Cohen, G. A. Coon, "Theoretical Consideration of Retarded Control," *Trans. ASME*, 75, 1953. - K. J. Åström, T. Hägglund, "Revisiting the Ziegler–Nichols step response method for PID control," *J. Process Control*, 14, 2004 (AMIGO). - K. J. Åström, T. Hägglund, *Advanced PID Control*, ISA, 2006 (relay autotuning, setpoint weighting). - D. E. Rivera, M. Morari, S. Skogestad, "Internal Model Control: PID Controller Design," *Ind. Eng. Chem. Process Des. Dev.*, 25, 1986. - S. Skogestad, "Simple analytic rules for model reduction and PID controller tuning," *J. Process Control*, 13, 2003 (SIMC). - B. D. Tyreus, W. L. Luyben, "Tuning PI Controllers for Integrator/Dead Time Processes," *Ind. Eng. Chem. Res.*, 31, 1992.