|
From: <re...@us...> - 2014-07-08 10:57:32
|
Revision: 304
http://sourceforge.net/p/python-control/code/304
Author: repa
Date: 2014-07-08 10:57:18 +0000 (Tue, 08 Jul 2014)
Log Message:
-----------
Correction to the gain margin calculation. Old implementation also returned
gains where arg(H) == 0.
Switched around the w_180 and wc return parameters, to match the order
of gain margin (matching w_180) and phase margin (matching wc) return.
Modified Paths:
--------------
trunk/src/margins.py
trunk/tests/margin_test.py
Modified: trunk/src/margins.py
===================================================================
--- trunk/src/margins.py 2014-07-06 17:06:44 UTC (rev 303)
+++ trunk/src/margins.py 2014-07-08 10:57:18 UTC (rev 304)
@@ -80,6 +80,9 @@
# idea for the frequency data solution copied/adapted from
# https://github.com/alchemyst/Skogestad-Python/blob/master/BODE.py
# Rene van Paassen <ren...@gm...>
+#
+# RvP, July 8, 2014, corrected to exclude phase=0 crossing for the gain
+# margin polynomial
def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-10):
"""Calculate gain, phase and stability margins and associated
crossover frequencies.
@@ -147,7 +150,19 @@
# test imaginary part of tf == 0, for phase crossover/gain margins
test_w_180 = np.polyadd(np.polymul(inum, rden), np.polymul(rnum, -iden))
w_180 = np.roots(test_w_180)
- w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 > epsw)])
+
+ # first remove imaginary and negative frequencies, epsw removes the
+ # "0" frequency for type-2 systems
+ w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 >= epsw)])
+
+ # evaluate response at remaining frequencies, to test for phase 180 vs 0
+ resp_w_180 = np.real(np.polyval(sys.num[0][0], 1.j*w_180) /
+ np.polyval(sys.den[0][0], 1.j*w_180))
+
+ # only keep frequencies where the negative real axis is crossed
+ w_180 = w_180[(resp_w_180 < 0.0)]
+
+ # and sort
w_180.sort()
# test magnitude is 1 for gain crossover/phase margins
@@ -203,14 +218,14 @@
SM = np.abs(sys.evalfr(wstab)[0][0]+1)
if returnall:
- return GM, PM, SM, wc, w_180, wstab
+ return GM, PM, SM, w_180, wc, wstab
else:
return (
(GM.shape[0] or None) and GM[0],
(PM.shape[0] or None) and PM[0],
(SM.shape[0] or None) and SM[0],
+ (w_180.shape[0] or None) and w_180[0],
(wc.shape[0] or None) and wc[0],
- (w_180.shape[0] or None) and w_180[0],
(wstab.shape[0] or None) and wstab[0])
Modified: trunk/tests/margin_test.py
===================================================================
--- trunk/tests/margin_test.py 2014-07-06 17:06:44 UTC (rev 303)
+++ trunk/tests/margin_test.py 2014-07-08 10:57:18 UTC (rev 304)
@@ -17,11 +17,18 @@
self.sys2 = TransferFunction([1], [1, 2, 3, 4])
self.sys3 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]],
[[1., 0.]], [[0.]])
+ s = TransferFunction([1, 0], [1])
+ self.sys4 = (8.75*(4*s**2+0.4*s+1))/((100*s+1)*(s**2+0.22*s+1)) * \
+ 1./(s**2/(10.**2)+2*0.04*s/10.+1)
def test_stability_margins(self):
gm, pm, sm, wg, wp, ws = stability_margins(self.sys1);
gm, pm, sm, wg, wp, ws = stability_margins(self.sys2);
gm, pm, sm, wg, wp, ws = stability_margins(self.sys3);
+ gm, pm, sm, wg, wp, ws = stability_margins(self.sys4);
+ np.testing.assert_array_almost_equal(
+ [gm, pm, sm, wg, wp, ws],
+ [2.2716, 97.5941, 1.0454, 10.0053, 0.0850, 0.4973], 3)
def test_phase_crossover_frequencies(self):
omega, gain = phase_crossover_frequencies(self.sys2)
|