Skip to content

Commit 310f580

Browse files
committed
BugFix: DC gain for discrete-time systems
For discrete time systems the DC gain is found at z=1; change dcgain method in TransferFunction and StateSpace classes for this. Added tests for static gain, low-pass filter, differencer and summer for both classes. If the StateSpace DC gain computation fails due to singularity, return an array of NaNs of size (output,input), rather than scalar NaN. Added separate test for this for continuous- and discrete-time cases.
1 parent 32f13bc commit 310f580

File tree

4 files changed

+79
-7
lines changed

4 files changed

+79
-7
lines changed

control/statesp.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,14 @@ def dcgain(self):
612612
at the origin
613613
"""
614614
try:
615-
gain = np.asarray(self.D -
616-
self.C.dot(np.linalg.solve(self.A, self.B)))
615+
if self.isctime():
616+
gain = np.asarray(self.D -
617+
self.C.dot(np.linalg.solve(self.A, self.B)))
618+
else:
619+
gain = self.horner(1)
617620
except LinAlgError:
618-
# zero eigenvalue: singular matrix
619-
return np.nan
621+
# eigenvalue at DC
622+
gain = np.tile(np.nan,(self.outputs,self.inputs))
620623
return np.squeeze(gain)
621624

622625

control/tests/statesp_test.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ def testArrayAccessSS(self):
228228

229229
assert sys1.dt == sys1_11.dt
230230

231-
def test_dcgain(self):
231+
def test_dcgain_cont(self):
232+
"""Test DC gain for continuous-time state-space systems"""
232233
sys = StateSpace(-2.,6.,5.,0)
233234
np.testing.assert_equal(sys.dcgain(), 15.)
234235

@@ -239,6 +240,43 @@ def test_dcgain(self):
239240
sys3 = StateSpace(0., 1., 1., 0.)
240241
np.testing.assert_equal(sys3.dcgain(), np.nan)
241242

243+
def test_dcgain_discr(self):
244+
"""Test DC gain for discrete-time state-space systems"""
245+
# static gain
246+
sys = StateSpace([], [], [], 2, True)
247+
np.testing.assert_equal(sys.dcgain(), 2)
248+
249+
# averaging filter
250+
sys = StateSpace(0.5, 0.5, 1, 0, True)
251+
np.testing.assert_almost_equal(sys.dcgain(), 1)
252+
253+
# differencer
254+
sys = StateSpace(0, 1, -1, 1, True)
255+
np.testing.assert_equal(sys.dcgain(), 0)
256+
257+
# summer
258+
sys = StateSpace(1, 1, 1, 0, True)
259+
np.testing.assert_equal(sys.dcgain(), np.nan)
260+
261+
def test_dcgain_integrator(self):
262+
"""DC gain when eigenvalue at DC returns appropriately sized array of nan"""
263+
# the SISO case is also tested in test_dc_gain_{cont,discr}
264+
import itertools
265+
# iterate over input and output sizes, and continuous (dt=None) and discrete (dt=True) time
266+
for inputs,outputs,dt in itertools.product(range(1,6),range(1,6),[None,True]):
267+
states = max(inputs,outputs)
268+
269+
# a matrix that is singular at DC, and has no "useless" states as in _remove_useless_states
270+
a = np.triu(np.tile(2,(states,states)))
271+
# eigenvalues all +2, except for ...
272+
a[0,0] = 0 if dt is None else 1
273+
b = np.eye(max(inputs,states))[:states,:inputs]
274+
c = np.eye(max(outputs,states))[:outputs,:states]
275+
d = np.zeros((outputs,inputs))
276+
sys = StateSpace(a,b,c,d,dt)
277+
dc = np.squeeze(np.tile(np.nan,(outputs,inputs)))
278+
np.testing.assert_array_equal(dc, sys.dcgain())
279+
242280

243281
def test_scalarStaticGain(self):
244282
"""Regression: can we create a scalar static gain?"""

control/tests/xferfcn_test.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,8 @@ def testMatrixMult(self):
524524
np.testing.assert_array_almost_equal(H.num[1][0], H2.num[0][0])
525525
np.testing.assert_array_almost_equal(H.den[1][0], H2.den[0][0])
526526

527-
def test_dcgain(self):
527+
def test_dcgain_cont(self):
528+
"""Test DC gain for continuous-time transfer functions"""
528529
sys = TransferFunction(6, 3)
529530
np.testing.assert_equal(sys.dcgain(), 2)
530531

@@ -540,6 +541,26 @@ def test_dcgain(self):
540541
expected = [[5, 7, 11], [2, 2, 2]]
541542
np.testing.assert_array_equal(sys4.dcgain(), expected)
542543

544+
def test_dcgain_discr(self):
545+
"""Test DC gain for discrete-time transfer functions"""
546+
# static gain
547+
sys = TransferFunction(6, 3, True)
548+
np.testing.assert_equal(sys.dcgain(), 2)
549+
550+
# averaging filter
551+
sys = TransferFunction(0.5, [1, -0.5], True)
552+
np.testing.assert_almost_equal(sys.dcgain(), 1)
553+
554+
# differencer
555+
sys = TransferFunction(1, [1, -1], True)
556+
np.testing.assert_equal(sys.dcgain(), np.inf)
557+
558+
# summer
559+
# causes a RuntimeWarning due to the divide by zero
560+
sys = TransferFunction([1,-1], [1], True)
561+
np.testing.assert_equal(sys.dcgain(), 0)
562+
563+
543564
def suite():
544565
return unittest.TestLoader().loadTestsFromTestCase(TestXferFcn)
545566

control/xferfcn.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,13 +945,23 @@ def sample(self, Ts, method='zoh', alpha=None):
945945
def dcgain(self):
946946
"""Return the zero-frequency (or DC) gain
947947
948-
For a transfer function G(s), the DC gain is G(0)
948+
For a continous-time transfer function G(s), the DC gain is G(0)
949+
For a discrete-time transfer function G(z), the DC gain is G(1)
949950
950951
Returns
951952
-------
952953
gain : ndarray
953954
The zero-frequency gain
954955
"""
956+
if self.isctime():
957+
return self._dcgain_cont()
958+
else:
959+
return self(1)
960+
961+
def _dcgain_cont(self):
962+
"""_dcgain_cont() -> DC gain as matrix or scalar
963+
964+
Special cased evaluation at 0 for continuous-time systems"""
955965
gain = np.empty((self.outputs, self.inputs), dtype=float)
956966
for i in range(self.outputs):
957967
for j in range(self.inputs):

0 commit comments

Comments
 (0)