Fix matched c2d/sample returning NaN for a pole or zero at the origin - #1241
Conversation
|
Thanks for the PR! This function has gotten almost no attention and has really atrophied, good find that it fails for systems with poles or zeros at the origin, I agree it shouldn’t do that. So thanks for taking this on. Something to consider: In the intervening years, scipy has introduced an impulse-matched conversion in cont2discrete called “impulse”. https://github.com/scipy/scipy/blob/v1.18.0/scipy/signal/_lti_conversion.py#L384-L583 Its implementation goes through a state-space conversion and back, but it looks clean and simple (only about 4 lines). Do you think it’s better to stick with your algorithm, that works directly with the poles and zeros of the TF, or switch to calling scipy, which is probably more tested? (My take - just call the scipy code, but make sure it passes your unit tests. Edit: Unless you know that this direct-from-the-transfer function approach is better!) |
|
I should be clear - I don’t know whether it is numerically more accurate to compute the impulse-matched conversion as it is done in this PR, from the transfer function directly, or as it is done in scipy using a state-space conversion. Do you know ? |
|
I think the question has a wrinkle in it:
Each is exact on its own invariant and wrong on the other — 10% off in DC gain for the first-order case. There's also a sample of delay: impulse invariance gives a strictly proper discrete system, matched pole-zero doesn't. So switching On the narrower numerical question I honestly don't know, and I didn't want to guess: Either way this PR is the origin-pole/zero NaN, which is orthogonal — happy to keep it scoped to that. |
|
I measured it. For the same transform neither route wins everywhere, but this PR's algorithm sidesteps the failure mode of both. Method: a 120-digit mpmath reference per transform (verified against closed forms, including repeated poles, and against an independent residue oracle), both implementations fed identical float64 coefficients, error = max relative error of
The state-space route loses in
Independent of accuracy: scipy's Where neither helps: at high order the TF representation is the limit, not the algorithm. That rounding floor is 3e-5 for Butterworth 10 and ~1.0 for poles -0.01..-1000 at T=1e-3, so nothing returning a float64 Also, a correction to my earlier comment: impulse invariance is only strictly proper when the relative degree is at least 2 — for |
|
FYI: CI failures have been addressed in #1243. Once that has been merged into main, you should be able to rebase and clear the errors. |
TransferFunction.sample(Ts, method='matched') (and c2d) returned an all-NaN numerator for any system with a pole or zero at s = 0 - integrators, PI/PID controllers, differentiators and other type-1/2 systems. The DC-gain match divides the continuous DC gain (inf for an origin pole, 0 for an origin zero) by a zgain that carries the same vanishing 1 - z factor, so the gain is 0/0 or inf/inf. Match the gain from the non-origin factors only and restore the origin scaling analytically through the z - 1 ~ s*Ts limit, which recovers the textbook Ts/(z - 1) for 1/s, Ts**2/(z - 1)**2 for 1/s**2, and so on. Finite-DC systems are byte-for-byte unchanged. Completes the gain fix from python-control#950/python-control#951.
3bca02b to
f80d990
Compare
|
Rebased onto |
|
Sorry, just got back to my computer after being away for awhile. My mistake about the two different conversions - you're right, scipy's This looks good, thanks for the pr and interesting to see the result of numerical testing. |
method='matched'returns a discrete transfer function with an all-NaNnumerator for any SISO system that has a pole or zero ats = 0— integrators, PI/PID controllers, differentiators, and other type-1/type-2 plants:The gain step matches the DC gain,
gain = dcgain / zgain. A pole or zero at the origin maps toz = 1, so its1 - zfactor vanishes:zgainbecomes0orinfwhiledcgainis simultaneouslyinfor0, giving0/0orinf/inf. This is the origin case of the DC-gain match added in #951 (which corrected the finite-DC offset from #950 but is undefined at the singularity).The fix leaves the origin factors out of the gain product and restores their scaling analytically through the
z - 1 ≈ s·Tlimit. This recovers the textbook matched transforms1/s → T/(z-1),1/s² → T²/(z-1)², … and preserves|G(jω)|across the band; finite-DC systems are byte-for-byte unchanged, so #951 is unaffected.Regression tests cover integrator / PI / differentiator / double-integrator / mixed pole-and-zero-at-origin systems over several sample times, asserting no NaN, the closed-form integrator coefficients, and the low-frequency gain.
matchedalso doesn't add thez = -1zeros for pole excess, but that (standard vs. modified MPZ) is a separate design choice and is left out of scope here.