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.
Severity: high
Domain: control_analysis
Status: VERIFIED — read + hand-traced against
a210d34on 2026-08-10Suggested labels:
bug,numerical-correctness,control-analysisSummary
ToTransferFunction()applies the feedthrough matrixDonly to the leading numerator coefficient.The terms
D · charPoly[k]fork ≥ 1are omitted, so any system withD ≠ 0converts to thewrong transfer function.
Location
numerical/control_analysis/TransferFunctionStateSpace.hpp
Evidence
The Faddeev–LeVerrier recursion for the characteristic polynomial and the
N_kmatrices iscorrect. The numerator assembly is not:
Expected
Since
G(s) = C(sI−A)⁻¹B + D:so
numCoeffs[k] = C·N_{k−1}·B + D·charPoly[k]for allk ≥ 1.Trace
A = −1,B = C = 1,D = 2,n = 1:Why tests do not catch it
Existing round-trip tests use strictly proper systems (
D = 0), where the missing term vanishesidentically. The one feedthrough test does not convert back.
Suggested fix
Add a round-trip test with
D ≠ 0and an analytic DC-gain assertion.