Deadbeat Control (gnc-autonomy/control/deadbeat-control)
Use when the task is a direct deadbeat synthesis for a discrete-time
plant: the plant is already given as a pulse transfer function G(z) =
B(z)/A(z) (no discretization is performed here), and the controller
must place every closed loop pole at the origin of the z plane so the
step response settles in a finite number of sample periods. This leaf
implements the deadbeat response design of Franklin, Powell and Workman
(Digital Control of Dynamic Systems, ch. 4-5) and Ogata (Discrete-Time
Control Systems, ch. 6) in pure Python, stdlib only, deterministic and
offline. It pairs with digital-control-design for the sampled-data
toolbox that produces a discrete plant from a continuous one, and with
pid-control-design for the continuous-plant analogue of direct pole
placement.
Domain quick reference
- Plant: G(z) = B(z)/A(z), coefficient lists in descending powers of z;
A is normalized monic (scaled by A[0]); the plant must be strictly
proper (deg B < deg A) and first or second order (1 <= deg A <= 2).
- Admissibility gate: deadbeat synthesis cancels the plant modes
exactly, so every canceled mode must be strictly stable: every root
of A and every root of B must have modulus strictly below 1.0. A pole
or zero on or outside the unit circle makes the design inadmissible.
Roots come from closed forms only (degree 1 and 2); degree 3 and
higher is out of scope.
- Deadbeat controller: unity feedback D(z) = N_c(z)/D_c(z) = A(z)/(B(z)
(z^d - 1)) with relative degree d = deg A - deg B, the plant's pure
sample delay. The closed-loop characteristic polynomial is
AD_c + BN_c = AB*z^d, so after the strictly-stable cancellations
the observable closed loop is T(z) = z^-d: a pure d-sample delay,
every closed-loop pole at the origin.
- Controller recursion on the tracking error: u(k) = sum_i num_e[i]*
e(k - i) + sum_j den_u[j]u(k - 1 - j), with num_e[i] = A[i]/mu0 and
mu0 the leading coefficient of B; den_u comes from the coefficients
of B(z)(1 - z^-d) shifted by the relative degree d.
- Plant recursion for the closed-loop simulation (zero initial state):
y(k) = -sum_i A[i]*y(k - i) + sum_j B[j]*u(k - d - j), advanced from
past samples only.
- Deadbeat response: the step response reaches the reference exactly at
sample d and holds it (settling time d*T_s); tracking error is
exactly r for 0 <= k < d and exactly zero for k >= d.
- Steady-state tracking: the control that holds y = r is u* = A(1)/B(1)
(the inverse of the plant DC gain). For a plant with no numerator
zeros the control reaches u* exactly at sample d; a plant with an
interior zero keeps the output deadbeat while the control converges
geometrically to u* at the plant-zero rate.
- Sample time T_s is reporting-only: it converts sample counts to a
time axis (settling time = d*T_s) and is never a design rule; the
sample-rate selection rule belongs to digital-control-design.
Workflow
- Fix the plant: the pulse transfer function G(z) = B(z)/A(z) as
descending-power coefficient lists, and confirm it is strictly
proper and first or second order.
- Verify admissibility with admissibility_check: every pole and zero
modulus must be strictly below 1.0, using the closed-form roots from
roots_moduli_desc.
- Solve the deadbeat design equation with deadbeat_design: place every
closed loop pole at the origin of the z plane and get the controller
coefficients num_e, den_u, the z-domain controller d_num_desc and
d_den_desc, and the pole-placement identity char_poly_desc.
- Form the finite-settling-time controller difference equation from
num_e and den_u (the causal recursion on the tracking error).
- Simulate the closed loop with simulate under the pinned per-sample
ordering to get the step response, tracking error and control
sequence histories.
- Read the settling sample and settling time from settling_report and
confirm the output has settled (settled True).
- Check the steady-state tracking with steady_control (u* = A(1)/B(1))
and summarize the control sequence with control_effort.
- Confirm the pure-delay identity with closed_loop_impulse: the
closed-loop impulse response is a d-sample delay.
- Confirm the deterministic checks with the contract test
scripts/test_deadbeat_control.py.
Worked example
Scenario A (first order, no zeros): G(z) = 0.5/(z - 0.5), A =
[1.0, -0.5], B = [0.5], T_s = 0.1 s, unit step r = 1.
- admissibility_check: admissible True, pole_moduli [0.5], zero_moduli
[].
- deadbeat_design: n = 1, m = 0, d = 1; num_e = [2.0, -1.0],
den_u = [1.0], mu0 = 0.5; controller recursion u(k) = 2.0e(k) -
1.0e(k - 1) + 1.0*u(k - 1); u_star = 1.0.
- Step response reaches y = 1.0 exactly at sample 1 (settling time
0.1 s) and holds it; error is 1.0 at sample 0 and 0.0 from sample 1
on; control effort u = [2.0, 1.0, 1.0, ...], peak 2.0, u* = 1.0 from
sample 1 on.
Scenario B (second order, no zeros): G(z) = 0.2/(z^2 - 1.1*z + 0.24),
A = [1.0, -1.1, 0.24], B = [0.2], T_s = 0.02 s, unit step r = 1.
- admissibility_check: pole_moduli [0.8, 0.3] (real anchors within
1e-9 of these values), admissible True.
- deadbeat_design: n = 2, m = 0, d = 2; num_e = [5.0, -5.5, 1.2],
den_u = [0.0, 1.0], mu0 = 0.2; char_poly_desc = [0.2, -0.22, 0.048,
0.0, 0.0]; u_star within 1e-9 of 0.7, plant_dc_gain within 1e-6
relative of 10/7.
- Step response reaches y = 1.0 at sample 2 (settling time 0.04 s) and
holds it to float precision; error area sum(e) = d = 2 within 1e-6
relative; control u = [5.0, -0.5, 0.7, 0.7, ...], peak 5.0, u* = 0.7
from sample 2 on; the pole-placement identity AD_c + BN_c equals
char_poly_desc within 1e-12; the closed-loop impulse response is a
pure 2-sample delay.
Scenario C (interior zero, relative-degree generality): G(z) =
(0.1z + 0.05)/(z^2 - 1.1z + 0.24), A = [1.0, -1.1, 0.24],
B = [0.1, 0.05], T_s = 0.02 s, unit step r = 1.
- admissibility_check: pole_moduli [0.8, 0.3], zero_moduli [0.5], all
admissible.
- deadbeat_design: n = 2, m = 1, d = n - m = 1: the interior zero
shortens the minimum settling time; num_e = [10.0, -11.0, 2.4],
den_u = [0.5, 0.5], mu0 = 0.1; u_star within 1e-9 of 14/15.
- Step response reaches y = 1.0 at sample 1 (settling time 0.02 s) and
holds it; the OUTPUT is deadbeat from sample 1 while the CONTROL
converges geometrically to u* at the plant-zero rate (control effort
u(0) = 10.0, u(1) = -6.0, u(39) within 1e-6 relative of 14/15).
A complex-pole guard plant G(z) = 0.1/(z^2 - 1.6z + 0.65) has pole
pair 0.8 +/- j0.1, pole_moduli sqrt(0.65) approx 0.8062257748298549
(both entries), admissible True: the closed-form quadratic branch
covers complex poles.
Verification
- Confirm admissibility_check returns the exact dict keys
{'admissible', 'pole_moduli', 'zero_moduli', 'reason'} and matches
the pole and zero moduli of scenarios A, B, C and the complex-pole
guard plant.
- Confirm deadbeat_design on scenarios A, B and C matches the worked
num_e, den_u, mu0 and u_star values within tolerance, and that the
pole-placement identity AD_c + BN_c equals char_poly_desc = ABz^d
within 1e-12.
- Confirm simulate reproduces the step response, error and control
histories of the worked examples within tolerance, and that
closed_loop_impulse gives a pure d-sample delay.
- Confirm deadbeat_design raises ValueError for an unstable plant, a
pole or zero on the unit circle, a non-minimum-phase zero, a degree-3
plant, a non-strictly-proper plant, an empty plant list and a zero
leading A coefficient.
- Confirm simulate raises ValueError for a non-positive sample time, an
unknown reference string, too few steps, and an inadmissible plant.
- Run the contract test offline: python3
scripts/test_deadbeat_control.py (deterministic, no imports beyond
math).
Related leaves
- gnc-autonomy/control/digital-control-design: the sampled-data toolbox
that discretizes a continuous plant with a zero-order hold and
emulates a continuous compensator, the source of a discrete plant
this leaf can then consume.
- gnc-autonomy/control/pid-control-design: continuous s-domain pole
placement for a first or second order plant, the continuous analogue
of the direct pole-placement idea used here in the z-domain.
- gnc-autonomy/control/state-space-analysis: eigenvalue and
controllability context for the state-space view of a discrete plant.
Pitfalls
- Feeding a continuous plant directly: this leaf never discretizes; the
plant coefficients A and B must already be the pulse transfer
function coefficients of a discrete-time plant (use
digital-control-design first if the plant starts continuous).
- Skipping the admissibility gate: a plant with a pole or zero outside
or on the unit circle cannot be canceled safely; deadbeat_design
raises ValueError rather than returning a marginal design, since an
uncanceled unstable mode would appear unbounded in the closed loop.
- Assuming the settling sample always equals the plant order: with an
interior plant zero (scenario C) the settling sample is d = n - m,
strictly less than n; only a plant with no numerator zeros settles at
exactly n samples.
- Reading the control sequence as deadbeat too: only the OUTPUT is
guaranteed to reach and hold the reference in d samples; when the
plant has a zero, the control sequence itself converges geometrically
to u* at the plant-zero rate rather than jumping to it.
- Treating T_s as part of the design: sample time only converts sample
counts to a time axis (settling time = d*T_s); it never changes the
controller coefficients and it is not a sample-rate selection rule.
Behavior contract (gate 3)
Run the deterministic contract test (stdlib unittest, offline):
python3 scripts/test_deadbeat_control.py
The test covers the admissibility truth table for scenarios A, B, C and
the complex-pole guard plant, the deadbeat design coefficients and the
pole-placement identity, the closed-loop step, error and control
histories of all three worked scenarios within tolerance, the
steady-state tracking identity, the pure-delay impulse identity,
determinism of repeated simulate runs, and ValueError rejection of
unstable, boundary, non-minimum-phase, degree-3 and non-strictly-proper
plants and of invalid simulate arguments.
Compliance
- Standards referenced, not reproduced: ARP4754A is a proprietary SAE
standard (name plus paraphrase only, per standards-map.yaml); the
deadbeat design relations above are standard engineering methodology
from Franklin, Powell and Workman and from Ogata, summary-only.
- compliance: STANDARDS-REF, gated: false.
1---2name: deadbeat-control3description: Use when you must design a deadbeat-control law for a discrete-time plant given by its pulse transfer function: verify admissibility for direct deadbeat synthesis (every plant pole and zero strictly inside the unit circle), solve the deadbeat design equation that places every closed loop pole at the origin of the z plane, form the finite-settling-time controller difference equation from the plant polynomials, and simulate the closed loop to confirm the output reaches and holds the reference in the minimum number of sample periods with the minimum-settling-time control sequence. Produces the admissibility verdict, the controller coefficients, the settling sample and settling time, the step response and control effort histories, the steady-state tracking check and the control effort summary that gate a deadbeat digital control assessment. Trigger: deadbeat-control, finite-settling-time, pole-placement-at-origin, minimum-settling-time, z-domain-deadbeat.4license: Apache-2.05---67# Deadbeat Control (gnc-autonomy/control/deadbeat-control)89Use when the task is a direct deadbeat synthesis for a discrete-time10plant: the plant is already given as a pulse transfer function G(z) =11B(z)/A(z) (no discretization is performed here), and the controller12must place every closed loop pole at the origin of the z plane so the13step response settles in a finite number of sample periods. This leaf14implements the deadbeat response design of Franklin, Powell and Workman15(Digital Control of Dynamic Systems, ch. 4-5) and Ogata (Discrete-Time16Control Systems, ch. 6) in pure Python, stdlib only, deterministic and17offline. It pairs with digital-control-design for the sampled-data18toolbox that produces a discrete plant from a continuous one, and with19pid-control-design for the continuous-plant analogue of direct pole20placement.2122## Domain quick reference2324- Plant: G(z) = B(z)/A(z), coefficient lists in descending powers of z;25 A is normalized monic (scaled by A[0]); the plant must be strictly26 proper (deg B < deg A) and first or second order (1 <= deg A <= 2).27- Admissibility gate: deadbeat synthesis cancels the plant modes28 exactly, so every canceled mode must be strictly stable: every root29 of A and every root of B must have modulus strictly below 1.0. A pole30 or zero on or outside the unit circle makes the design inadmissible.31 Roots come from closed forms only (degree 1 and 2); degree 3 and32 higher is out of scope.33- Deadbeat controller: unity feedback D(z) = N_c(z)/D_c(z) = A(z)/(B(z)34 *(z^d - 1)) with relative degree d = deg A - deg B, the plant's pure35 sample delay. The closed-loop characteristic polynomial is36 A*D_c + B*N_c = A*B*z^d, so after the strictly-stable cancellations37 the observable closed loop is T(z) = z^-d: a pure d-sample delay,38 every closed-loop pole at the origin.39- Controller recursion on the tracking error: u(k) = sum_i num_e[i]*40 e(k - i) + sum_j den_u[j]*u(k - 1 - j), with num_e[i] = A[i]/mu0 and41 mu0 the leading coefficient of B; den_u comes from the coefficients42 of B(z)*(1 - z^-d) shifted by the relative degree d.43- Plant recursion for the closed-loop simulation (zero initial state):44 y(k) = -sum_i A[i]*y(k - i) + sum_j B[j]*u(k - d - j), advanced from45 past samples only.46- Deadbeat response: the step response reaches the reference exactly at47 sample d and holds it (settling time d*T_s); tracking error is48 exactly r for 0 <= k < d and exactly zero for k >= d.49- Steady-state tracking: the control that holds y = r is u* = A(1)/B(1)50 (the inverse of the plant DC gain). For a plant with no numerator51 zeros the control reaches u* exactly at sample d; a plant with an52 interior zero keeps the output deadbeat while the control converges53 geometrically to u* at the plant-zero rate.54- Sample time T_s is reporting-only: it converts sample counts to a55 time axis (settling time = d*T_s) and is never a design rule; the56 sample-rate selection rule belongs to digital-control-design.5758## Workflow59601. Fix the plant: the pulse transfer function G(z) = B(z)/A(z) as61 descending-power coefficient lists, and confirm it is strictly62 proper and first or second order.632. Verify admissibility with admissibility_check: every pole and zero64 modulus must be strictly below 1.0, using the closed-form roots from65 roots_moduli_desc.663. Solve the deadbeat design equation with deadbeat_design: place every67 closed loop pole at the origin of the z plane and get the controller68 coefficients num_e, den_u, the z-domain controller d_num_desc and69 d_den_desc, and the pole-placement identity char_poly_desc.704. Form the finite-settling-time controller difference equation from71 num_e and den_u (the causal recursion on the tracking error).725. Simulate the closed loop with simulate under the pinned per-sample73 ordering to get the step response, tracking error and control74 sequence histories.756. Read the settling sample and settling time from settling_report and76 confirm the output has settled (settled True).777. Check the steady-state tracking with steady_control (u* = A(1)/B(1))78 and summarize the control sequence with control_effort.798. Confirm the pure-delay identity with closed_loop_impulse: the80 closed-loop impulse response is a d-sample delay.819. Confirm the deterministic checks with the contract test82 scripts/test_deadbeat_control.py.8384## Worked example8586Scenario A (first order, no zeros): G(z) = 0.5/(z - 0.5), A =87[1.0, -0.5], B = [0.5], T_s = 0.1 s, unit step r = 1.88- admissibility_check: admissible True, pole_moduli [0.5], zero_moduli89 [].90- deadbeat_design: n = 1, m = 0, d = 1; num_e = [2.0, -1.0],91 den_u = [1.0], mu0 = 0.5; controller recursion u(k) = 2.0*e(k) -92 1.0*e(k - 1) + 1.0*u(k - 1); u_star = 1.0.93- Step response reaches y = 1.0 exactly at sample 1 (settling time94 0.1 s) and holds it; error is 1.0 at sample 0 and 0.0 from sample 195 on; control effort u = [2.0, 1.0, 1.0, ...], peak 2.0, u* = 1.0 from96 sample 1 on.9798Scenario B (second order, no zeros): G(z) = 0.2/(z^2 - 1.1*z + 0.24),99A = [1.0, -1.1, 0.24], B = [0.2], T_s = 0.02 s, unit step r = 1.100- admissibility_check: pole_moduli [0.8, 0.3] (real anchors within101 1e-9 of these values), admissible True.102- deadbeat_design: n = 2, m = 0, d = 2; num_e = [5.0, -5.5, 1.2],103 den_u = [0.0, 1.0], mu0 = 0.2; char_poly_desc = [0.2, -0.22, 0.048,104 0.0, 0.0]; u_star within 1e-9 of 0.7, plant_dc_gain within 1e-6105 relative of 10/7.106- Step response reaches y = 1.0 at sample 2 (settling time 0.04 s) and107 holds it to float precision; error area sum(e) = d = 2 within 1e-6108 relative; control u = [5.0, -0.5, 0.7, 0.7, ...], peak 5.0, u* = 0.7109 from sample 2 on; the pole-placement identity A*D_c + B*N_c equals110 char_poly_desc within 1e-12; the closed-loop impulse response is a111 pure 2-sample delay.112113Scenario C (interior zero, relative-degree generality): G(z) =114(0.1*z + 0.05)/(z^2 - 1.1*z + 0.24), A = [1.0, -1.1, 0.24],115B = [0.1, 0.05], T_s = 0.02 s, unit step r = 1.116- admissibility_check: pole_moduli [0.8, 0.3], zero_moduli [0.5], all117 admissible.118- deadbeat_design: n = 2, m = 1, d = n - m = 1: the interior zero119 shortens the minimum settling time; num_e = [10.0, -11.0, 2.4],120 den_u = [0.5, 0.5], mu0 = 0.1; u_star within 1e-9 of 14/15.121- Step response reaches y = 1.0 at sample 1 (settling time 0.02 s) and122 holds it; the OUTPUT is deadbeat from sample 1 while the CONTROL123 converges geometrically to u* at the plant-zero rate (control effort124 u(0) = 10.0, u(1) = -6.0, u(39) within 1e-6 relative of 14/15).125126A complex-pole guard plant G(z) = 0.1/(z^2 - 1.6*z + 0.65) has pole127pair 0.8 +/- j*0.1, pole_moduli sqrt(0.65) approx 0.8062257748298549128(both entries), admissible True: the closed-form quadratic branch129covers complex poles.130131## Verification132133- Confirm admissibility_check returns the exact dict keys134 {'admissible', 'pole_moduli', 'zero_moduli', 'reason'} and matches135 the pole and zero moduli of scenarios A, B, C and the complex-pole136 guard plant.137- Confirm deadbeat_design on scenarios A, B and C matches the worked138 num_e, den_u, mu0 and u_star values within tolerance, and that the139 pole-placement identity A*D_c + B*N_c equals char_poly_desc = A*B*z^d140 within 1e-12.141- Confirm simulate reproduces the step response, error and control142 histories of the worked examples within tolerance, and that143 closed_loop_impulse gives a pure d-sample delay.144- Confirm deadbeat_design raises ValueError for an unstable plant, a145 pole or zero on the unit circle, a non-minimum-phase zero, a degree-3146 plant, a non-strictly-proper plant, an empty plant list and a zero147 leading A coefficient.148- Confirm simulate raises ValueError for a non-positive sample time, an149 unknown reference string, too few steps, and an inadmissible plant.150- Run the contract test offline: python3151 scripts/test_deadbeat_control.py (deterministic, no imports beyond152 math).153154## Related leaves155156- gnc-autonomy/control/digital-control-design: the sampled-data toolbox157 that discretizes a continuous plant with a zero-order hold and158 emulates a continuous compensator, the source of a discrete plant159 this leaf can then consume.160- gnc-autonomy/control/pid-control-design: continuous s-domain pole161 placement for a first or second order plant, the continuous analogue162 of the direct pole-placement idea used here in the z-domain.163- gnc-autonomy/control/state-space-analysis: eigenvalue and164 controllability context for the state-space view of a discrete plant.165166## Pitfalls167168- Feeding a continuous plant directly: this leaf never discretizes; the169 plant coefficients A and B must already be the pulse transfer170 function coefficients of a discrete-time plant (use171 digital-control-design first if the plant starts continuous).172- Skipping the admissibility gate: a plant with a pole or zero outside173 or on the unit circle cannot be canceled safely; deadbeat_design174 raises ValueError rather than returning a marginal design, since an175 uncanceled unstable mode would appear unbounded in the closed loop.176- Assuming the settling sample always equals the plant order: with an177 interior plant zero (scenario C) the settling sample is d = n - m,178 strictly less than n; only a plant with no numerator zeros settles at179 exactly n samples.180- Reading the control sequence as deadbeat too: only the OUTPUT is181 guaranteed to reach and hold the reference in d samples; when the182 plant has a zero, the control sequence itself converges geometrically183 to u* at the plant-zero rate rather than jumping to it.184- Treating T_s as part of the design: sample time only converts sample185 counts to a time axis (settling time = d*T_s); it never changes the186 controller coefficients and it is not a sample-rate selection rule.187188## Behavior contract (gate 3)189190Run the deterministic contract test (stdlib unittest, offline):191192 python3 scripts/test_deadbeat_control.py193194The test covers the admissibility truth table for scenarios A, B, C and195the complex-pole guard plant, the deadbeat design coefficients and the196pole-placement identity, the closed-loop step, error and control197histories of all three worked scenarios within tolerance, the198steady-state tracking identity, the pure-delay impulse identity,199determinism of repeated simulate runs, and ValueError rejection of200unstable, boundary, non-minimum-phase, degree-3 and non-strictly-proper201plants and of invalid simulate arguments.202203## Compliance204205- Standards referenced, not reproduced: ARP4754A is a proprietary SAE206 standard (name plus paraphrase only, per standards-map.yaml); the207 deadbeat design relations above are standard engineering methodology208 from Franklin, Powell and Workman and from Ogata, summary-only.209- compliance: STANDARDS-REF, gated: false.