Skip to content

BugFix: DC gain for discrete-time systems #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,11 +612,14 @@ def dcgain(self):
at the origin
"""
try:
gain = np.asarray(self.D -
self.C.dot(np.linalg.solve(self.A, self.B)))
if self.isctime():
gain = np.asarray(self.D -
self.C.dot(np.linalg.solve(self.A, self.B)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is off alignment here. This is a small matter of style; OK for someone to fix it directly on master branch if there are not other issues with this PR.

else:
gain = self.horner(1)
except LinAlgError:
# zero eigenvalue: singular matrix
return np.nan
# eigenvalue at DC
gain = np.tile(np.nan,(self.outputs,self.inputs))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line deletion here does not seem to be relevant to the other changes in this commit.

return np.squeeze(gain)


Expand Down
40 changes: 39 additions & 1 deletion control/tests/statesp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ def testArrayAccessSS(self):

assert sys1.dt == sys1_11.dt

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

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

def test_dcgain_discr(self):
"""Test DC gain for discrete-time state-space systems"""
# static gain
sys = StateSpace([], [], [], 2, True)
np.testing.assert_equal(sys.dcgain(), 2)

# averaging filter
sys = StateSpace(0.5, 0.5, 1, 0, True)
np.testing.assert_almost_equal(sys.dcgain(), 1)

# differencer
sys = StateSpace(0, 1, -1, 1, True)
np.testing.assert_equal(sys.dcgain(), 0)

# summer
sys = StateSpace(1, 1, 1, 0, True)
np.testing.assert_equal(sys.dcgain(), np.nan)

def test_dcgain_integrator(self):
"""DC gain when eigenvalue at DC returns appropriately sized array of nan"""
# the SISO case is also tested in test_dc_gain_{cont,discr}
import itertools
# iterate over input and output sizes, and continuous (dt=None) and discrete (dt=True) time
for inputs,outputs,dt in itertools.product(range(1,6),range(1,6),[None,True]):
states = max(inputs,outputs)

# a matrix that is singular at DC, and has no "useless" states as in _remove_useless_states
a = np.triu(np.tile(2,(states,states)))
# eigenvalues all +2, except for ...
a[0,0] = 0 if dt is None else 1
b = np.eye(max(inputs,states))[:states,:inputs]
c = np.eye(max(outputs,states))[:outputs,:states]
d = np.zeros((outputs,inputs))
sys = StateSpace(a,b,c,d,dt)
dc = np.squeeze(np.tile(np.nan,(outputs,inputs)))
np.testing.assert_array_equal(dc, sys.dcgain())


def test_scalarStaticGain(self):
"""Regression: can we create a scalar static gain?"""
Expand Down
23 changes: 22 additions & 1 deletion control/tests/xferfcn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,8 @@ def testMatrixMult(self):
np.testing.assert_array_almost_equal(H.num[1][0], H2.num[0][0])
np.testing.assert_array_almost_equal(H.den[1][0], H2.den[0][0])

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

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

def test_dcgain_discr(self):
"""Test DC gain for discrete-time transfer functions"""
# static gain
sys = TransferFunction(6, 3, True)
np.testing.assert_equal(sys.dcgain(), 2)

# averaging filter
sys = TransferFunction(0.5, [1, -0.5], True)
np.testing.assert_almost_equal(sys.dcgain(), 1)

# differencer
sys = TransferFunction(1, [1, -1], True)
np.testing.assert_equal(sys.dcgain(), np.inf)

# summer
# causes a RuntimeWarning due to the divide by zero
sys = TransferFunction([1,-1], [1], True)
np.testing.assert_equal(sys.dcgain(), 0)


def suite():
return unittest.TestLoader().loadTestsFromTestCase(TestXferFcn)

Expand Down
12 changes: 11 additions & 1 deletion control/xferfcn.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,13 +945,23 @@ def sample(self, Ts, method='zoh', alpha=None):
def dcgain(self):
"""Return the zero-frequency (or DC) gain

For a transfer function G(s), the DC gain is G(0)
For a continous-time transfer function G(s), the DC gain is G(0)
For a discrete-time transfer function G(z), the DC gain is G(1)

Returns
-------
gain : ndarray
The zero-frequency gain
"""
if self.isctime():
return self._dcgain_cont()
else:
return self(1)

def _dcgain_cont(self):
"""_dcgain_cont() -> DC gain as matrix or scalar

Special cased evaluation at 0 for continuous-time systems"""
gain = np.empty((self.outputs, self.inputs), dtype=float)
for i in range(self.outputs):
for j in range(self.inputs):
Expand Down