python-control's LTISystem.bandwidth returns infinity for any stable system with a negative DC gain,
even though its magnitude response drops normally. The bandwidth is the frequency where the magnitude has
dropped a given number of decibels below its value at zero frequency, so it depends only on the magnitude
and the sign of the DC gain is irrelevant. The function finds where the magnitude has dropped by testing
mag - dcgain * 10**(dbdrop/20) < 0 with the signed DC gain, but bisects with abs(dcgain). For a
negative DC gain the threshold is negative, so mag - (negative) is never below zero, the drop set is
empty, and the function returns infinity, reporting that the gain never drops. The bisection step in the
same function uses abs(dcgain), so the drop test contradicts it.
control, control/lti.py, LTISystem.bandwidth, 0.10.2 and current main (identical), lines 205 and
215/221:
idx_dropped = np.nonzero(mag - dcgain*10**(dbdrop/20) < 0)[0] # signed dcgain
if idx_dropped.shape[0] == 0:
... return inf ...
else:
... bracket via
lambda w: np.abs(self(w*1j)) - np.abs(dcgain)*10**(dbdrop/20) # abs(dcgain)For a negative DC gain, dcgain * 10**(dbdrop/20) is negative, so mag - (negative) is always positive
and idx_dropped is empty. The bisection lambda uses abs(dcgain), so the drop test is inconsistent with
the function's own bisection. The fix is to use abs(dcgain) in the drop test.
The minus-three-decibel bandwidth from the drop test with the signed or absolute DC gain, on a first-order system:
dc gain +1: signed drop test 0.9976311356937564, absolute drop test 0.9976 (magnitude identical to the +1 case: True)
dc gain -1: signed drop test inf, absolute drop test 0.9976 (magnitude identical to the +1 case: True)
Driving the real library:
first-order: bandwidth pos 0.9976 (correct), neg inf (brute-force true 0.9976)
magnitude responses identical: True (dc gain pos 1.0, neg -1.0)
second-order: pos 2.0178, neg inf (brute-force true 2.0178)
dbdrop=-6: neg bandwidth inf (inf: True)
The systems 1/(s+1) and -1/(s+1) have the same magnitude response, 0.894, 0.707, 0.447 at
w = 0.5, 1, 2, so they must have the same bandwidth, but the real bandwidth returns 0.9976 for the
positive one and infinity for the negative one. The base case isolates the fault: the only difference
between the two systems is the sign of the numerator, and the correctly written bisection uses
abs(dcgain). A second-order system with negative DC gain likewise returns infinity against a finite
brute-force bandwidth of 2.0178, and the fault is not specific to the default drop, breaking at
dbdrop=-6 as well.
bandwidth runs with the default drop of three decibels and standard SISO input, and negative-DC-gain
systems are the ordinary case for any sign-inverted plant or controller. The failure is silent: infinity is
returned with no warning, documented as the gain not dropping for any frequency, which is false. The error
is a signed DC gain where the magnitude is intended, not floating-point round-off, and the bisection in the
same function uses the absolute value. The fix is np.abs(dcgain) in the drop test.
excerpt.py: the drop test and the bisection quoted with the flags that name the fault, and the correct absolute-value drop test.bandwidth.py: the drop test with the signed or absolute DC gain over a magnitude response, so the signed test returns infinity on a negative DC gain and the absolute test returns the correct bandwidth.consequence.py: the realcontrol.bandwidthreturning infinity on a negative DC gain, the identical magnitude responses, the second-order case, and the non-default drop.test_bwsign.py: the model signed test is infinite and the absolute test is correct while a positive DC gain agrees, the real negative-DC-gain bandwidth is infinite against a finite true value, the magnitudes are identical, and the second-order and non-default cases break.
python bandwidth.py
python consequence.py
python test_bwsign.py
The drop test is quoted from current main; the bandwidths are produced by the real control.bandwidth.
The fix is to use the absolute DC gain in the drop test.