Feedforward and disturbance rejection in PID loops
Feedback waits for error; feedforward uses a measured disturbance. Static and lead-lag FF, PID plus FF, and how to test the combination in simulation.
A PID controller cannot act on a load until the load has already moved the process variable. That is not a tuning problem. It is the structure of feedback: the error is the sensor. If the disturbance is measured, you do not have to wait. Feedforward computes an actuator move from the disturbance and applies it before, or at least while, the error appears. The PID remains in charge of setpoint tracking, model error, and every load you did not measure. The two together — a feedback PID plus a feedforward term from a measured disturbance — are how heat exchangers hold outlet temperature when inlet flow changes, how fired heaters ride a pass-flow swing, and how a drum-level loop survives a steam-demand step that would otherwise empty the glass.
This article treats feedforward as an engineering calculation, not as a checkbox. Static feedforward is the plant inverted at DC: u_ff = −(Kd / Kp) d in consistent gain units. Dynamic feedforward puts a lead-lag (or a delay compensation) on that same path so the timing matches. Combined PID plus feedforward needs bump-free switching, limits, and a PID that is not tuned as if it still had to reject the measured load by itself. The limits are honest: unmeasured disturbances still need PID, inverted models that are wrong will over-feedforward, and a feedforward that saturates the valve has made things worse. The last sections show how to test the combination in simulation against a pure PID, which is the experiment you should run in PID Solver 360 before anyone wires a summer on a live DCS.
Feedback waits; feedforward uses a measurement
Write the plant as two paths. The manipulated variable u (valve, firing, speed command) enters through Gp(s). A disturbance d (incoming temperature, inlet flow, load torque, header pressure) enters through Gd(s). The process variable is
y(s) = Gp(s) u(s) + Gd(s) d(s)Feedback measures y, forms e = r − y, and runs a PID. The PID sees d only through y. The best possible feedback rejection is constrained by the loop bandwidth you can afford, which on a process plant is constrained by dead time. You cannot reject a disturbance faster than the delay in Gp, no matter how large Kp is. That is why violent tuning looks busy on a load step and still lets the first minutes of error through.
Feedforward measures d and adds a term u_ff(s) = Gff(s) d(s) into the actuator. If you could set
Gff(s) = − Gd(s) / Gp(s)then y would be independent of d. That is perfect cancellation. It is also unattainable as a general transfer function: Gd / Gp is often improper, often non-causal (when the disturbance reaches y before the actuator can), and always uncertain. The engineering job is to implement a stable, causal approximation of −Gd/Gp and to let PID mop up the residual.
Two consequences follow immediately. First, if you cannot measure `d`, you cannot feedforward. Inferential substitutes (a nearby temperature, a calculated duty) are feedforward only to the extent they correlate with the real load; when they do not, you have added a disturbance. Second, feedforward does not replace PID. Setpoint changes do not appear in d. Unmeasured loads do not appear in d. Gain error in Gff leaves a residual that only feedback can remove. A plant that “runs on feedforward” with the PID in manual is a plant that will drift.
Cascade is a different structure: it measures a secondary process variable on the path, not the disturbance itself. A fuel-flow slave rejects header pressure without ever having a header-pressure transmitter. Feedforward rejects a measured load that may not be on a secondary you can close a loop around — inlet temperature is the classic case. The two combine well: feedforward into a cascade master’s output, or into the slave setpoint, is how a heater holds outlet temperature when pass flow changes. See cascade PID for the inner-loop half of that design.
Static feedforward: invert the DC gains
At steady state the plants are numbers. Let Kp = Gp(0) be the process gain from actuator to PV, and Kd = Gd(0) the gain from disturbance to PV. Perfect static cancellation requires
u_ff = − (Kd / Kp) dplus, in implementation, a bias so that u_ff is zero at the design operating point. If d is measured as a deviation from that point, the formula is already in deviation variables. If d is an absolute signal, subtract the design value first or the feedforward will slam the valve when you switch it on.
Units must match. If Kp is 2.0 °C per percent valve and Kd is −0.4 °C per (°C of inlet temperature) — inlet hotter, outlet hotter, so the valve must close — then
Kd / Kp = −0.4 / 2.0 = −0.2 % valve per °C inletand u_ff = −(Kd/Kp) d = 0.2 d in those units, with d the inlet-temperature deviation. The two minus signs are where people lose a day of commissioning. Draw the signs on a sketch: if an increase in d raises y, and an increase in u also raises y, feedforward must decrease u when d increases. If an increase in d lowers y (more cold feed), feedforward must increase u.
Static feedforward is a gain. It is correct at DC and wrong during the transient whenever Gd and Gp have different dynamics. On many exchangers the disturbance (inlet temperature) and the actuator (steam flow) share similar lags, so the static ratio is already a good start. On a tank, inlet-flow feedforward to outlet flow can be static and almost perfect, because both paths are the same mass balance: every extra m³/h in should become an extra m³/h out, and the gain ratio is −1 in consistent flow units. Level PID then only trims inventory. That is why averaging-level control plus flow feedforward is more powerful than a heroic level PID.
You get Kp and Kd from the same class of experiment you use for PID tuning. Step the actuator at constant disturbance, read Kp from Δy / Δu. Step the disturbance at constant actuator (or wait for a natural step and use the recorded Δd), read Kd. The identifier is built for the Gp step; the Gd step is the same calculation on a different input. Do not steal Kp from a closed-loop autotune and combine it with a Kd guessed from a data sheet. Inconsistent gains produce a feedforward that is systematically high or low, and the PID will look “offset-prone” when it is actually correcting your ratio forever.
Static feedforward fails when the steady-state ratio depends on operating point: a heat exchanger whose Kp falls as the valve opens, a pH loop, a once-through boiler at different loads. Then either schedule Kd/Kp with load, or keep the static gain conservative (slightly under-feedforward) and let PID finish. Over-feedforward — moving the valve more than the true inverse — creates a disturbance in the opposite direction, which is more embarrassing than under-feedforward, because operators see the PV move the wrong way when the load changes.
Dynamic feedforward: lead-lag and delay
The ideal Gff = −Gd/Gp is dynamic. For FOPDT paths
Gp(s) = Kp e^(−Lp s) / (τp s + 1)
Gd(s) = Kd e^(−Ld s) / (τd s + 1)the ideal feedforward is
Gff(s) = − (Kd/Kp) · (τp s + 1)/(τd s + 1) · e^(−(Ld − Lp) s)Three pieces: the static gain, a lead-lag, and a delay difference.
If Ld > Lp, the disturbance reaches the PV later than the actuator would, and you should delay the feedforward by Ld − Lp so you do not move the valve early and create an unnecessary bump. That delay is causal and easy. If Ld < Lp, the disturbance hits y before the actuator can, and the exponential is a prediction: non-causal. You cannot implement it. The best you can do is act immediately and let the lead term help; the first part of the error is unavoidable. Dead time in Gp is the same enemy feedforward cannot defeat that PID cannot defeat. Measure Lp honestly.
The lead-lag (τp s + 1)/(τd s + 1) is the workhorse. If the actuator path is slower than the disturbance path (τp > τd), you need lead: start moving the valve ahead of the disturbance’s effect. If the actuator path is faster (τp < τd), you need lag: do not dump the whole static move at once, or you will overshoot y while Gd is still catching up. In DCS blocks this is usually
Gll(s) = Kff (α s + 1) / (β s + 1)with Kff = −Kd/Kp, α = τp, β = τd, plus a separate dead-time block if Ld > Lp. Real blocks also clamp the lead, because a pure differentiator on a noisy d is an actuator chew. Filter the disturbance measurement first, with a time constant smaller than β and α, or you will feed noise forward with high bandwidth — worse than PID derivative, because feedforward is not inside a loop that can be detuned with Kp.
A first-order lead-lag is enough for most process FOPDT pairs. Higher-order inversion is how feedforward gets a bad name: an inverted zero near the imaginary axis, a noisy analyser as d, and a valve that follows the analyser noise. If Gd is poorly known, implement static feedforward only. A correct DC ratio with no lead is better than a clever lead-lag fitted to one campaign of data.
On integrating processes the static picture changes. For a tank, Gp(s) ≈ Kp / s from outflow to level and Gd(s) ≈ Kd / s from inflow to level, with Kd = −Kp in consistent units. The s cancels and Gff is again a gain (outlet flow setpoint = inlet flow, plus the PID trim). You do not need a lead-lag to invert two integrators that already match. You do need the flow measurement to be in the same units as the slave setpoint if the level controller is cascaded to outflow.
Combined PID and feedforward
The implemented law is a sum:
u = u_pid + u_ffwith u_pid the usual PID on e = r − y, and u_ff = Gff d. The summer sits before the actuator limits. That sentence is the implementation detail that decides whether the scheme works.
If you add u_ff after the PID limiter, feedforward can drive the valve into saturation while the PID still thinks it has headroom, or feedforward can be clipped while the PID winds. Put the summer inside the limited output, and run anti-windup on the PID against the total limited u, not against u_pid alone. When the valve is at 100 %, both the PID integral and any further feedforward increase are useless; the back-calculation should freeze the integrator at the value that makes u_pid + u_ff sit on the limit.
Setpoint handling: feedforward does not know about r. A setpoint step is the PID’s job. Do not expect Gff to improve a setpoint response; if it seems to, you have accidentally correlated d with r (operators changing load and setpoint together) and you will pay for it when they change independently. Derivative on PV rather than on error remains good practice so that setpoint steps do not spike u_pid on top of u_ff.
Tuning interaction: if you tune the PID with feedforward already working on the loads you will test, the PID will be given an easy job and you will pick a sleepy controller. Then, on the first unmeasured load, the loop is too slow. If you tune the PID without feedforward, you get a controller that can reject that load unaided — which is what you want as a backup — and when you enable Gff the measured-load response becomes much better while the unmeasured-load response stays as designed. That is the right order: tune PID on the plant as if feedforward might be off (because it will be, the day the disturbance transmitter fails), then enable Gff and do not immediately cut Kp just because the measured-load plot looks quiet. You may lengthen Ti slightly if the combination overshoots on a measured load, but keep enough PID that a lost d measurement is not an incident.
When the disturbance transmitter fails, d often goes to zero, span, or last value. Last-value freeze is the least dangerous if you also alarm. Zeroing d without removing u_ff’s bias will shift the operating point. A failure mode that belongs in the design: if d is bad, switch Gff off and leave PID in automatic. That is an extra line of logic, not an optional extra.
Heat exchangers and incoming temperature
A steam-heated (or hot-oil) exchanger with a cold-side outlet temperature as PV is the cleanest process example. Measured disturbances that deserve feedforward are inlet temperature and inlet flow (throughput). Steam-header pressure is often better handled by a steam-flow cascade than by a pressure feedforward, because the cascade also linearises the valve.
Inlet temperature: Kd is typically positive (hotter in, hotter out) and of order the effectiveness of the exchanger, less than one in dimensionless terms, but you need it in °C outlet per °C inlet at the actual flow. Kp is °C outlet per percent valve or per steam-flow setpoint. Static Kff = −Kd/Kp closes the valve as inlet warms. Dynamics: the inlet-temperature path and the steam path often have similar τ (both wait on the same metal and holdup) but different delays (inlet bulb location versus steam-valve location). Fit τp, τd, Lp, Ld from two step tests if you can, or from one well-instrumented campaign. If τp ≈ τd, skip the lead-lag and keep static feedforward plus PID. The lead-lag buys you the cases where the steam valve is slow (large actuator, lagged trap) relative to a fast inlet-temperature change.
Inlet flow: more throughput usually cools the outlet if steam is unchanged, so Kd is negative in “°C per (m³/h).” Feedforward must open steam as flow rises. This path is often more dynamic than inlet temperature: flow changes can be fast (a pump start, a batch dump), while the thermal Gp is slow. That is a lead-dominant Gff: you want steam moving immediately. Limit the lead. A pump start that triples flow will ask for a triple steam move if you invert a linear Kd; the real exchanger saturates thermodynamically, and the valve will hit 100 % anyway. Static feedforward with a scheduled gain (or a simple concave curve versus flow) beats an aggressive linear lead that saturates.
Testing on an exchanger: do not commission feedforward by staring at a noisy day. Make or wait for a step in inlet temperature of several degrees at roughly constant flow, with PID in automatic and Gff off; record the outlet excursion and the PID’s steam move. Compute the static ratio from that record. Enable Gff and repeat a similar inlet step. The outlet excursion should drop by a factor of two to five if the ratio is close; it will not drop to zero. Then a flow step. Then, with Gff on, a steam-header event if you do not have cascade — to remind everyone that unmeasured (or differently measured) loads still belong to the PID.
Fired heaters with pass-flow feedforward into a fuel-flow slave follow the same arithmetic with more safety constraints. Do not invert a pass-flow measurement into firing if the flow meter can fail high and the feedforward would cut fuel under load. Constrain u_ff, keep the temperature PID able to fire, and keep the trip system independent of Gff.
What feedforward cannot do
Unmeasured disturbances still need PID. Ambient losses, fouling, a second inlet you did not meter, composition, a sticky valve, a sensor bias on y itself — none of these appear in d. If you have spent the project’s instrumentation budget on a beautiful Gff and a timid PID, the loop will look brilliant in the commissioning test you designed and average on the plant you actually run. Tune the PID as a real regulator. Judge it on an unmeasured load even after feedforward is on.
Model error in Gd/Gp becomes a new disturbance. A 30 % error in Kd/Kp means 30 % of the measured load still hits y, and the other 70 % is cancelled; that is still a win. A sign error or a unit error means 170 % of the load hits y in the wrong story. Commission with a small Kff (half the calculated ratio), watch the sign, then raise it. Under-feedforward is stable. Over-feedforward with the wrong sign is an oscillator with extra steps.
Non-minimum-phase and delay: if Lp > Ld, the first moments after a disturbance are uncorrectable by u. Feedforward can still reduce the area under the error, but the initial dip or spike is physics. Do not crank lead to “get ahead” of a delay you do not have; you will move y the wrong way first.
Saturation: u_ff + u_pid is still one valve. A feedforward that uses 80 % of the valve on a routine flow swing has left the PID 20 % to handle everything else, including the same swing’s model error. If routine loads need most of the actuator, the unit is undersized; feedforward will not invent steam.
Noise and sampling: analyser feedforward (composition, moisture) is slow and noisy. Filter heavily, use static Kff, and never lead an analyser. Flow and temperature feedforward can be fast; still filter at a few times the noise period. Sampled d with a long cycle (a GC) should update u_ff as a step through the static gain, not through a lead-lag that assumes continuous d.
Feedforward is also not a substitute for the right structure. Header pressure on a fuel valve is a cascade problem. Valve stiction is a maintenance problem. A loop that cannot track setpoint is a PID or saturation problem. Add Gff when you have a measured d with a repeatable Kd and a Gp you already trust enough to tune.
How to test feedforward in simulation versus pure PID
A live plant gives you one disturbance at a time, never the same twice, with the PID always in the picture. Simulation is where you separate the effects. You need a plant model with two inputs, u and d. PID Solver 360’s core studio is a single manipulated input; you can still do a fair test by treating the disturbance as an additive load on the PV or as a temporary shift in apparent setpoint, but the honest study is:
- Model
Gpas the FOPDT you identified from an actuator step. - Model
Gdas a second FOPDT from a disturbance step (or from first-principles gains and the sameτif you only have a staticKd). - Run PID only. Disturbance step
Δdat t = t0, no setpoint change. Record IAE, peak error, settling, and the actuator trajectory. This is the baseline. Use the same PID you would ship if the disturbance transmitter died. - Run feedforward only, PID in manual at the design bias. The same
Δd. PerfectGffwould leaveyunchanged; the residual is your inversion error. If even this case is worse than PID, yourGffsign or gain is wrong — fix it before combining. - Run PID + Gff. Same
Δd. Peak error and IAE should fall sharply versus PID only. Settling may be similar or faster. The actuator should move earlier than in the PID-only run; that earlier move is the whole point. - Run PID + Gff on a disturbance
Gffcannot see (a second load, orΔdwithGffswitched off). This must look like the PID-only case, not worse. If it is worse, the PID was detuned against the feedforward test and you have stolen robustness. - Repeat with
Kdwrong by ±30 % in the plant but not inGff(model error). Feedforward should still beat PID-only on IAE for the measured load. If a +30 % gain error makes the combination worse than PID-only, you over-inverted; reduceKff. - Repeat with noise on
d. If the valve plot becomes unusable, add a filter or drop the lead.
Metrics: peak error is what quality cares about; IAE is what the regulator literature cares about; actuator travel is what maintenance cares about. Feedforward should win on the first two for the measured load and should not lose badly on the third. If feedforward wins IAE by chattering the valve, you will not be allowed to keep it. Compare against the same closed-loop metrics you use for PID: overshoot on setpoint (should be almost unchanged), load IAE (should drop for measured d), and gain and phase margin of the feedback loop (unchanged by Gff, because feedforward is outside the loop — unless you accidentally scheduled PID gains).
A simple numerical picture. Suppose
Gp(s) = 2.0 e^(−8 s) / (60 s + 1)
Gd(s) = 0.5 e^(−5 s) / (40 s + 1)with d in the same units as a measured inlet-temperature deviation. Static Kff = −0.5/2.0 = −0.25. Ideal extra dynamics: lead-lag (60 s + 1)/(40 s + 1) and no extra delay because Ld − Lp = −3 s is non-causal; implement lead-lag without delay. A PID tuned by SIMC on Gp with τc = L will, on a unit step in d, show a peak outlet error on the order of several tenths of a unit and a recovery of a few minutes. With static Kff only, that peak often halves. With the lead-lag, the peak falls again if d is clean. With a sign error in Kff, the peak roughly doubles. That last run is worth doing once in simulation so the commissioning team knows what “wrong sign” looks like.
You can approximate the two-input test inside a single-input simulator by adding the disturbance effect onto the setpoint (a downward SP step of Kd Δd in magnitude) while simultaneously injecting u_ff as a bias change on the output. It is clumsy, but it trains the eye: PID-only versus output-bias-plus-PID. The docs describe the studio modes; use identify for Gp, tuning for the PID, and the simulator for the PID-only baseline. Treat Gff as a calculated bias trajectory you compare by eye to the PID’s slower output move.
A commissioning sequence that stays reversible
Identify Gp. Tune a robust PID (SIMC or AMIGO, not a quarter-amplitude Ziegler–Nichols unless you have a reason) and confirm setpoint and unmeasured-load behaviour. That controller is the fallback. Identify Kd and, if the tests exist, τd and Ld. Compute static Kff = −Kd/Kp and the lead-lag if both time constants are trusted. Implement u = u_pid + u_ff with shared limits and anti-windup on the total output. Enable Gff at half gain. Wait for a real disturbance or inject a small one. Check the sign. Raise to the calculated gain. Do not add lead until static feedforward is boring and correct. Document the failure action: bad d switches Gff off, PID stays in automatic.
Feedforward is the rare control element whose ideal form you can write in one line, −Gd/Gp, and whose practical form is a gain, a lead-lag, a delay, and a pile of limits. It does not invent causality, it does not measure the unmeasured, and it does not excuse a weak PID. Used on a real d with a real Kd, it removes the part of the load that feedback would otherwise have to wait for. That wait is the entire cost of a pure PID regulator on a slow plant. Paying it when you already have a flow meter or an inlet RTD is a choice, not a law of control.
Put this into practice — model your process, auto-tune it and check the stability margins.
Launch PID Solver 360