Skip to content

Commit c51d665

Browse files
committed
Implement dcgain outside of matlab module
The dcgain function is now implemented as a method of the StateSpace and TransferFunction classes. There is also a routine lti.dcgain(sys) that simply calls sys.dcgain(). Note that the DC gain is now returned as a numpy array (not a matrix), and furthermore, single dimensions are removed with np.squeeze. This is a change from the previous implementation in matlab.dcgain(). The new convention makes it easier to deal with SISO systems (a common case), since instead of referring to a gain as g[0][0], you can refer to it simply as g. This seems preferable to me than returning a np.matrix. (The new convention is also more consistent with Matlab.)
1 parent c2bf84d commit c51d665

10 files changed

Lines changed: 189 additions & 73 deletions

File tree

control/lti.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from numpy import absolute, real
1616

1717
__all__ = ['issiso', 'timebase', 'timebaseEqual', 'isdtime', 'isctime',
18-
'pole', 'zero', 'damp', 'evalfr', 'freqresp']
18+
'pole', 'zero', 'damp', 'evalfr', 'freqresp', 'dcgain']
1919

2020
class LTI:
2121
"""LTI is a parent class to linear time-invariant (LTI) system objects.
@@ -89,6 +89,11 @@ def damp(self):
8989
Z = -real(poles)/wn
9090
return wn, Z, poles
9191

92+
def dcgain(self):
93+
"""Return the zero-frequency gain"""
94+
raise NotImplementedError("dcgain not implemented for %s objects" %
95+
str(self.__class__))
96+
9297
# Test to see if a system is SISO
9398
def issiso(sys, strict=False):
9499
if isinstance(sys, (int, float, complex)) and not strict:
@@ -215,11 +220,8 @@ def pole(sys):
215220
See Also
216221
--------
217222
zero
218-
219-
Notes
220-
-----
221-
This function is a wrapper for StateSpace.pole and
222-
TransferFunction.pole.
223+
TransferFunction.pole
224+
StateSpace.pole
223225
224226
"""
225227

@@ -243,16 +245,13 @@ def zero(sys):
243245
Raises
244246
------
245247
NotImplementedError
246-
when called on a TransferFunction object or a MIMO StateSpace object
248+
when called on a MIMO system
247249
248250
See Also
249251
--------
250252
pole
251-
252-
Notes
253-
-----
254-
This function is a wrapper for StateSpace.zero and
255-
TransferFunction.zero.
253+
StateSpace.zero
254+
TransferFunction.zero
256255
257256
"""
258257

@@ -391,3 +390,14 @@ def freqresp(sys, omega):
391390
"""
392391

393392
return sys.freqresp(omega)
393+
394+
def dcgain(sys):
395+
"""Return the zero-frequency (or DC) gain of the given system
396+
397+
Returns
398+
-------
399+
gain : ndarray
400+
The zero-frequency gain, or np.nan if the system has a pole
401+
at the origin
402+
"""
403+
return sys.dcgain()

control/matlab/__init__.py

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090

9191
# Import functions specific to Matlab compatibility package
9292
from .timeresp import *
93-
from .plots import *
93+
from .wrappers import *
9494

9595
__doc__ += r"""
9696
The following tables give an overview of the module ``control.matlab``.
@@ -382,57 +382,3 @@
382382
== ========================== ============================================
383383
384384
"""
385-
386-
def dcgain(*args):
387-
'''
388-
Compute the gain of the system in steady state.
389-
390-
The function takes either 1, 2, 3, or 4 parameters:
391-
392-
Parameters
393-
----------
394-
A, B, C, D: array-like
395-
A linear system in state space form.
396-
Z, P, k: array-like, array-like, number
397-
A linear system in zero, pole, gain form.
398-
num, den: array-like
399-
A linear system in transfer function form.
400-
sys: LTI (StateSpace or TransferFunction)
401-
A linear system object.
402-
403-
Returns
404-
-------
405-
gain: matrix
406-
The gain of each output versus each input:
407-
:math:`y = gain \cdot u`
408-
409-
Notes
410-
-----
411-
This function is only useful for systems with invertible system
412-
matrix ``A``.
413-
414-
All systems are first converted to state space form. The function then
415-
computes:
416-
417-
.. math:: gain = - C \cdot A^{-1} \cdot B + D
418-
'''
419-
#Convert the parameters to state space form
420-
if len(args) == 4:
421-
A, B, C, D = args
422-
sys = ss(A, B, C, D)
423-
elif len(args) == 3:
424-
Z, P, k = args
425-
A, B, C, D = zpk2ss(Z, P, k)
426-
sys = ss(A, B, C, D)
427-
elif len(args) == 2:
428-
num, den = args
429-
sys = tf2ss(num, den)
430-
elif len(args) == 1:
431-
sys, = args
432-
sys = ss(sys)
433-
else:
434-
raise ValueError("Function ``dcgain`` needs either 1, 2, 3 or 4 "
435-
"arguments.")
436-
#gain = - C * A**-1 * B + D
437-
gain = sys.D - sys.C * sys.A.I * sys.B
438-
return gain
Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
"""
2-
Plotting routines for the Matlab compatibility module
2+
Wrappers for the Matlab compatibility module
33
"""
44

55
import numpy as np
6+
from ..statesp import ss
7+
from ..xferfcn import tf
8+
from scipy.signal import zpk2tf
69

7-
__all__ = ['bode', 'ngrid']
10+
__all__ = ['bode', 'ngrid', 'dcgain']
811

912
def bode(*args, **keywords):
1013
"""Bode plot of the frequency response
@@ -103,3 +106,54 @@ def bode(*args, **keywords):
103106
def ngrid():
104107
return nichols_grid()
105108
ngrid.__doc__ = nichols_grid.__doc__
109+
110+
def dcgain(*args):
111+
'''
112+
Compute the gain of the system in steady state.
113+
114+
The function takes either 1, 2, 3, or 4 parameters:
115+
116+
Parameters
117+
----------
118+
A, B, C, D: array-like
119+
A linear system in state space form.
120+
Z, P, k: array-like, array-like, number
121+
A linear system in zero, pole, gain form.
122+
num, den: array-like
123+
A linear system in transfer function form.
124+
sys: LTI (StateSpace or TransferFunction)
125+
A linear system object.
126+
127+
Returns
128+
-------
129+
gain: ndarray
130+
The gain of each output versus each input:
131+
:math:`y = gain \cdot u`
132+
133+
Notes
134+
-----
135+
This function is only useful for systems with invertible system
136+
matrix ``A``.
137+
138+
All systems are first converted to state space form. The function then
139+
computes:
140+
141+
.. math:: gain = - C \cdot A^{-1} \cdot B + D
142+
'''
143+
#Convert the parameters to state space form
144+
if len(args) == 4:
145+
A, B, C, D = args
146+
return ss(A, B, C, D).dcgain()
147+
elif len(args) == 3:
148+
Z, P, k = args
149+
num, den = zpk2tf(Z, P, k)
150+
return tf(num, den).dcgain()
151+
elif len(args) == 2:
152+
num, den = args
153+
return tf(num, den).dcgain()
154+
elif len(args) == 1:
155+
sys, = args
156+
return sys.dcgain()
157+
else:
158+
raise ValueError("Function ``dcgain`` needs either 1, 2, 3 or 4 "
159+
"arguments.")

control/statesp.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
$Id$
5151
"""
5252

53+
import numpy as np
5354
from numpy import all, angle, any, array, asarray, concatenate, cos, delete, \
5455
dot, empty, exp, eye, matrix, ones, pi, poly, poly1d, roots, shape, sin, \
5556
zeros, squeeze
@@ -591,6 +592,27 @@ def sample(self, Ts, method='zoh', alpha=None):
591592
Ad, Bd, C, D, dt = cont2discrete(sys, Ts, method, alpha)
592593
return StateSpace(Ad, Bd, C, D, dt)
593594

595+
def dcgain(self):
596+
"""Return the zero-frequency gain
597+
598+
The zero-frequency gain of a state-space system is given by:
599+
600+
.. math: G(0) = - C A^{-1} B + D
601+
602+
Returns
603+
-------
604+
gain : ndarray
605+
The zero-frequency gain, or np.nan if the system has a pole
606+
at the origin
607+
"""
608+
try:
609+
gain = np.asarray(self.D -
610+
self.C.dot(np.linalg.solve(self.A, self.B)))
611+
except LinAlgError:
612+
# zero eigenvalue: singular matrix
613+
return np.nan
614+
return np.squeeze(gain)
615+
594616

595617
# TODO: add discrete time check
596618
def _convertToStateSpace(sys, **kw):

control/tests/lti_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env python
2+
3+
import unittest
4+
import numpy as np
5+
from control.lti import *
6+
from control.xferfcn import tf
7+
import numpy as np
8+
9+
class TestUtils(unittest.TestCase):
10+
def test_pole(self):
11+
sys = tf(126, [-1, 42])
12+
np.testing.assert_equal(sys.pole(), 42)
13+
np.testing.assert_equal(pole(sys), 42)
14+
15+
def test_zero(self):
16+
sys = tf([-1, 42], [1, 10])
17+
np.testing.assert_equal(sys.zero(), 42)
18+
np.testing.assert_equal(zero(sys), 42)
19+
20+
def test_damp(self):
21+
zeta = 0.1
22+
wn = 42
23+
p = -wn * zeta + 1j * wn * np.sqrt(1 - zeta**2)
24+
sys = tf(1, [1, 2 * zeta * wn, wn**2])
25+
expected = ([wn, wn], [zeta, zeta], [p, p.conjugate()])
26+
np.testing.assert_equal(sys.damp(), expected)
27+
np.testing.assert_equal(damp(sys), expected)
28+
29+
def test_dcgain(self):
30+
sys = tf(84, [1, 2])
31+
np.testing.assert_equal(sys.dcgain(), 42)
32+
np.testing.assert_equal(dcgain(sys), 42)

control/tests/matlab_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def testDcgain(self):
283283

284284
#All gain values must be approximately equal to the known gain
285285
np.testing.assert_array_almost_equal(
286-
[gain_abcd[0,0], gain_zpk[0,0], gain_numden[0,0], gain_sys_ss[0,0],
286+
[gain_abcd, gain_zpk, gain_numden, gain_sys_ss,
287287
gain_sim],
288288
[59, 59, 59, 59, 59])
289289

control/tests/statesp_test.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from control.statesp import StateSpace, _convertToStateSpace
1111
from control.xferfcn import TransferFunction
1212

13-
1413
class TestStateSpace(unittest.TestCase):
1514
"""Tests for the StateSpace class."""
1615

@@ -224,6 +223,17 @@ def testArrayAccessSS(self):
224223

225224
assert sys1.dt == sys1_11.dt
226225

226+
def test_dcgain(self):
227+
sys = StateSpace(-2.,6.,5.,0)
228+
np.testing.assert_equal(sys.dcgain(), 15.)
229+
230+
sys2 = StateSpace(-2, [6., 4.], [[5.],[7.],[11]], np.zeros((3,2)))
231+
expected = np.array([[15., 10.], [21., 14.], [33., 22.]])
232+
np.testing.assert_array_equal(sys2.dcgain(), expected)
233+
234+
sys3 = StateSpace(0., 1., 1., 0.)
235+
np.testing.assert_equal(sys3.dcgain(), np.nan)
236+
227237
class TestRss(unittest.TestCase):
228238
"""These are tests for the proper functionality of statesp.rss."""
229239

control/tests/test_control_matlab.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ def test_dcgain_2(self):
119119
# print('gain_sim:', gain_sim)
120120

121121
#All gain values must be approximately equal to the known gain
122-
assert_array_almost_equal([gain_abcd[0,0], gain_zpk[0,0],
123-
gain_numden[0,0], gain_sys_ss[0,0], gain_sim],
122+
assert_array_almost_equal([gain_abcd, gain_zpk,
123+
gain_numden, gain_sys_ss, gain_sim],
124124
[0.026948, 0.026948, 0.026948, 0.026948,
125125
0.026948],
126126
decimal=6)

control/tests/xferfcn_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,22 @@ def testMatrixMult(self):
510510
np.testing.assert_array_almost_equal(H.num[1][0], H2.num[0][0])
511511
np.testing.assert_array_almost_equal(H.den[1][0], H2.den[0][0])
512512

513+
def test_dcgain(self):
514+
sys = TransferFunction(6, 3)
515+
np.testing.assert_equal(sys.dcgain(), 2)
516+
517+
sys2 = TransferFunction(6, [1, 3])
518+
np.testing.assert_equal(sys2.dcgain(), 2)
519+
520+
sys3 = TransferFunction(6, [1, 0])
521+
np.testing.assert_equal(sys3.dcgain(), np.inf)
522+
523+
num = [[[15], [21], [33]], [[10], [14], [22]]]
524+
den = [[[1, 3], [2, 3], [3, 3]], [[1, 5], [2, 7], [3, 11]]]
525+
sys4 = TransferFunction(num, den)
526+
expected = [[5, 7, 11], [2, 2, 2]]
527+
np.testing.assert_array_equal(sys4.dcgain(), expected)
528+
513529
def suite():
514530
return unittest.TestLoader().loadTestsFromTestCase(TestXferFcn)
515531

control/xferfcn.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,32 @@ def sample(self, Ts, method='zoh', alpha=None):
943943
numd, dend, dt = cont2discrete(sys, Ts, method, alpha)
944944
return TransferFunction(numd[0,:], dend, dt)
945945

946+
def dcgain(self):
947+
"""Return the zero-frequency (or DC) gain
948+
949+
For a transfer function G(s), the DC gain is G(0)
950+
951+
Returns
952+
-------
953+
gain : ndarray
954+
The zero-frequency gain
955+
"""
956+
gain = np.empty((self.outputs, self.inputs), dtype=float)
957+
for i in range(self.outputs):
958+
for j in range(self.inputs):
959+
num = self.num[i][j][-1]
960+
den = self.den[i][j][-1]
961+
if den:
962+
gain[i][j] = num / den
963+
else:
964+
if num:
965+
# numerator nonzero: infinite gain
966+
gain[i][j] = np.inf
967+
else:
968+
# numerator is zero too: give up
969+
gain[i][j] = np.nan
970+
return np.squeeze(gain)
971+
946972
# c2d function contributed by Benjamin White, Oct 2012
947973
def _c2dmatched(sysC, Ts):
948974
# Pole-zero match method of continuous to discrete time conversion

0 commit comments

Comments
 (0)