56
56
57
57
# Bode plot
58
58
def bode_plot (syslist , omega = None , dB = False , Hz = False , deg = True ,
59
- color = None , Plot = True ):
59
+ Plot = True , * args , ** kwargs ):
60
60
"""Bode plot for a system
61
61
62
62
Plots a Bode plot for the system over a (optional) frequency range.
@@ -71,12 +71,12 @@ def bode_plot(syslist, omega=None, dB=False, Hz=False, deg=True,
71
71
If True, plot result in dB
72
72
Hz : boolean
73
73
If True, plot frequency in Hz (omega must be provided in rad/sec)
74
- color : matplotlib color
75
- Color of line in bode plot
76
74
deg : boolean
77
75
If True, return phase in degrees (else radians)
78
76
Plot : boolean
79
77
If True, plot magnitude and phase
78
+ *args, **kwargs:
79
+ Additional options to matplotlib (color, linestyle, etc)
80
80
81
81
Returns
82
82
-------
@@ -95,7 +95,6 @@ def bode_plot(syslist, omega=None, dB=False, Hz=False, deg=True,
95
95
96
96
Examples
97
97
--------
98
- >>> from matlab import ss
99
98
>>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
100
99
>>> mag, phase, omega = bode(sys)
101
100
"""
@@ -132,53 +131,37 @@ def bode_plot(syslist, omega=None, dB=False, Hz=False, deg=True,
132
131
# Magnitude plot
133
132
plt .subplot (211 );
134
133
if dB :
135
- if color == None :
136
- plt .semilogx (omega , mag )
137
- else :
138
- plt .semilogx (omega , mag , color = color )
139
- plt .ylabel ("Magnitude (dB)" )
134
+ plt .semilogx (omega , mag , * args , ** kwargs )
140
135
else :
141
- if color == None :
142
- plt .loglog (omega , mag )
143
- else :
144
- plt .loglog (omega , mag , color = color )
145
- plt .ylabel ("Magnitude" )
136
+ plt .loglog (omega , mag , * args , ** kwargs )
137
+ plt .hold (True );
146
138
147
- # Add a grid to the plot
139
+ # Add a grid to the plot + labeling
148
140
plt .grid (True )
149
141
plt .grid (True , which = 'minor' )
150
- plt .hold ( True );
142
+ plt .ylabel ( "Magnitude (dB)" if dB else "Magnitude" )
151
143
152
144
# Phase plot
153
145
plt .subplot (212 );
154
- if deg :
155
- phase_deg = phase
156
- else :
157
- phase_deg = phase * 180 / sp .pi
158
- if color == None :
159
- plt .semilogx (omega , phase_deg )
160
- else :
161
- plt .semilogx (omega , phase_deg , color = color )
162
- plt .hold (True )
146
+ plt .semilogx (omega , phase , * args , ** kwargs )
147
+ plt .hold (True );
163
148
164
- # Add a grid to the plot
149
+ # Add a grid to the plot + labeling
165
150
plt .grid (True )
166
151
plt .grid (True , which = 'minor' )
167
- plt .ylabel ("Phase (deg)" )
152
+ plt .ylabel ("Phase (deg)" if deg else "Phase (rad)" )
168
153
169
154
# Label the frequency axis
170
- if Hz :
171
- plt .xlabel ("Frequency (Hz)" )
172
- else :
173
- plt .xlabel ("Frequency (rad/sec)" )
155
+ plt .xlabel ("Frequency (Hz)" if Hz else "Frequency (rad/sec)" )
174
156
175
157
if len (syslist ) == 1 :
176
158
return mags [0 ], phases [0 ], omegas [0 ]
177
159
else :
178
160
return mags , phases , omegas
179
161
180
162
# Nyquist plot
181
- def nyquist_plot (syslist , omega = None , Plot = True ):
163
+ def nyquist_plot (syslist , omega = None , Plot = True , color = 'b' ,
164
+ labelFreq = 0 , * args , ** kwargs ):
182
165
"""Nyquist plot for a system
183
166
184
167
Plots a Nyquist plot for the system over a (optional) frequency range.
@@ -190,7 +173,11 @@ def nyquist_plot(syslist, omega=None, Plot=True):
190
173
omega : freq_range
191
174
Range of frequencies (list or bounds) in rad/sec
192
175
Plot : boolean
193
- if True, plot magnitude
176
+ If True, plot magnitude
177
+ labelFreq : int
178
+ Label every nth frequency on the plot
179
+ *args, **kwargs:
180
+ Additional options to matplotlib (color, linestyle, etc)
194
181
195
182
Returns
196
183
-------
@@ -203,7 +190,6 @@ def nyquist_plot(syslist, omega=None, Plot=True):
203
190
204
191
Examples
205
192
--------
206
- >>> from matlab import ss
207
193
>>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
208
194
>>> real, imag, freq = nyquist(sys)
209
195
"""
@@ -214,12 +200,14 @@ def nyquist_plot(syslist, omega=None, Plot=True):
214
200
# Select a default range if none is provided
215
201
if (omega == None ):
216
202
omega = default_frequency_range (syslist )
203
+
217
204
# Interpolate between wmin and wmax if a tuple or list are provided
218
205
elif (isinstance (omega ,list ) | isinstance (omega ,tuple )):
219
206
# Only accept tuple or list of length 2
220
207
if (len (omega ) != 2 ):
221
208
raise ValueError ("Supported frequency arguments are (wmin,wmax) tuple or list, or frequency vector. " )
222
- omega = np .logspace (np .log10 (omega [0 ]),np .log10 (omega [1 ]),num = 50 ,endpoint = True ,base = 10.0 )
209
+ omega = np .logspace (np .log10 (omega [0 ]), np .log10 (omega [1 ]),
210
+ num = 50 , endpoint = True , base = 10.0 )
223
211
for sys in syslist :
224
212
if (sys .inputs > 1 or sys .outputs > 1 ):
225
213
#TODO: Add MIMO nyquist plots.
@@ -236,11 +224,33 @@ def nyquist_plot(syslist, omega=None, Plot=True):
236
224
237
225
if (Plot ):
238
226
# Plot the primary curve and mirror image
239
- plt .plot (x , y , '-' );
240
- plt .plot (x , - y , '--' );
227
+ plt .plot (x , y , '-' , color = color , * args , ** kwargs );
228
+ plt .plot (x , - y , '--' , color = color , * args , ** kwargs );
241
229
# Mark the -1 point
242
230
plt .plot ([- 1 ], [0 ], 'r+' )
243
231
232
+ # Label the frequencies of the points
233
+ if (labelFreq ):
234
+ for xpt , ypt , omegapt in zip (x , y , omega )[::labelFreq ]:
235
+ # Convert to Hz
236
+ f = omegapt / (2 * sp .pi )
237
+
238
+ # Factor out multiples of 1000 and limit the
239
+ # result to the range [-8, 8].
240
+ pow1000 = max (min (get_pow1000 (f ),8 ),- 8 )
241
+
242
+ # Get the SI prefix.
243
+ prefix = gen_prefix (pow1000 )
244
+
245
+ # Apply the text. (Use a space before the text to
246
+ # prevent overlap with the data.)
247
+ #
248
+ # np.round() is used because 0.99... appears
249
+ # instead of 1.0, and this would otherwise be
250
+ # truncated to 0.
251
+ plt .text (xpt , ypt ,
252
+ ' ' + str (int (np .round (f / 1000 ** pow1000 , 0 ))) +
253
+ ' ' + prefix + 'Hz' )
244
254
return x , y , omega
245
255
246
256
# Gang of Four
@@ -362,6 +372,49 @@ def default_frequency_range(syslist):
362
372
363
373
return omega
364
374
375
+ #
376
+ # KLD 5/23/11: Two functions to create nice looking labels
377
+ #
378
+ def get_pow1000 (num ):
379
+ '''Determine the exponent for which the significand of a number is within the
380
+ range [1, 1000).
381
+ '''
382
+ # Based on algorithm from http://www.mail-archive.com/matplotlib-users@lists.sourceforge.net/msg14433.html, accessed 2010/11/7
383
+ # by Jason Heeris 2009/11/18
384
+ from decimal import Decimal
385
+ from math import floor
386
+ dnum = Decimal (str (num ))
387
+ if dnum == 0 :
388
+ return 0
389
+ elif dnum < 0 :
390
+ dnum = - dnum
391
+ return int (floor (dnum .log10 ()/ 3 ))
392
+
393
+ def gen_prefix (pow1000 ):
394
+ '''Return the SI prefix for a power of 1000.
395
+ '''
396
+ # Prefixes according to Table 5 of [BIPM 2006] (excluding hecto,
397
+ # deca, deci, and centi).
398
+ if pow1000 < - 8 or pow1000 > 8 :
399
+ raise ValueError ("Value is out of the range covered by the SI prefixes." )
400
+ return ['Y' , # yotta (10^24)
401
+ 'Z' , # zetta (10^21)
402
+ 'E' , # exa (10^18)
403
+ 'P' , # peta (10^15)
404
+ 'T' , # tera (10^12)
405
+ 'G' , # giga (10^9)
406
+ 'M' , # mega (10^6)
407
+ 'k' , # kilo (10^3)
408
+ '' , # (10^0)
409
+ 'm' , # milli (10^-3)
410
+ r'$\mu$' , # micro (10^-6)
411
+ 'n' , # nano (10^-9)
412
+ 'p' , # pico (10^-12)
413
+ 'f' , # femto (10^-15)
414
+ 'a' , # atto (10^-18)
415
+ 'z' , # zepto (10^-21)
416
+ 'y' ][8 - pow1000 ] # yocto (10^-24)
417
+
365
418
# Function aliases
366
419
bode = bode_plot
367
420
nyquist = nyquist_plot
0 commit comments