4747import math
4848from .ctrlutil import unwrap
4949from .bdalg import feedback
50+ from .margins import stability_margins
5051
5152__all__ = ['bode_plot' , 'nyquist_plot' , 'gangof4_plot' ,
5253 'bode' , 'nyquist' , 'gangof4' ]
6061
6162# Bode plot
6263def 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 ):
6465 """Bode plot for a system
6566
6667 Plots a Bode plot for the system over a (optional) frequency range.
@@ -84,6 +85,8 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
8485 If Hz=True the limits are in Hz otherwise in rad/s.
8586 omega_num: int
8687 number of samples
88+ margins : boolean
89+ if True, plot gain and phase margin
8790 *args, **kwargs:
8891 Additional options to matplotlib (color, linestyle, etc)
8992
@@ -216,7 +219,7 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
216219 color = pltline [0 ].get_color ())
217220
218221 # Add a grid to the plot + labeling
219- ax_mag .grid (True , which = 'both' )
222+ ax_mag .grid (False if margins else True , which = 'both' )
220223 ax_mag .set_ylabel ("Magnitude (dB)" if dB else "Magnitude" )
221224
222225 # Phase plot
@@ -226,6 +229,50 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
226229 phase_plot = phase
227230 ax_phase .semilogx (omega_plot , phase_plot , * args , ** kwargs )
228231
232+ # Show the phase and gain margins in the plot
233+ if margins :
234+ margin = stability_margins (sys )
235+ gm , pm , Wcg , Wcp = margin [0 ], margin [1 ], margin [3 ], margin [4 ]
236+ if pm >= 0. :
237+ phase_limit = - 180.
238+ else :
239+ phase_limit = 180.
240+
241+ ax_mag .axhline (y = 0 if dB else 1 , color = 'k' , linestyle = ':' )
242+ ax_phase .axhline (y = phase_limit if deg else math .radians (phase_limit ), color = 'k' , linestyle = ':' )
243+ mag_ylim = ax_mag .get_ylim ()
244+ phase_ylim = ax_phase .get_ylim ()
245+
246+ if pm != float ('inf' ) and Wcp != float ('nan' ):
247+ if dB :
248+ ax_mag .semilogx ([Wcp , Wcp ], [0. ,- 1e5 ],color = 'k' , linestyle = ':' )
249+ else :
250+ ax_mag .loglog ([Wcp ,Wcp ], [1. ,1e-8 ],color = 'k' ,linestyle = ':' )
251+
252+ if deg :
253+ ax_phase .semilogx ([Wcp , Wcp ], [1e5 , phase_limit + pm ],color = 'k' , linestyle = ':' )
254+ ax_phase .semilogx ([Wcp , Wcp ], [phase_limit + pm , phase_limit ],color = 'k' )
255+ else :
256+ ax_phase .semilogx ([Wcp , Wcp ], [1e5 , math .radians (phase_limit )+ math .radians (pm )],color = 'k' , linestyle = ':' )
257+ ax_phase .semilogx ([Wcp , Wcp ], [math .radians (phase_limit ) + math .radians (pm ), math .radians (phase_limit )],color = 'k' )
258+
259+ if gm != float ('inf' ) and Wcg != float ('nan' ):
260+ if dB :
261+ ax_mag .semilogx ([Wcg , Wcg ], [- 20. * np .log10 (gm ), - 1e5 ],color = 'k' , linestyle = ':' )
262+ ax_mag .semilogx ([Wcg , Wcg ], [0 ,- 20 * np .log10 (gm )],color = 'k' )
263+ else :
264+ ax_mag .loglog ([Wcg , Wcg ], [1. / gm ,1e-8 ],color = 'k' , linestyle = ':' )
265+ ax_mag .loglog ([Wcg , Wcg ], [1. ,1. / gm ],color = 'k' )
266+
267+ if deg :
268+ ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , phase_limit ],color = 'k' , linestyle = ':' )
269+ else :
270+ ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , math .radians (phase_limit )],color = 'k' , linestyle = ':' )
271+
272+ ax_mag .set_ylim (mag_ylim )
273+ ax_phase .set_ylim (phase_ylim )
274+ 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 ))
275+
229276 if nyquistfrq_plot :
230277 ax_phase .axvline (nyquistfrq_plot , color = pltline [0 ].get_color ())
231278
@@ -244,7 +291,7 @@ def genZeroCenteredSeries(val_min, val_max, period):
244291 ylim = ax_phase .get_ylim ()
245292 ax_phase .set_yticks (genZeroCenteredSeries (ylim [0 ], ylim [1 ], math .pi / 4. ))
246293 ax_phase .set_yticks (genZeroCenteredSeries (ylim [0 ], ylim [1 ], math .pi / 12. ), minor = True )
247- ax_phase .grid (True , which = 'both' )
294+ ax_phase .grid (False if margins else True , which = 'both' )
248295 # ax_mag.grid(which='minor', alpha=0.3)
249296 # ax_mag.grid(which='major', alpha=0.9)
250297 # ax_phase.grid(which='minor', alpha=0.3)
0 commit comments