Skip to content

State-space → transfer-function drops the feedthrough contribution #253

Description

@gabrielfrasantos

Severity: high
Domain: control_analysis
Status: VERIFIED — read + hand-traced against a210d34 on 2026-08-10
Suggested labels: bug, numerical-correctness, control-analysis

Summary

ToTransferFunction() applies the feedthrough matrix D only to the leading numerator coefficient.
The terms D · charPoly[k] for k ≥ 1 are omitted, so any system with D ≠ 0 converts to the
wrong transfer function.

Location

numerical/control_analysis/TransferFunctionStateSpace.hpp

Evidence

The Faddeev–LeVerrier recursion for the characteristic polynomial and the N_k matrices is
correct. The numerator assembly is not:

std::array<T, n + 1> numCoeffs{};
numCoeffs[0] = sys.D.at(0, 0);

SqMat N{ SqMat::Identity() };
for (std::size_t k{ 1 }; k <= n; ++k)
{
    auto CB{ sys.C * N * sys.B };
    numCoeffs[k] = CB.at(0, 0);          // missing + D * charPoly[k]
    N = sys.A * N;
    for (std::size_t i{ 0 }; i < n; ++i)
        N.at(i, i) += charPoly[k];
}

Expected

Since G(s) = C(sI−A)⁻¹B + D:

numerator(s) = D · charPoly(s) + Σ_k (C N_{k−1} B) s^{n−k}

so numCoeffs[k] = C·N_{k−1}·B + D·charPoly[k] for all k ≥ 1.

Trace

A = −1, B = C = 1, D = 2, n = 1:

charPoly = [1, 1]                      (denominator s + 1)
true G(s) = 1/(s+1) + 2 = (2s + 3)/(s + 1)

code: numCoeffs[0] = 2
      numCoeffs[1] = C·I·B = 1
      => (2s + 1)/(s + 1)              wrong DC gain: 1 instead of 3

Why tests do not catch it

Existing round-trip tests use strictly proper systems (D = 0), where the missing term vanishes
identically. The one feedthrough test does not convert back.

Suggested fix

numCoeffs[k] = CB.at(0, 0) + sys.D.at(0, 0) * charPoly[k];

Add a round-trip test with D ≠ 0 and an analytic DC-gain assertion.

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