Skip to content

DeadbeatControl: non-unity DC gain causes static tracking error for Steps > 1 #278

Description

@gabrielsantosphilips

Summary

controllers::DeadbeatControl with Steps > 1 never reaches its reference when used in receding-horizon mode (the mode ComputeControl implies). The closed loop settles to a fraction of the commanded value. Steps == 1 is correct and unaffected.

Introduced in #271.

Root cause

ComputeGains assigns the reference gain directly from the pseudo-inverse block:

auto gammaPinv = solvers::SolveSystem<T, StateSize, ReachSize>(gamma * gamma.Transpose(), gamma).Transpose();

gainRef = gammaPinv.template GetBlock<InputSize, StateSize>(0, 0);
gainState = gainRef * AN;

This is the minimum-norm solution for the whole input sequence [u[k] … u[k+N-1]], and it is correct if that entire sequence is applied open-loop — the state then reaches the reference in exactly N steps.

But ComputeControl returns only the first element and is called again every sample, so the effective closed loop is:

x[k+1] = (A - B·gainState)·x[k] + B·gainRef·r

Taking only the first element of a min-norm sequence and recomputing does not preserve the terminal condition. For a scalar plant the closed-loop pole is Ad/(Ad²+1) — stable, geometric, not deadbeat — and the DC gain is:

$$\frac{A_d}{A_d^2 - A_d + 1} < 1$$

For Steps == 1 the same algebra gives a pole at the origin and DC gain exactly 1, which is why the defect is invisible there.

Reproduction

Scalar plant A = [Ad], B = [Bd], DeadbeatControl<float, 1, 1, 2>, simulated closed-loop to steady state with r = 1.0:

Ad Steps Steady state Error
0.9048 1 1.000000 0.00%
0.9048 2 0.990091 0.99%
0.8465 2 0.972912 2.71%
0.6065 2 0.796653 20.33%

The error grows as Ad decreases, i.e. as the plant gets faster relative to the sample rate.

Why the existing tests miss it

All three tests in TestDeadbeatControl.cpp assert open-loop N-step convergence from a fixed reference, or zero control when already at the reference. None checks closed-loop steady state, and none sweeps Ad. A regression test should simulate to steady state and assert x → r.

Suggested fix

Solve for a reference gain that makes the closed-loop DC gain unity (the standard N̄ precompensator) instead of taking the pseudo-inverse block:

B·gainRef = I - A + B·gainState

For the scalar case this reduces to gainRef = (1 - Ad + Bd·gainState) / Bd. Steps == 1 already satisfies it, so the change is backward compatible.

Caveat worth deciding on

This is not universally solvable. B·gainRef has rank ≤ InputSize, so exact unity DC gain on every state requires B to have full row rank. It works for StateSize == InputSize, but an under-actuated plant with Steps > 1 cannot achieve it exactly.

So there are two defensible resolutions:

  1. Apply the N̄ correction where B has full row rank, and document the limitation otherwise.
  2. Declare that DeadbeatControl is intended for open-loop application of the full N-step sequence, and add an API that exposes the whole sequence — making the current receding-horizon use of ComputeControl the misuse.

Option 2 is arguably more faithful to what the min-norm solution actually computes, but it conflicts with ComputeControl being the StateFeedbackController interface method, which strongly implies per-sample feedback use.

Context

Found while adopting DeadbeatControl for the PMSM current loop in the downstream e-foc project (per-axis scalar RL plant, Ad = e^(-Rs·Ts/Ls), 20 kHz). We are currently shipping Steps = 1 only; the two-step variant is intended for low-inductance motors, which is exactly the small-Ad regime where the error is largest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions