Skip to content

Commit 6caad9a

Browse files
committed
improving the margin calculation & choice of which margin to
present, as much as practical in line with Matlab and > A note on the Gain and Phase Margin Concepts > Journal of Control and Systems Engineering, Yazdan Bavafi-Toosi, > Dec 205, vol 3 iss 1, pp 51-59
1 parent 9abfee5 commit 6caad9a

2 files changed

Lines changed: 44 additions & 15 deletions

File tree

control/margins.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,15 @@ def stability_margins(sysdata, returnall=False, epsw=0.0):
157157

158158
# first remove imaginary and negative frequencies, epsw removes the
159159
# "0" frequency for type-2 systems
160-
w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 > epsw)])
160+
w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 >= epsw)])
161161
#print ('2:w_180', w_180)
162162

163163
# evaluate response at remaining frequencies, to test for phase 180 vs 0
164-
resp_w_180 = np.real(np.polyval(sys.num[0][0], 1.j*w_180) /
165-
np.polyval(sys.den[0][0], 1.j*w_180))
166-
164+
with np.errstate(all='ignore'):
165+
resp_w_180 = np.real(
166+
np.polyval(sys.num[0][0], 1.j*w_180) /
167+
np.polyval(sys.den[0][0], 1.j*w_180))
168+
167169
# only keep frequencies where the negative real axis is crossed
168170
w_180 = w_180[np.real(resp_w_180) <= 0.0]
169171

@@ -252,20 +254,27 @@ def dstab(w):
252254

253255
# margins, as iterables, converted frdata and xferfcn calculations to
254256
# vector for this
255-
gain_w_180 = np.abs(sys.evalfr(w_180)[0][0])
256-
GM = np.where(gain_w_180 == 0.0, float('inf'), 1.0/gain_w_180)
257+
with np.errstate(all='ignore'):
258+
gain_w_180 = np.abs(sys.evalfr(w_180)[0][0])
259+
GM = 1.0/gain_w_180
257260
SM = np.abs(sys.evalfr(wstab)[0][0]+1)
258261
PM = np.remainder(np.angle(sys.evalfr(wc)[0][0], deg=True), 360.0) - 180.0
259262

260263
if returnall:
261264
return GM, PM, SM, w_180, wc, wstab
262265
else:
266+
if GM.shape[0]:
267+
with np.errstate(all='ignore'):
268+
gmidx = np.where(np.abs(np.log(GM)) ==
269+
np.min(np.abs(np.log(GM))))
270+
if PM.shape[0]:
271+
pmidx = np.where(np.abs(PM) == np.amin(np.abs(PM)))[0]
263272
return (
264-
(not GM.shape[0] and float('inf')) or np.amin(GM),
265-
(not PM.shape[0] and float('inf')) or np.amin(PM),
273+
(not GM.shape[0] and float('inf')) or GM[gmidx][0],
274+
(not PM.shape[0] and float('inf')) or PM[pmidx][0],
266275
(not SM.shape[0] and float('inf')) or np.amin(SM),
267-
(not w_180.shape[0] and float('nan')) or w_180[GM==np.amin(GM)][0],
268-
(not wc.shape[0] and float('nan')) or wc[PM==np.amin(PM)][0],
276+
(not w_180.shape[0] and float('nan')) or w_180[gmidx][0],
277+
(not wc.shape[0] and float('nan')) or wc[pmidx][0],
269278
(not wstab.shape[0] and float('nan')) or wstab[SM==np.amin(SM)][0])
270279

271280

control/tests/margin_test.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ def setUp(self):
8585
dict(sys='type3', K=1.0, digits=3, result=(
8686
0.0626, 37.1748, 0.1119, 0.7951)),
8787
)
88-
88+
89+
8990
# from "A note on the Gain and Phase Margin Concepts
9091
# Journal of Control and Systems Engineering, Yazdan Bavafi-Toosi,
9192
# Dec 205, vol 3 iss 1, pp 51-59
@@ -129,8 +130,13 @@ def setUp(self):
129130
self.yazdan['example25b'] = self.yazdan['example25a']*100
130131
self.yazdan['example22'] = self.yazdan['example21']*(s**2 - 2*s + 401)
131132
self.ymargin = (
132-
dict(sys='example21', K=1.0, result=(
133-
0.01, 345.43, 0., 0, 0, 0.01)))
133+
dict(sys='example21', K=1.0, digits=2, result=(
134+
0.0100, -14.5640, 0, 0.0022)),
135+
dict(sys='example21', K=1000.0, digits=2, result=(
136+
0.1793, 22.5215, 0.0243, 0.0630)),
137+
dict(sys='example21', K=5000.0, digits=4, result=(
138+
4.5596, 21.2101, 0.4385, 0.1868)),
139+
)
134140

135141
self.yallmargin = (
136142
dict(sys='example21', K=1.0, result=(
@@ -264,15 +270,29 @@ def test_nocross(self):
264270
out3b = stability_margins(FRD(h3, omega))
265271

266272
def test_zmore_margin(self):
273+
print("""
274+
warning, Matlab gives different values (0 and 0) for gain
275+
margin of the following system:
276+
{:s}
277+
python-control gives inf
278+
difficult to argue which is right? Special case or different
279+
approach?
280+
""".format(str(self.types['type2'])))
281+
267282
sdict = self.tmargin[0]
268283
for test in self.tmargin[1:]:
269284
res = margin(sdict[test['sys']]*test['K'])
270285
print("more margin {}\n".format(sdict[test['sys']]),
271286
res, '\n', test['result'])
272287
assert_array_almost_equal(
273288
res, test['result'], test['digits'])
274-
275-
289+
sdict = self.yazdan
290+
for test in self.ymargin:
291+
res = margin(sdict[test['sys']]*test['K'])
292+
print("more margin {}\n".format(sdict[test['sys']]),
293+
res, '\n', test['result'])
294+
assert_array_almost_equal(
295+
res, test['result'], test['digits'])
276296

277297
def test_suite():
278298
return unittest.TestLoader().loadTestsFromTestCase(TestMargin)

0 commit comments

Comments
 (0)