You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Inconsistent evaluation of tf/ss systems with pole at the origin #532
Systems that have a pole at the origin return inconsistent values:
import control as ct
sys_tf = ct.tf([1], [1, 0])
sys_ss = ct.tf2ss(sys_tf)
sys_tf(0) # returns (inf+0j) with divide by zero warning
sys_tf(0j) # returns (inf+nanj) with divide by zero warning
sys_ss(0) # LinAlgError: Singular matrix
sys_tf.dcgain() # returns array(inf)
sys_ss.dcgain() # returns array(nan)
I think all of these should return inf (or array(inf)).
If you have a pole and zero at the origin, you get nan or 1:
sys_tf = ct.tf([1, 0], [1, 0])
sys_ss = ct.tf2ss(sys_tf)
sys_tf(0) # returns (nan+0j) with invalid value warning
sys_ss(0) # returns (1+0j) [state space system is empty]
sys_tf.dcgain() # returns array(nan)
sys_ss.dcgain() # returns (1+0j) [state space system is empty]
I think all of these cases should produce nan.
I have a partial fix in a branch, but want to see what people think the "right" answer is before committing it.
Would this apply to evaluating a system at any s-value where it has a pole?
For the second set of examples, for TFs one objection is that \lim_{s->0} (s/s) = 1. For a Bode or Nyquist plot, for example, wouldn't one want this limit, and not a NaN ?
For state-space objects, why would you want a static gain to evaluate to NaN?
Separarately for state-space, the MIMO case might be tricky: consider a diagonal system with g_11 = 1/(s+1), g_22 = 1/s; would evaluating this at s = 0 be expected to give [[1, 0], [0, inf]] ? I wonder how easy this will be to arrange:
n [37]: np.eye(2) @ np.diag([1, np.inf]) @ np.eye(2)
<ipython-input-37-7a3bfddc9905>:1: RuntimeWarning: invalid value encountered in matmul
np.eye(2) @ np.diag([1, np.inf]) @ np.eye(2)
Out[37]:
array([[nan, nan],
[nan, inf]])
+1 for consistency. nan looks like a good choice to me.
Although one could argue that the transfer function should make a distinction between true marginal stability cases and cases where the pole/zero at 0 cancel each other. (Just as @roryyorke pointed out just now)
I think point-wise evaluation at 0 is not the correct way to compute the limit but using the explicit expression D - C @ solve(A, B) for the limit should be preferred (and the discrete version obviously). If A is not invertible then it should return inf with correct shape. NaN doesn't seem to be a meaningful return value for the gain.
So basically the real and imaginary parts of the numerator are evaluated independently and 1 / 0 returns inf while 0 / 0 returns nan. These should happen automatically for the TransferFunction class and StateSpace objects should be consistent.
Regarding unstable systems: while it is true that H(jw) is not well defined in terms of giving the magnitude and phase of the output for a sinusoidal input, it is still the case that we want to evaluate transfer functions at these values. For example, the loop transfer function can be unstable while the closed loop system is stable.
Specific proposal:
sys_tf = ct.tf([1], [1, 0])
sys_ss = ct.tf2ss(sys_tf)
sys_tf(0) # return (inf+0j) with divide by zero warning
sys_tf(0j) # return (inf+nanj) with divide by zero warning
sys_ss(0) # return (inf+0j) with singular matrix warning
sys_ss(0j) # return (inf+nanjj) with singular matrix warning
sys_tf.dcgain() # return array(inf+nanj) with divide by zero warning
sys_ss.dcgain() # return array(inf+nanj) with singular matrix warning
Activity
roryyorke commentedon Feb 6, 2021
Would this apply to evaluating a system at any s-value where it has a pole?
For the second set of examples, for TFs one objection is that
\lim_{s->0} (s/s) = 1. For a Bode or Nyquist plot, for example, wouldn't one want this limit, and not a NaN ?For state-space objects, why would you want a static gain to evaluate to NaN?
Separarately for state-space, the MIMO case might be tricky: consider a diagonal system with g_11 = 1/(s+1), g_22 = 1/s; would evaluating this at s = 0 be expected to give [[1, 0], [0, inf]] ? I wonder how easy this will be to arrange:
bnavigator commentedon Feb 6, 2021
+1 for consistency.
nanlooks like a good choice to me.Although one could argue that the transfer function should make a distinction between true marginal stability cases and cases where the pole/zero at 0 cancel each other. (Just as @roryyorke pointed out just now)
ilayn commentedon Feb 6, 2021
I think point-wise evaluation at 0 is not the correct way to compute the limit but using the explicit expression
D - C @ solve(A, B)for the limit should be preferred (and the discrete version obviously). If A is not invertible then it should return inf with correct shape. NaN doesn't seem to be a meaningful return value for the gain.bnavigator commentedon Feb 6, 2021
If the system is not asymptotically stable, is there a static gain?
ilayn commentedon Feb 6, 2021
That's inf by convention which is the value of the limit, NaN is suitable for cases like 0/0 etc.
murrayrm commentedon Feb 8, 2021
I thought it might be interesting to see what Python and NumPy do with various cases:
So basically the real and imaginary parts of the numerator are evaluated independently and
1 / 0returnsinfwhile0 / 0returnsnan. These should happen automatically for theTransferFunctionclass andStateSpaceobjects should be consistent.Regarding unstable systems: while it is true that H(jw) is not well defined in terms of giving the magnitude and phase of the output for a sinusoidal input, it is still the case that we want to evaluate transfer functions at these values. For example, the loop transfer function can be unstable while the closed loop system is stable.
Specific proposal:
Thoughts?