Skip to content

Commit 78a03c2

Browse files
authored
Merge pull request #126 from roryyorke/rory/discr-time-dcgain-fix
BugFix: DC gain for discrete-time systems
2 parents 32f13bc + 310f580 commit 78a03c2

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)