47
47
import math
48
48
from .ctrlutil import unwrap
49
49
from .bdalg import feedback
50
+ from .margins import stability_margins
50
51
51
52
__all__ = ['bode_plot' , 'nyquist_plot' , 'gangof4_plot' ,
52
53
'bode' , 'nyquist' , 'gangof4' ]
60
61
61
62
# Bode plot
62
63
def bode_plot (syslist , omega = None , dB = None , Hz = None , deg = None ,
63
- Plot = True , omega_limits = None , omega_num = None , * args , ** kwargs ):
64
+ Plot = True , omega_limits = None , omega_num = None ,margins = None , * args , ** kwargs ):
64
65
"""
65
66
Bode plot for a system
66
67
@@ -85,6 +86,8 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
85
86
If Hz=True the limits are in Hz otherwise in rad/s.
86
87
omega_num: int
87
88
number of samples
89
+ margins : boolean
90
+ if True, plot gain and phase margin
88
91
\*args, \**kwargs:
89
92
Additional options to matplotlib (color, linestyle, etc)
90
93
@@ -209,7 +212,7 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
209
212
color = pltline [0 ].get_color ())
210
213
211
214
# Add a grid to the plot + labeling
212
- ax_mag .grid (True , which = 'both' )
215
+ ax_mag .grid (False if margins else True , which = 'both' )
213
216
ax_mag .set_ylabel ("Magnitude (dB)" if dB else "Magnitude" )
214
217
215
218
# Phase plot
@@ -219,6 +222,50 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
219
222
phase_plot = phase
220
223
ax_phase .semilogx (omega_plot , phase_plot , * args , ** kwargs )
221
224
225
+ # Show the phase and gain margins in the plot
226
+ if margins :
227
+ margin = stability_margins (sys )
228
+ gm , pm , Wcg , Wcp = margin [0 ], margin [1 ], margin [3 ], margin [4 ]
229
+ if pm >= 0. :
230
+ phase_limit = - 180.
231
+ else :
232
+ phase_limit = 180.
233
+
234
+ ax_mag .axhline (y = 0 if dB else 1 , color = 'k' , linestyle = ':' )
235
+ ax_phase .axhline (y = phase_limit if deg else math .radians (phase_limit ), color = 'k' , linestyle = ':' )
236
+ mag_ylim = ax_mag .get_ylim ()
237
+ phase_ylim = ax_phase .get_ylim ()
238
+
239
+ if pm != float ('inf' ) and Wcp != float ('nan' ):
240
+ if dB :
241
+ ax_mag .semilogx ([Wcp , Wcp ], [0. ,- 1e5 ],color = 'k' , linestyle = ':' )
242
+ else :
243
+ ax_mag .loglog ([Wcp ,Wcp ], [1. ,1e-8 ],color = 'k' ,linestyle = ':' )
244
+
245
+ if deg :
246
+ ax_phase .semilogx ([Wcp , Wcp ], [1e5 , phase_limit + pm ],color = 'k' , linestyle = ':' )
247
+ ax_phase .semilogx ([Wcp , Wcp ], [phase_limit + pm , phase_limit ],color = 'k' )
248
+ else :
249
+ ax_phase .semilogx ([Wcp , Wcp ], [1e5 , math .radians (phase_limit )+ math .radians (pm )],color = 'k' , linestyle = ':' )
250
+ ax_phase .semilogx ([Wcp , Wcp ], [math .radians (phase_limit ) + math .radians (pm ), math .radians (phase_limit )],color = 'k' )
251
+
252
+ if gm != float ('inf' ) and Wcg != float ('nan' ):
253
+ if dB :
254
+ ax_mag .semilogx ([Wcg , Wcg ], [- 20. * np .log10 (gm ), - 1e5 ],color = 'k' , linestyle = ':' )
255
+ ax_mag .semilogx ([Wcg , Wcg ], [0 ,- 20 * np .log10 (gm )],color = 'k' )
256
+ else :
257
+ ax_mag .loglog ([Wcg , Wcg ], [1. / gm ,1e-8 ],color = 'k' , linestyle = ':' )
258
+ ax_mag .loglog ([Wcg , Wcg ], [1. ,1. / gm ],color = 'k' )
259
+
260
+ if deg :
261
+ ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , phase_limit ],color = 'k' , linestyle = ':' )
262
+ else :
263
+ ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , math .radians (phase_limit )],color = 'k' , linestyle = ':' )
264
+
265
+ ax_mag .set_ylim (mag_ylim )
266
+ ax_phase .set_ylim (phase_ylim )
267
+ plt .suptitle ('Gm = %.2f %s(at %.2f rad/s), Pm = %.2f %s (at %.2f rad/s)' % (20 * np .log10 (gm ) if dB else gm ,'dB ' if dB else '\b ' ,Wcg ,pm if deg else math .radians (pm ),'deg' if deg else 'rad' ,Wcp ))
268
+
222
269
if nyquistfrq_plot :
223
270
ax_phase .axvline (nyquistfrq_plot , color = pltline [0 ].get_color ())
224
271
@@ -237,7 +284,7 @@ def genZeroCenteredSeries(val_min, val_max, period):
237
284
ylim = ax_phase .get_ylim ()
238
285
ax_phase .set_yticks (genZeroCenteredSeries (ylim [0 ], ylim [1 ], math .pi / 4. ))
239
286
ax_phase .set_yticks (genZeroCenteredSeries (ylim [0 ], ylim [1 ], math .pi / 12. ), minor = True )
240
- ax_phase .grid (True , which = 'both' )
287
+ ax_phase .grid (False if margins else True , which = 'both' )
241
288
# ax_mag.grid(which='minor', alpha=0.3)
242
289
# ax_mag.grid(which='major', alpha=0.9)
243
290
# ax_phase.grid(which='minor', alpha=0.3)
0 commit comments