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- """Bode plot for a system
64+ Plot = True , omega_limits = None , omega_num = None ,margins = None , * args , ** kwargs ):
65+ """
66+ Bode plot for a system
6567
6668 Plots a Bode plot for the system over a (optional) frequency range.
6769
6870 Parameters
6971 ----------
7072 syslist : linsys
7173 List of linear input/output systems (single system is OK)
72- omega : freq_range
73- Range of frequencies in rad/sec
74+ omega : list
75+ List of frequencies in rad/sec to be used for frequency response
7476 dB : boolean
7577 If True, plot result in dB
7678 Hz : boolean
@@ -84,7 +86,9 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
8486 If Hz=True the limits are in Hz otherwise in rad/s.
8587 omega_num: int
8688 number of samples
87- *args, **kwargs:
89+ margins : boolean
90+ if True, plot gain and phase margin
91+ \*args, \**kwargs:
8892 Additional options to matplotlib (color, linestyle, etc)
8993
9094 Returns
@@ -105,7 +109,7 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
105109 2. If a discrete time model is given, the frequency response is plotted
106110 along the upper branch of the unit circle, using the mapping z = exp(j
107111 \omega dt) where omega ranges from 0 to pi/dt and dt is the discrete
108- time base . If not timebase is specified (dt = True), dt is set to 1.
112+ timebase . If not timebase is specified (dt = True), dt is set to 1.
109113
110114 Examples
111115 --------
@@ -208,7 +212,7 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
208212 color = pltline [0 ].get_color ())
209213
210214 # Add a grid to the plot + labeling
211- ax_mag .grid (True , which = 'both' )
215+ ax_mag .grid (False if margins else True , which = 'both' )
212216 ax_mag .set_ylabel ("Magnitude (dB)" if dB else "Magnitude" )
213217
214218 # Phase plot
@@ -218,6 +222,50 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
218222 phase_plot = phase
219223 ax_phase .semilogx (omega_plot , phase_plot , * args , ** kwargs )
220224
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+
221269 if nyquistfrq_plot :
222270 ax_phase .axvline (nyquistfrq_plot , color = pltline [0 ].get_color ())
223271
@@ -236,7 +284,7 @@ def genZeroCenteredSeries(val_min, val_max, period):
236284 ylim = ax_phase .get_ylim ()
237285 ax_phase .set_yticks (genZeroCenteredSeries (ylim [0 ], ylim [1 ], math .pi / 4. ))
238286 ax_phase .set_yticks (genZeroCenteredSeries (ylim [0 ], ylim [1 ], math .pi / 12. ), minor = True )
239- ax_phase .grid (True , which = 'both' )
287+ ax_phase .grid (False if margins else True , which = 'both' )
240288 # ax_mag.grid(which='minor', alpha=0.3)
241289 # ax_mag.grid(which='major', alpha=0.9)
242290 # ax_phase.grid(which='minor', alpha=0.3)
@@ -253,7 +301,8 @@ def genZeroCenteredSeries(val_min, val_max, period):
253301# Nyquist plot
254302def nyquist_plot (syslist , omega = None , Plot = True , color = 'b' ,
255303 labelFreq = 0 , * args , ** kwargs ):
256- """Nyquist plot for a system
304+ """
305+ Nyquist plot for a system
257306
258307 Plots a Nyquist plot for the system over a (optional) frequency range.
259308
@@ -267,7 +316,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, color='b',
267316 If True, plot magnitude
268317 labelFreq : int
269318 Label every nth frequency on the plot
270- *args, **kwargs:
319+ \ *args, \ **kwargs:
271320 Additional options to matplotlib (color, linestyle, etc)
272321
273322 Returns
@@ -283,6 +332,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, color='b',
283332 --------
284333 >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
285334 >>> real, imag, freq = nyquist_plot(sys)
335+
286336 """
287337 # If argument was a singleton, turn it into a list
288338 if (not getattr (syslist , '__iter__' , False )):
0 commit comments