40
40
# SUCH DAMAGE.
41
41
#
42
42
# $Id$
43
-
43
+ import matplotlib
44
44
import matplotlib .pyplot as plt
45
45
import scipy as sp
46
46
import numpy as np
@@ -89,7 +89,7 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
89
89
omega_num: int
90
90
number of samples
91
91
margins : boolean
92
- if True, plot gain and phase margin
92
+ If True, plot gain and phase margin
93
93
\*args, \**kwargs:
94
94
Additional options to matplotlib (color, linestyle, etc)
95
95
@@ -193,23 +193,35 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
193
193
# The code below should work on all cases.
194
194
195
195
# Get the current figure
196
- fig = plt .gcf ()
197
- ax_mag = None
198
- ax_phase = None
199
-
200
- # Get the current axes if they already exist
201
- for ax in fig .axes :
202
- if ax .get_label () == 'control-bode-magnitude' :
203
- ax_mag = ax
204
67F4
- elif ax .get_label () == 'control-bode-phase' :
205
- ax_phase = ax
206
-
207
- # If no axes present, create them from scratch
208
- if ax_mag is None or ax_phase is None :
209
- plt .clf ()
210
- ax_mag = plt .subplot (211 , label = 'control-bode-magnitude' )
211
- ax_phase = plt .subplot (212 , label = 'control-bode-phase' ,
212
- sharex = ax_mag )
196
+
197
+ if 'sisotool' in kwargs :
198
+ fig = kwargs ['fig' ]
199
+ ax_mag = fig .axes [0 ]
200
+ ax_phase = fig .axes [2 ]
201
+ sisotool = kwargs ['sisotool' ]
202
+ del kwargs ['fig' ]
203
+ del kwargs ['sisotool' ]
204
+ else :
205
+ fig = plt .gcf ()
206
+ ax_mag = None
207
+ ax_phase = None
208
+ sisotool = False
209
+
210
+ # Get the current axes if they already exist
211
+ for ax in fig .axes :
212
+ if ax .get_label () == 'control-bode-magnitude' :
213
+ ax_mag = ax
214
+ elif ax .get_label () == 'control-bode-phase' :
215
+ ax_phase = ax
216
+
217
+ # If no axes present, create them from scratch
218
+ if ax_mag is None or ax_phase is None :
219
+ plt .clf ()
220
+ ax_mag = plt .subplot (211 ,
221
+ label = 'control-bode-magnitude' )
222
+ ax_phase = plt .subplot (212 ,
223
+ label = 'control-bode-phase' ,
224
+ sharex = ax_mag )
213
225
214
226
# Magnitude plot
215
227
if dB :
@@ -237,58 +249,107 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
237
249
if margins :
238
250
margin = stability_margins (sys )
239
251
gm , pm , Wcg , Wcp = margin [0 ], margin [1 ], margin [3 ], margin [4 ]
240
- if pm >= 0. :
241
- phase_limit = - 180.
242
- else :
252
+ # TODO: add some documentation describing why this is here
253
+ phase_at_cp = phases [ 0 ][( np . abs ( omegas [ 0 ] - Wcp )). argmin ()]
254
+ if phase_at_cp >= 0. :
243
255
phase_limit = 180.
256
+ else :
257
+ phase_limit = - 180.
258
+
259
+ if Hz :
260
+ Wcg , Wcp = Wcg / (2 * math .pi ),Wcp / (2 * math .pi )
244
261
245
- ax_mag .axhline (y = 0 if dB else 1 , color = 'k' , linestyle = ':' )
262
+ ax_mag .axhline (y = 0 if dB else 1 , color = 'k' , linestyle = ':' ,
263
+ zorder = - 20 )
246
264
ax_phase .axhline (y = phase_limit if deg else math .radians (phase_limit ),
247
- color = 'k' , linestyle = ':' )
265
+ color = 'k' , linestyle = ':' , zorder = - 20 )
248
266
mag_ylim = ax_mag .get_ylim ()
249
267
phase_ylim = ax_phase .get_ylim ()
250
268
251
269
if pm != float ('inf' ) and Wcp != float ('nan' ):
252
270
if dB :
253
- ax_mag .semilogx ([Wcp , Wcp ], [0. , - 1e5 ], color = 'k' , linestyle = ':' )
271
+ ax_mag .semilogx ([Wcp , Wcp ], [0. ,- 1e5 ],
272
+ color = 'k' , linestyle = ':' ,
273
+ zorder = - 20 )
254
274
else :
255
- ax_mag .loglog ([Wcp , Wcp ], [1. , 1e-8 ], color = 'k' , linestyle = ':' )
275
+ ax_mag .loglog ([Wcp ,Wcp ], [1. ,1e-8 ],color = 'k' ,
276
+ linestyle = ':' , zorder = - 20 )
256
277
257
278
if deg :
258
- ax_phase .semilogx ([Wcp , Wcp ], [1e5 , phase_limit + pm ],
259
- color = 'k' , linestyle = ':' )
260
- ax_phase .semilogx ([Wcp , Wcp ], [phase_limit + pm , phase_limit ],
261
- color = 'k' )
279
+ ax_phase .semilogx ([Wcp , Wcp ],
280
+ [1e5 , phase_limit + pm ],
281
+ color = 'k' , linestyle = ':' ,
282
+ zorder = - 20 )
283
+ ax_phase .semilogx ([Wcp , Wcp ],
284
+ [phase_limit + pm , phase_limit ],
285
+ color = 'k' , zorder = - 20 )
262
286
else :
263
- ax_phase .semilogx ([Wcp , Wcp ], [1e5 , math .radians (phase_limit ) +
264
- math .radians (pm )],
265
- color = 'k' , linestyle = ':' )
266
- ax_phase .semilogx ([Wcp , Wcp ], [math .radians (phase_limit ) +
267
- math .radians (pm ),
268
- math .radians (phase_limit )], color = 'k' )
287
+ ax_phase .semilogx ([Wcp , Wcp ],
288
+ [1e5 , math .radians (phase_limit ) +
289
+ math .radians (pm )],
290
+ color = 'k' , linestyle = ':' ,
291
+ zorder = - 20 )
292
+ ax_phase .semilogx ([Wcp , Wcp ],
293
+ [math .radians (phase_limit ) +
294
+ math .radians (pm ),
295
+ math .radians (phase_limit )],
296
+ color = 'k' , zorder = - 20 )
269
297
270
298
if gm != float ('inf' ) and Wcg != float ('nan' ):
271
299
if dB :
272
- ax_mag .semilogx ([Wcg , Wcg ], [- 20. * np .log10 (gm ), - 1e5 ], color = 'k' ,
273
- linestyle = ':' )
274
- ax_mag .semilogx ([Wcg , Wcg ], [0 , - 20 * np .log10 (gm )], color = 'k' )
300
+ ax_mag .semilogx ([Wcg , Wcg ],
301
+ [- 20. * np .log10 (gm ), - 1e5 ],
302
+ color = 'k' , linestyle = ':' ,
303
+ zorder = - 20 )
304
+ ax_mag .semilogx ([Wcg , Wcg ], [0 ,- 20 * np .log10 (gm )],
305
+ color = 'k' , zorder = - 20 )
275
306
else :
276
- ax_mag .loglog ([Wcg , Wcg ], [1. / gm , 1e-8 ], color = 'k' , linestyle = ':' )
277
- ax_mag .loglog ([Wcg , Wcg ], [1. , 1. / gm ], color = 'k' )
307
+ ax_mag .loglog ([Wcg , Wcg ],
308
+ [1. / gm ,1e-8 ],color = 'k' ,
309
+ linestyle = ':' , zorder = - 20 )
310
+ ax_mag .loglog ([Wcg , Wcg ],
311
+ [1. ,1. / gm ],color = 'k' , zorder = - 20 )
278
312
279
313
if deg :
280
- ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , phase_limit ],
281
- color = 'k' , linestyle = ':' )
314
+ ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , phase_limit ],
315
+ color = 'k' , linestyle = ':' ,
316
+ zorder = - 20 )
282
317
else :
283
- ax_phase .semilogx ([Wcg , Wcg ], [1e-8 , math .radians (phase_limit )],
284
- color = 'k' , linestyle = ':' )
318
+ ax_phase .semilogx ([Wcg , Wcg ],
319
+ [1e-8 , math .radians (phase_limit )],
320
+ color = 'k' , linestyle = ':' ,
321
+ zorder = - 20 )
285
322
286
323
ax_mag .set_ylim (mag_ylim )
287
324
ax_phase .set_ylim (phase_ylim )
288
- plt .suptitle ('Gm = %.2f %s(at %.2f rad/s), Pm = %.2f %s (at %.2f rad/s)' %
289
- (20 * np .log10 (gm ) if dB else gm ,
290
- 'dB ' if dB else '\b ' , Wcg , pm if deg else math .radians (pm ),
291
- 'deg' if deg else 'rad' , Wcp ))
325
+
326
+ if sisotool :
327
+ ax_mag .text (0.04 , 0.06 ,
328
+ 'G.M.: %.2f %s\n Freq: %.2f %s' %
329
+ (20 * np .log10 (gm ) if dB else gm ,
330
+ 'dB ' if dB else '' ,
331
+ Wcg , 'Hz' if Hz else 'rad/s' ),
332
+ horizontalalignment = 'left' ,
333
+ verticalalignment = 'bottom' ,
334
+ transform = ax_mag .transAxes ,
335
+ fontsize = 8 if int (matplotlib .__version__ [0 ]) == 1 else 6 )
336
+ ax_phase .text (0.04 , 0.06 ,
337
+ 'P.M.: %.2f %s\n Freq: %.2f %s' %
338
+ (pm if deg else math .radians (pm ),
339
+ 'deg' if deg else 'rad' ,
340
+ Wcp , 'Hz' if Hz else 'rad/s' ),
341
+ horizontalalignment = 'left' ,
342
+ verticalalignment = 'bottom' ,
343
+ transform = ax_phase .transAxes ,
344
+ fontsize = 8 if int (matplotlib .__version__ [0 ]) == 1 else 6 )
345
+ else :
346
+ plt .suptitle ('Gm = %.2f %s(at %.2f %s), Pm = %.2f %s (at %.2f %s)' %
347
+ (20 * np .log10 (gm ) if dB else gm ,
348
+ 'dB ' if dB else '\b ' ,
349
+ Wcg , 'Hz' if Hz else 'rad/s' ,
350
+ pm if deg else math .radians (pm ),
351
+ 'deg' if deg else 'rad' ,
352
+ Wcp , 'Hz' if Hz else 'rad/s' ))
292
353
293
354
if nyquistfrq_plot :
294
355
ax_phase .axvline (nyquistfrq_plot , color = pltline [0 ].get_color ())
@@ -302,12 +363,19 @@ def gen_zero_centered_series(val_min, val_max, period):
302
363
return np .arange (v1 , v2 + 1 ) * period
303
364
if deg :
304
365
ylim = ax_phase .get_ylim ()
305
- ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ], ylim [1 ], 45. ))
306
- ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ], ylim [1 ], 15. ), minor = True )
366
+ ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ],
367
+ ylim [1 ], 45. ))
368
+ ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ],
369
+ ylim [1 ], 15. ),
370
+ minor = True )
307
371
else :
308
372
ylim = ax_phase .get_ylim ()
309
- ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ], ylim [1 ], math .pi / 4. ))
310
- ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ], ylim [1 ], math .pi / 12. ),
373
+ ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ],
374
+ ylim [1 ],
375
+ math .pi / 4. ))
376
+ ax_phase .set_yticks (gen_zero_centered_series (ylim [0 ],
377
+ ylim [1 ],
378
+ math .pi / 12. ),
311
379
minor = True )
312
380
ax_phase .grid (False if margins else True , which = 'both' )
313
381
# ax_mag.grid(which='minor', alpha=0.3)
@@ -316,15 +384,17 @@ def gen_zero_centered_series(val_min, val_max, period):
316
384
# ax_phase.grid(which='major', alpha=0.9)
317
385
318
386
# Label the frequency axis
319
- ax_phase .set_xlabel ("Frequency (Hz)" if Hz else "Frequency (rad/sec)" )
387
+ ax_phase .set_xlabel ("Frequency (Hz)" if Hz
388
+ else "Frequency (rad/sec)" )
320
389
321
390
if len (syslist ) == 1 :
322
391
return mags [0 ], phases [0 ], omegas [0 ]
323
392
else :
324
393
return mags , phases , omegas
325
394
326
395
327
- def nyquist_plot (syslist , omega = None , Plot = True , color = None , labelFreq = 0 , * args , ** kwargs ):
396
+ def nyquist_plot (syslist , omega = None , Plot = True , color = None ,
397
+ labelFreq = 0 , * args , ** kwargs ):
328
398
"""
329
399
Nyquist plot for a system
330
400
@@ -683,6 +753,10 @@ def gen_prefix(pow1000):
683
753
'z' , # zepto (10^-21)
684
754
'y' ][8 - pow1000 ] # yocto (10^-24)
685
755
756
+ def find_nearest_omega (omega_list , omega ):
757
+ omega_list = np .asarray (omega_list )
758
+ idx = (np .abs (omega_list - omega )).argmin ()
759
+ return omega_list [(np .abs (omega_list - omega )).argmin ()]
686
760
687
761
# Function aliases
688
762
bode = bode_plot
0 commit comments