Skip to content

Commit 326d7b3

Browse files
committed
- additional documentation for bode and rlocus
- epsilon for determining whether a margin is calculated for omega=0 - fixed the minreal test
1 parent 13441f9 commit 326d7b3

File tree

3 files changed

+39
-21
lines changed

3 files changed

+39
-21
lines changed

src/margins.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _polysqr(pol):
8080
# idea for the frequency data solution copied/adapted from
8181
# https://github.com/alchemyst/Skogestad-Python/blob/master/BODE.py
8282
# Rene van Paassen <rene.vanpaassen@gmail.com>
83-
def stability_margins(sysdata, deg=True, returnall=False):
83+
def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
8484
"""Calculate gain, phase and stability margins and associated
8585
crossover frequencies.
8686
@@ -101,6 +101,9 @@ def stability_margins(sysdata, deg=True, returnall=False):
101101
returnall=False: boolean
102102
If true, return all margins found. Note that for frequency data or
103103
FRD systems, only one margin is found and returned.
104+
epsw=1e-12: float
105+
frequencies below this value are considered static gain, and not
106+
returned as margin.
104107
105108
Returns
106109
-------
@@ -113,7 +116,7 @@ def stability_margins(sysdata, deg=True, returnall=False):
113116
When requesting all margins, the return values are array_like,
114117
and all margins are returns for linear systems not equal to FRD
115118
"""
116-
119+
117120
try:
118121
if isinstance(sysdata, frdata.FRD):
119122
sys = frdata.FRD(sysdata, smooth=True)
@@ -143,14 +146,14 @@ def stability_margins(sysdata, deg=True, returnall=False):
143146
# test imaginary part of tf == 0, for phase crossover/gain margins
144147
test_w_180 = np.polyadd(np.polymul(inum, rden), np.polymul(rnum, -iden))
145148
w_180 = np.roots(test_w_180)
146-
w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 > 0.)])
149+
w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 > epsw)])
147150
w_180.sort()
148151

149152
# test magnitude is 1 for gain crossover/phase margins
150153
test_wc = np.polysub(np.polyadd(_polysqr(rnum), _polysqr(inum)),
151154
np.polyadd(_polysqr(rden), _polysqr(iden)))
152155
wc = np.roots(test_wc)
153-
wc = np.real(wc[(np.imag(wc) == 0) * (wc > 0.)])
156+
wc = np.real(wc[(np.imag(wc) == 0) * (wc > epsw)])
154157
wc.sort()
155158

156159
# stability margin was a bitch to elaborate, relies on magnitude to
@@ -168,7 +171,7 @@ def stability_margins(sysdata, deg=True, returnall=False):
168171

169172
# and find the value of the 2nd derivative there, needs to be positive
170173
wstabplus = np.polyval(np.polyder(test_wstab), wstab)
171-
wstab = np.real(wstab[(np.imag(wstab) == 0) * (wstab > 0.) *
174+
wstab = np.real(wstab[(np.imag(wstab) == 0) * (wstab > epsw) *
172175
(np.abs(wstabplus) > 0.)])
173176
wstab.sort()
174177

src/matlab.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,31 @@ def freqresp(sys, omega):
991991
def bode(*args, **keywords):
992992
"""Bode plot of the frequency response
993993
994+
Plots a bode gain and phase diagram
995+
996+
Parameters
997+
----------
998+
sys : Lti, or list of Lti
999+
System for which the Bode response is plotted and give. Optionally
1000+
a list of systems can be entered, or several systems can be
1001+
specified (i.e. several parameters). The sys arguments may also be
1002+
interspersed with format strings. A frequency argument (array_like)
1003+
may also be added, some examples:
1004+
* >>> bode(sys, w) # one system, freq vector
1005+
* >>> bode(sys1, sys2, ..., sysN) # several systems
1006+
* >>> bode(sys1, sys2, ..., sysN, w)
1007+
* >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # + plot formats
1008+
omega: freq_range
1009+
Range of frequencies in rad/s
1010+
dB : boolean
1011+
If True, plot result in dB
1012+
Hz : boolean
1013+
If True, plot frequency in Hz (omega must be provided in rad/sec)
1014+
deg : boolean
1015+
If True, return phase in degrees (else radians)
1016+
Plot : boolean
1017+
If True, plot magnitude and phase
1018+
9941019
Examples
9951020
--------
9961021
>>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
@@ -1064,6 +1089,10 @@ def ngrid():
10641089
def rlocus(sys, klist = None, **keywords):
10651090
"""Root locus plot
10661091
1092+
The root-locus plot has a callback function that prints pole location,
1093+
gain and damping to the Python consol on mouseclicks on the root-locus
1094+
graph.
1095+
10671096
Parameters
10681097
----------
10691098
sys: StateSpace or TransferFunction

tests/minreal_test.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,8 @@ def testMinrealBrute(self):
5858
# the original rss, but not the balanced one
5959
if n < 6:
6060
raise e
61-
#if n > 6:
62-
# continue
63-
#print n, m, p
64-
#print s
65-
#print sr
66-
#print ht1
67-
#print ht2
68-
#print ht1.pole(), ht1.zero()
69-
#print ht2.pole(), ht2.zero()
70-
71-
#np.testing.assert_array_almost_equal(
72-
# ht1.num[0][0], ht2.num[0][0])
73-
#np.testing.assert_array_almost_equal(
74-
# ht1.den[0][0], ht2.den[0][0])
7561

76-
self.assertEqual(self.nreductions, 7)
62+
self.assertEqual(self.nreductions, 2)
7763

7864
def testMinrealSS(self):
7965
"""Test a minreal model reduction"""
@@ -106,7 +92,7 @@ def testMinrealtf(self):
10692
np.testing.assert_array_almost_equal(hm.den[0][0], hr.den[0][0])
10793

10894
def suite():
109-
return unittest.TestLoader().loadTestsFromTestCase(TestStateSpace)
95+
return unittest.TestLoader().loadTestsFromTestCase(TestMinreal)
11096

11197

11298
if __name__ == "__main__":

0 commit comments

Comments
 (0)