Skip to content

Commit 8d22279

Browse files
committed
and even more margin fixing and testing, now correct for systems with
differentiating s, and return NAN for phase crossover frequency when no gain margin found
1 parent 6caad9a commit 8d22279

2 files changed

Lines changed: 38 additions & 12 deletions

File tree

control/margins.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ def _polysqr(pol):
8787
# RvP, July 8, 2015, augmented to calculate all phase/gain crossings with
8888
# frd data. Correct to return smallest phase
8989
# margin, smallest gain margin and their frequencies
90+
# RvP, Jun 10, 2017, modified the inclusion of roots found for phase
91+
# crossing to include all >= 0, made subsequent calc
92+
# insensitive to div by 0
93+
# also changed the selection of which crossings to
94+
# return on basis of "A note on the Gain and Phase
95+
# Margin Concepts" Journal of Control and Systems
96+
# Engineering, Yazdan Bavafi-Toosi, Dec 2015, vol 3
97+
# issue 1, pp 51-59, closer to Matlab behavior??
9098
def stability_margins(sysdata, returnall=False, epsw=0.0):
9199
"""Calculate stability margins and associated crossover frequencies.
92100
@@ -104,7 +112,7 @@ def stability_margins(sysdata, returnall=False, epsw=0.0):
104112
minimum stability margins. For frequency data or FRD systems, only one
105113
margin is found and returned.
106114
epsw: float, optional
107-
Frequencies below this value (default 1e-8) are considered static gain,
115+
Frequencies below this value (default 0.0) are considered static gain,
108116
and not returned as margin.
109117
110118
Returns
@@ -263,17 +271,19 @@ def dstab(w):
263271
if returnall:
264272
return GM, PM, SM, w_180, wc, wstab
265273
else:
266-
if GM.shape[0]:
274+
if GM.shape[0] and not np.isinf(GM).all():
267275
with np.errstate(all='ignore'):
268276
gmidx = np.where(np.abs(np.log(GM)) ==
269277
np.min(np.abs(np.log(GM))))
278+
else:
279+
gmidx = -1
270280
if PM.shape[0]:
271281
pmidx = np.where(np.abs(PM) == np.amin(np.abs(PM)))[0]
272282
return (
273-
(not GM.shape[0] and float('inf')) or GM[gmidx][0],
283+
(not gmidx != -1 and float('inf')) or GM[gmidx][0],
274284
(not PM.shape[0] and float('inf')) or PM[pmidx][0],
275285
(not SM.shape[0] and float('inf')) or np.amin(SM),
276-
(not w_180.shape[0] and float('nan')) or w_180[gmidx][0],
286+
(not gmidx != -1 and float('nan')) or w_180[gmidx][0],
277287
(not wc.shape[0] and float('nan')) or wc[pmidx][0],
278288
(not wstab.shape[0] and float('nan')) or wstab[SM==np.amin(SM)][0])
279289

control/tests/margin_test.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ def assert_array_almost_equal(x, y, ndigit=4):
1616
x = np.array(x)
1717
y = np.array(y)
1818
try:
19-
if np.isfinite(x).any() and (np.isfinite(x) == np.isfinite(y)).all() and \
20-
(x[np.logical_not(np.isfinite(x))] ==
21-
y[np.logical_not(np.isfinite(y))]).all():
19+
if np.isfinite(x).any() and \
20+
np.equal(np.isfinite(x), np.isfinite(y)).all() and \
21+
np.equal(np.isnan(x), np.isnan(y)).all():
2222
np.testing.assert_array_almost_equal(
2323
x[np.isfinite(x)], y[np.isfinite(y)], ndigit)
2424
return
@@ -69,15 +69,25 @@ def setUp(self):
6969
[2.2716, 97.5941, 0.5591, 10.0053, 0.0850, 9.9918]
7070

7171
"""
72+
hm1 = s/(s+1);
73+
h0 = 1/(s+1)^3;
7274
h1 = (s + 0.1)/s/(s+1);
7375
h2 = (s + 0.1)/s^2/(s+1);
7476
h3 = (s + 0.1)*(s+0.1)/s^3/(s+1);
7577
"""
7678
self.types = {
79+
'typem1': s/(s+1),
80+
'type0': 1/(s+1)**3,
7781
'type1': (s + 0.1)/s/(s+1),
7882
'type2': (s + 0.1)/s**2/(s+1),
7983
'type3': (s + 0.1)*(s+0.1)/s**3/(s+1) }
80-
self.tmargin = ( self.types,
84+
self.tmargin = ( self.types,
85+
dict(sys='typem1', K=2.0, digits=3, result=(
86+
float('Inf'), -120.0007, float('NaN'), 0.5774)),
87+
dict(sys='type0', K = 0.8, digits=3, result=(
88+
10.0014, float('inf'), 1.7322, float('nan'))),
89+
dict(sys='type0', K = 2.0, digits=2, result=(
90+
4.000, 67.6058, 1.7322, 0.7663)),
8191
dict(sys='type1', K=1.0, digits=4, result=(
8292
float('Inf'), 144.9032, float('NaN'), 0.3162)),
8393
dict(sys='type2', K=1.0, digits=4, result=(
@@ -89,10 +99,10 @@ def setUp(self):
8999

90100
# from "A note on the Gain and Phase Margin Concepts
91101
# Journal of Control and Systems Engineering, Yazdan Bavafi-Toosi,
92-
# Dec 205, vol 3 iss 1, pp 51-59
102+
# Dec 2015, vol 3 iss 1, pp 51-59
93103
#
94104
# A cornucopia of tricky systems for phase / gain margin
95-
# Still have to convert this to tests + fix margin to handle
105+
# Still have to convert more to tests + fix margin to handle
96106
# also these torture cases
97107
"""
98108
% matlab compatible
@@ -273,11 +283,17 @@ def test_zmore_margin(self):
273283
print("""
274284
warning, Matlab gives different values (0 and 0) for gain
275285
margin of the following system:
276-
{:s}
286+
{type2!s}
277287
python-control gives inf
278288
difficult to argue which is right? Special case or different
279289
approach?
280-
""".format(str(self.types['type2'])))
290+
291+
edge cases, like
292+
{type0!s}
293+
which approaches a gain of 1 for w -> 0, are also not identically
294+
indicated, Matlab gives phase margin -180, at w = 0. for higher or
295+
lower gains, results match
296+
""".format(**self.types))
281297

282298
sdict = self.tmargin[0]
283299
for test in self.tmargin[1:]:

0 commit comments

Comments
 (0)