Skip to content

Commit 3b19ae9

Browse files
authored
Fix plot issues (#382)
* fix sgrid, zgrid to use existing axes if they exist + PEP8 cleanup * change plot and print_gain keywords to lower case, with deprecation warning + fix bugs in gangof4 using dB + PEP8, docstring cleanup * remove conversion to state space that was giving spurious zero at infinity * change plot and print_gain keywords to lower case, with deprecation warning * PEP8 cleanup * use Hz=False for MATLAB + docstring corrections * labelFreq -> label_freq in nyquist_plot() w/ deprecation warning + remove extraneous docstring text
1 parent a09d059 commit 3b19ae9

13 files changed

+203
-129
lines changed

control/config.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ def use_matlab_defaults():
114114
115115
The following conventions are used:
116116
* Bode plots plot gain in dB, phase in degrees, frequency in
117-
Hertz, with grids
117+
rad/sec, with grids
118118
* State space class and functions use Numpy matrix objects
119119
120120
"""
121-
set_defaults('bode', dB=True, deg=True, Hz=True, grid=True)
121+
set_defaults('bode', dB=True, deg=True, Hz=False, grid=True)
122122
set_defaults('statesp', use_numpy_matrix=True)
123123

124124

@@ -128,7 +128,7 @@ def use_fbs_defaults():
128128
129129
The following conventions are used:
130130
* Bode plots plot gain in powers of ten, phase in degrees,
131-
frequency in Hertz, no grid
131+
frequency in rad/sec, no grid
132132
133133
"""
134134
set_defaults('bode', dB=False, deg=True, Hz=False, grid=False)

control/freqplot.py

+76-33
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080

8181

8282
def bode_plot(syslist, omega=None,
83-
Plot=True, omega_limits=None, omega_num=None,
83+
plot=True, omega_limits=None, omega_num=None,
8484
margins=None, *args, **kwargs):
8585
"""Bode plot for a system
8686
@@ -100,7 +100,7 @@ def bode_plot(syslist, omega=None,
100100
deg : bool
101101
If True, plot phase in degrees (else radians). Default value (True)
102102
config.defaults['bode.deg']
103-
Plot : bool
103+
plot : bool
104104
If True (default), plot magnitude and phase
105105
omega_limits: tuple, list, ... of two values
106106
Limits of the to generate frequency vector.
@@ -110,9 +110,9 @@ def bode_plot(syslist, omega=None,
110110
config.defaults['freqplot.number_of_samples'].
111111
margins : bool
112112
If True, plot gain and phase margin.
113-
*args
114-
Additional arguments for :func:`matplotlib.plot` (color, linestyle, etc)
115-
**kwargs:
113+
*args : `matplotlib` plot positional properties, optional
114+
Additional arguments for `matplotlib` plots (color, linestyle, etc)
115+
**kwargs : `matplotlib` plot keyword properties, optional
116116
Additional keywords (passed to `matplotlib`)
117117
118118
Returns
@@ -153,12 +153,20 @@ def bode_plot(syslist, omega=None,
153153
# Make a copy of the kwargs dictonary since we will modify it
154154
kwargs = dict(kwargs)
155155

156+
# Check to see if legacy 'Plot' keyword was used
157+
if 'Plot' in kwargs:
158+
import warnings
159+
warnings.warn("'Plot' keyword is deprecated in bode_plot; use 'plot'",
160+
FutureWarning)
161+
# Map 'Plot' keyword to 'plot' keyword
162+
plot = kwargs.pop('Plot')
163+
156164
# Get values for params (and pop from list to allow keyword use in plot)
157165
dB = config._get_param('bode', 'dB', kwargs, _bode_defaults, pop=True)
158166
deg = config._get_param('bode', 'deg', kwargs, _bode_defaults, pop=True)
159167
Hz = config._get_param('bode', 'Hz', kwargs, _bode_defaults, pop=True)
160168
grid = config._get_param('bode', 'grid', kwargs, _bode_defaults, pop=True)
161-
Plot = config._get_param('bode', 'grid', Plot, True)
169+
plot = config._get_param('bode', 'grid', plot, True)
162170
margins = config._get_param('bode', 'margins', margins, False)
163171

164172
# If argument was a singleton, turn it into a list
@@ -211,7 +219,7 @@ def bode_plot(syslist, omega=None,
211219
# Get the dimensions of the current axis, which we will divide up
212220
# TODO: Not current implemented; just use subplot for now
213221

214-
if Plot:
222+
if plot:
215223
nyquistfrq_plot = None
216224
if Hz:
217225
omega_plot = omega_sys / (2. * math.pi)
@@ -429,12 +437,13 @@ def gen_zero_centered_series(val_min, val_max, period):
429437
else:
430438
return mags, phases, omegas
431439

440+
432441
#
433442
# Nyquist plot
434443
#
435444

436-
def nyquist_plot(syslist, omega=None, Plot=True,
437-
labelFreq=0, arrowhead_length=0.1, arrowhead_width=0.1,
445+
def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
446+
arrowhead_length=0.1, arrowhead_width=0.1,
438447
color=None, *args, **kwargs):
439448
"""
440449
Nyquist plot for a system
@@ -451,13 +460,13 @@ def nyquist_plot(syslist, omega=None, Plot=True,
451460
If True, plot magnitude
452461
color : string
453462
Used to specify the color of the plot
454-
labelFreq : int
463+
label_freq : int
455464
Label every nth frequency on the plot
456465
arrowhead_width : arrow head width
457466
arrowhead_length : arrow head length
458-
*args
459-
Additional arguments for :func:`matplotlib.plot` (color, linestyle, etc)
460-
**kwargs:
467+
*args : `matplotlib` plot positional properties, optional
468+
Additional arguments for `matplotlib` plots (color, linestyle, etc)
469+
**kwargs : `matplotlib` plot keyword properties, optional
461470
Additional keywords (passed to `matplotlib`)
462471
463472
Returns
@@ -475,6 +484,22 @@ def nyquist_plot(syslist, omega=None, Plot=True,
475484
>>> real, imag, freq = nyquist_plot(sys)
476485
477486
"""
487+
# Check to see if legacy 'Plot' keyword was used
488+
if 'Plot' in kwargs:
489+
import warnings
490+
warnings.warn("'Plot' keyword is deprecated in nyquist_plot; "
491+
"use 'plot'", FutureWarning)
492+
# Map 'Plot' keyword to 'plot' keyword
493+
plot = kwargs.pop('Plot')
494+
495+
# Check to see if legacy 'labelFreq' keyword was used
496+
if 'labelFreq' in kwargs:
497+
import warnings
498+
warnings.warn("'labelFreq' keyword is deprecated in nyquist_plot; "
499+
"use 'label_freq'", FutureWarning)
500+
# Map 'labelFreq' keyword to 'label_freq' keyword
501+
label_freq = kwargs.pop('labelFreq')
502+
478503
# If argument was a singleton, turn it into a list
479504
if not getattr(syslist, '__iter__', False):
480505
syslist = (syslist,)
@@ -507,7 +532,7 @@ def nyquist_plot(syslist, omega=None, Plot=True,
507532
x = sp.multiply(mag, sp.cos(phase))
508533
y = sp.multiply(mag, sp.sin(phase))
509534

510-
if Plot:
535+
if plot:
511536
# Plot the primary curve and mirror image
512537
p = plt.plot(x, y, '-', color=color, *args, **kwargs)
513538
c = p[0].get_color()
@@ -527,8 +552,8 @@ def nyquist_plot(syslist, omega=None, Plot=True,
527552
plt.plot([-1], [0], 'r+')
528553

529554
# Label the frequencies of the points
530-
if labelFreq:
531-
ind = slice(None, None, labelFreq)
555+
if label_freq:
556+
ind = slice(None, None, label_freq)
532557
for xpt, ypt, omegapt in zip(x[ind], y[ind], omega[ind]):
533558
# Convert to Hz
534559
f = omegapt / (2 * sp.pi)
@@ -550,14 +575,15 @@ def nyquist_plot(syslist, omega=None, Plot=True,
550575
str(int(np.round(f / 1000 ** pow1000, 0))) + ' ' +
551576
prefix + 'Hz')
552577

553-
if Plot:
578+
if plot:
554579
ax = plt.gca()
555580
ax.set_xlabel("Real axis")
556581
ax.set_ylabel("Imaginary axis")
557582
ax.grid(color="lightgray")
558583

559584
return x, y, omega
560585

586+
561587
#
562588
# Gang of Four plot
563589
#
@@ -575,6 +601,8 @@ def gangof4_plot(P, C, omega=None, **kwargs):
575601
Linear input/output systems (process and control)
576602
omega : array
577603
Range of frequencies (list or bounds) in rad/sec
604+
**kwargs : `matplotlib` plot keyword properties, optional
605+
Additional keywords (passed to `matplotlib`)
578606
579607
Returns
580608
-------
@@ -590,16 +618,16 @@ def gangof4_plot(P, C, omega=None, **kwargs):
590618
Hz = config._get_param('bode', 'Hz', kwargs, _bode_defaults, pop=True)
591619
grid = config._get_param('bode', 'grid', kwargs, _bode_defaults, pop=True)
592620

593-
# Select a default range if none is provided
594-
# TODO: This needs to be made more intelligent
595-
if omega is None:
596-
omega = default_frequency_range((P, C))
597-
598621
# Compute the senstivity functions
599622
L = P * C
600623
S = feedback(1, L)
601624
T = L * S
602625

626+
# Select a default range if none is provided
627+
# TODO: This needs to be made more intelligent
628+
if omega is None:
629+
omega = default_frequency_range((P, C, S))
630+
603631
# Set up the axes with labels so that multiple calls to
604632
# gangof4_plot will superimpose the data. See details in bode_plot.
605633
plot_axes = {'t': None, 's': None, 'ps': None, 'cs': None}
@@ -628,36 +656,49 @@ def gangof4_plot(P, C, omega=None, **kwargs):
628656
# TODO: Need to add in the mag = 1 lines
629657
mag_tmp, phase_tmp, omega = S.freqresp(omega)
630658
mag = np.squeeze(mag_tmp)
631-
plot_axes['s'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
632-
plot_axes['s'].set_ylabel("$|S|$")
659+
if dB:
660+
plot_axes['s'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
661+
else:
662+
plot_axes['s'].loglog(omega_plot, mag, **kwargs)
663+
plot_axes['s'].set_ylabel("$|S|$" + " (dB)" if dB else "")
633664
plot_axes['s'].tick_params(labelbottom=False)
634665
plot_axes['s'].grid(grid, which='both')
635666

636667
mag_tmp, phase_tmp, omega = (P * S).freqresp(omega)
637668
mag = np.squeeze(mag_tmp)
638-
plot_axes['ps'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
669+
if dB:
670+
plot_axes['ps'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
671+
else:
672+
plot_axes['ps'].loglog(omega_plot, mag, **kwargs)
639673
plot_axes['ps'].tick_params(labelbottom=False)
640-
plot_axes['ps'].set_ylabel("$|PS|$")
674+
plot_axes['ps'].set_ylabel("$|PS|$" + " (dB)" if dB else "")
641675
plot_axes['ps'].grid(grid, which='both')
642676

643677
mag_tmp, phase_tmp, omega = (C * S).freqresp(omega)
644678
mag = np.squeeze(mag_tmp)
645-
plot_axes['cs'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
679+
if dB:
680+
plot_axes['cs'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
681+
else:
682+
plot_axes['cs'].loglog(omega_plot, mag, **kwargs)
646683
plot_axes['cs'].set_xlabel(
647684
"Frequency (Hz)" if Hz else "Frequency (rad/sec)")
648-
plot_axes['cs'].set_ylabel("$|CS|$")
685+
plot_axes['cs'].set_ylabel("$|CS|$" + " (dB)" if dB else "")
649686
plot_axes['cs'].grid(grid, which='both')
650687

651688
mag_tmp, phase_tmp, omega = T.freqresp(omega)
652689
mag = np.squeeze(mag_tmp)
653-
plot_axes['t'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag)
690+
if dB:
691+
plot_axes['t'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs)
692+
else:
693+
plot_axes['t'].loglog(omega_plot, mag, **kwargs)
654694
plot_axes['t'].set_xlabel(
655695
"Frequency (Hz)" if Hz else "Frequency (rad/sec)")
656-
plot_axes['t'].set_ylabel("$|T|$")
696+
plot_axes['t'].set_ylabel("$|T|$" + " (dB)" if dB else "")
657697
plot_axes['t'].grid(grid, which='both')
658698

659699
plt.tight_layout()
660700

701+
661702
#
662703
# Utility functions
663704
#
@@ -754,7 +795,7 @@ def default_frequency_range(syslist, Hz=None, number_of_samples=None,
754795
# TODO
755796
raise NotImplementedError(
756797
"type of system in not implemented now")
757-
except:
798+
except NotImplementedError:
758799
pass
759800

760801
# Make sure there is at least one point in the range
@@ -787,15 +828,17 @@ def default_frequency_range(syslist, Hz=None, number_of_samples=None,
787828
omega = sp.logspace(lsp_min, lsp_max, endpoint=True)
788829
return omega
789830

831+
790832
#
791-
# KLD 5/23/11: Two functions to create nice looking labels
833+
# Utility functions to create nice looking labels (KLD 5/23/11)
792834
#
793835

794836
def get_pow1000(num):
795837
"""Determine exponent for which significand of a number is within the
796838
range [1, 1000).
797839
"""
798-
# Based on algorithm from http://www.mail-archive.com/matplotlib-users@lists.sourceforge.net/msg14433.html, accessed 2010/11/7
840+
# Based on algorithm from http://www.mail-archive.com/
841+
# matplotlib-users@lists.sourceforge.net/msg14433.html, accessed 2010/11/7
799842
# by Jason Heeris 2009/11/18
800843
from decimal import Decimal
801844
from math import floor

0 commit comments

Comments
 (0)