diff --git a/control/config.py b/control/config.py index f61469394..02028cfba 100644 --- a/control/config.py +++ b/control/config.py @@ -114,11 +114,11 @@ def use_matlab_defaults(): The following conventions are used: * Bode plots plot gain in dB, phase in degrees, frequency in - Hertz, with grids + rad/sec, with grids * State space class and functions use Numpy matrix objects """ - set_defaults('bode', dB=True, deg=True, Hz=True, grid=True) + set_defaults('bode', dB=True, deg=True, Hz=False, grid=True) set_defaults('statesp', use_numpy_matrix=True) @@ -128,7 +128,7 @@ def use_fbs_defaults(): The following conventions are used: * Bode plots plot gain in powers of ten, phase in degrees, - frequency in Hertz, no grid + frequency in rad/sec, no grid """ set_defaults('bode', dB=False, deg=True, Hz=False, grid=False) diff --git a/control/freqplot.py b/control/freqplot.py index c8b513943..a1772fea7 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -80,7 +80,7 @@ def bode_plot(syslist, omega=None, - Plot=True, omega_limits=None, omega_num=None, + plot=True, omega_limits=None, omega_num=None, margins=None, *args, **kwargs): """Bode plot for a system @@ -100,7 +100,7 @@ def bode_plot(syslist, omega=None, deg : bool If True, plot phase in degrees (else radians). Default value (True) config.defaults['bode.deg'] - Plot : bool + plot : bool If True (default), plot magnitude and phase omega_limits: tuple, list, ... of two values Limits of the to generate frequency vector. @@ -110,9 +110,9 @@ def bode_plot(syslist, omega=None, config.defaults['freqplot.number_of_samples']. margins : bool If True, plot gain and phase margin. - *args - Additional arguments for :func:`matplotlib.plot` (color, linestyle, etc) - **kwargs: + *args : `matplotlib` plot positional properties, optional + Additional arguments for `matplotlib` plots (color, linestyle, etc) + **kwargs : `matplotlib` plot keyword properties, optional Additional keywords (passed to `matplotlib`) Returns @@ -153,12 +153,20 @@ def bode_plot(syslist, omega=None, # Make a copy of the kwargs dictonary since we will modify it kwargs = dict(kwargs) + # Check to see if legacy 'Plot' keyword was used + if 'Plot' in kwargs: + import warnings + warnings.warn("'Plot' keyword is deprecated in bode_plot; use 'plot'", + FutureWarning) + # Map 'Plot' keyword to 'plot' keyword + plot = kwargs.pop('Plot') + # Get values for params (and pop from list to allow keyword use in plot) dB = config._get_param('bode', 'dB', kwargs, _bode_defaults, pop=True) deg = config._get_param('bode', 'deg', kwargs, _bode_defaults, pop=True) Hz = config._get_param('bode', 'Hz', kwargs, _bode_defaults, pop=True) grid = config._get_param('bode', 'grid', kwargs, _bode_defaults, pop=True) - Plot = config._get_param('bode', 'grid', Plot, True) + plot = config._get_param('bode', 'grid', plot, True) margins = config._get_param('bode', 'margins', margins, False) # If argument was a singleton, turn it into a list @@ -211,7 +219,7 @@ def bode_plot(syslist, omega=None, # Get the dimensions of the current axis, which we will divide up # TODO: Not current implemented; just use subplot for now - if Plot: + if plot: nyquistfrq_plot = None if Hz: omega_plot = omega_sys / (2. * math.pi) @@ -429,12 +437,13 @@ def gen_zero_centered_series(val_min, val_max, period): else: return mags, phases, omegas + # # Nyquist plot # -def nyquist_plot(syslist, omega=None, Plot=True, - labelFreq=0, arrowhead_length=0.1, arrowhead_width=0.1, +def nyquist_plot(syslist, omega=None, plot=True, label_freq=0, + arrowhead_length=0.1, arrowhead_width=0.1, color=None, *args, **kwargs): """ Nyquist plot for a system @@ -451,13 +460,13 @@ def nyquist_plot(syslist, omega=None, Plot=True, If True, plot magnitude color : string Used to specify the color of the plot - labelFreq : int + label_freq : int Label every nth frequency on the plot arrowhead_width : arrow head width arrowhead_length : arrow head length - *args - Additional arguments for :func:`matplotlib.plot` (color, linestyle, etc) - **kwargs: + *args : `matplotlib` plot positional properties, optional + Additional arguments for `matplotlib` plots (color, linestyle, etc) + **kwargs : `matplotlib` plot keyword properties, optional Additional keywords (passed to `matplotlib`) Returns @@ -475,6 +484,22 @@ def nyquist_plot(syslist, omega=None, Plot=True, >>> real, imag, freq = nyquist_plot(sys) """ + # Check to see if legacy 'Plot' keyword was used + if 'Plot' in kwargs: + import warnings + warnings.warn("'Plot' keyword is deprecated in nyquist_plot; " + "use 'plot'", FutureWarning) + # Map 'Plot' keyword to 'plot' keyword + plot = kwargs.pop('Plot') + + # Check to see if legacy 'labelFreq' keyword was used + if 'labelFreq' in kwargs: + import warnings + warnings.warn("'labelFreq' keyword is deprecated in nyquist_plot; " + "use 'label_freq'", FutureWarning) + # Map 'labelFreq' keyword to 'label_freq' keyword + label_freq = kwargs.pop('labelFreq') + # If argument was a singleton, turn it into a list if not getattr(syslist, '__iter__', False): syslist = (syslist,) @@ -507,7 +532,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, x = sp.multiply(mag, sp.cos(phase)) y = sp.multiply(mag, sp.sin(phase)) - if Plot: + if plot: # Plot the primary curve and mirror image p = plt.plot(x, y, '-', color=color, *args, **kwargs) c = p[0].get_color() @@ -527,8 +552,8 @@ def nyquist_plot(syslist, omega=None, Plot=True, plt.plot([-1], [0], 'r+') # Label the frequencies of the points - if labelFreq: - ind = slice(None, None, labelFreq) + if label_freq: + ind = slice(None, None, label_freq) for xpt, ypt, omegapt in zip(x[ind], y[ind], omega[ind]): # Convert to Hz f = omegapt / (2 * sp.pi) @@ -550,7 +575,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, str(int(np.round(f / 1000 ** pow1000, 0))) + ' ' + prefix + 'Hz') - if Plot: + if plot: ax = plt.gca() ax.set_xlabel("Real axis") ax.set_ylabel("Imaginary axis") @@ -558,6 +583,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, return x, y, omega + # # Gang of Four plot # @@ -575,6 +601,8 @@ def gangof4_plot(P, C, omega=None, **kwargs): Linear input/output systems (process and control) omega : array Range of frequencies (list or bounds) in rad/sec + **kwargs : `matplotlib` plot keyword properties, optional + Additional keywords (passed to `matplotlib`) Returns ------- @@ -590,16 +618,16 @@ def gangof4_plot(P, C, omega=None, **kwargs): Hz = config._get_param('bode', 'Hz', kwargs, _bode_defaults, pop=True) grid = config._get_param('bode', 'grid', kwargs, _bode_defaults, pop=True) - # Select a default range if none is provided - # TODO: This needs to be made more intelligent - if omega is None: - omega = default_frequency_range((P, C)) - # Compute the senstivity functions L = P * C S = feedback(1, L) T = L * S + # Select a default range if none is provided + # TODO: This needs to be made more intelligent + if omega is None: + omega = default_frequency_range((P, C, S)) + # Set up the axes with labels so that multiple calls to # gangof4_plot will superimpose the data. See details in bode_plot. plot_axes = {'t': None, 's': None, 'ps': None, 'cs': None} @@ -628,36 +656,49 @@ def gangof4_plot(P, C, omega=None, **kwargs): # TODO: Need to add in the mag = 1 lines mag_tmp, phase_tmp, omega = S.freqresp(omega) mag = np.squeeze(mag_tmp) - plot_axes['s'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag) - plot_axes['s'].set_ylabel("$|S|$") + if dB: + plot_axes['s'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs) + else: + plot_axes['s'].loglog(omega_plot, mag, **kwargs) + plot_axes['s'].set_ylabel("$|S|$" + " (dB)" if dB else "") plot_axes['s'].tick_params(labelbottom=False) plot_axes['s'].grid(grid, which='both') mag_tmp, phase_tmp, omega = (P * S).freqresp(omega) mag = np.squeeze(mag_tmp) - plot_axes['ps'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag) + if dB: + plot_axes['ps'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs) + else: + plot_axes['ps'].loglog(omega_plot, mag, **kwargs) plot_axes['ps'].tick_params(labelbottom=False) - plot_axes['ps'].set_ylabel("$|PS|$") + plot_axes['ps'].set_ylabel("$|PS|$" + " (dB)" if dB else "") plot_axes['ps'].grid(grid, which='both') mag_tmp, phase_tmp, omega = (C * S).freqresp(omega) mag = np.squeeze(mag_tmp) - plot_axes['cs'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag) + if dB: + plot_axes['cs'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs) + else: + plot_axes['cs'].loglog(omega_plot, mag, **kwargs) plot_axes['cs'].set_xlabel( "Frequency (Hz)" if Hz else "Frequency (rad/sec)") - plot_axes['cs'].set_ylabel("$|CS|$") + plot_axes['cs'].set_ylabel("$|CS|$" + " (dB)" if dB else "") plot_axes['cs'].grid(grid, which='both') mag_tmp, phase_tmp, omega = T.freqresp(omega) mag = np.squeeze(mag_tmp) - plot_axes['t'].loglog(omega_plot, 20 * np.log10(mag) if dB else mag) + if dB: + plot_axes['t'].semilogx(omega_plot, 20 * np.log10(mag), **kwargs) + else: + plot_axes['t'].loglog(omega_plot, mag, **kwargs) plot_axes['t'].set_xlabel( "Frequency (Hz)" if Hz else "Frequency (rad/sec)") - plot_axes['t'].set_ylabel("$|T|$") + plot_axes['t'].set_ylabel("$|T|$" + " (dB)" if dB else "") plot_axes['t'].grid(grid, which='both') plt.tight_layout() + # # Utility functions # @@ -754,7 +795,7 @@ def default_frequency_range(syslist, Hz=None, number_of_samples=None, # TODO raise NotImplementedError( "type of system in not implemented now") - except: + except NotImplementedError: pass # 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, omega = sp.logspace(lsp_min, lsp_max, endpoint=True) return omega + # -# KLD 5/23/11: Two functions to create nice looking labels +# Utility functions to create nice looking labels (KLD 5/23/11) # def get_pow1000(num): """Determine exponent for which significand of a number is within the range [1, 1000). """ - # Based on algorithm from http://www.mail-archive.com/matplotlib-users@lists.sourceforge.net/msg14433.html, accessed 2010/11/7 + # Based on algorithm from http://www.mail-archive.com/ + # matplotlib-users@lists.sourceforge.net/msg14433.html, accessed 2010/11/7 # by Jason Heeris 2009/11/18 from decimal import Decimal from math import floor diff --git a/control/grid.py b/control/grid.py index ed46ff0f7..8aa583bc0 100644 --- a/control/grid.py +++ b/control/grid.py @@ -2,19 +2,22 @@ from numpy import cos, sin, sqrt, linspace, pi, exp import matplotlib.pyplot as plt from mpl_toolkits.axisartist import SubplotHost -from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear +from mpl_toolkits.axisartist.grid_helper_curvelinear \ + import GridHelperCurveLinear import mpl_toolkits.axisartist.angle_helper as angle_helper from matplotlib.projections import PolarAxes from matplotlib.transforms import Affine2D + class FormatterDMS(object): '''Transforms angle ticks to damping ratios''' - def __call__(self,direction,factor,values): + def __call__(self, direction, factor, values): angles_deg = values/factor - damping_ratios = np.cos((180-angles_deg)*np.pi/180) - ret = ["%.2f"%val for val in damping_ratios] + damping_ratios = np.cos((180-angles_deg) * np.pi/180) + ret = ["%.2f" % val for val in damping_ratios] return ret + class ModifiedExtremeFinderCycle(angle_helper.ExtremeFinderCycle): '''Changed to allow only left hand-side polar grid''' def __call__(self, transform_xy, x1, y1, x2, y2): @@ -25,10 +28,14 @@ def __call__(self, transform_xy, x1, y1, x2, y2): with np.errstate(invalid='ignore'): if self.lon_cycle is not None: lon0 = np.nanmin(lon) - lon -= 360. * ((lon - lon0) > 360.) # Changed from 180 to 360 to be able to span only 90-270 (left hand side) + # Changed from 180 to 360 to be able to span only + # 90-270 (left hand side) + lon -= 360. * ((lon - lon0) > 360.) if self.lat_cycle is not None: lat0 = np.nanmin(lat) - lat -= 360. * ((lat - lat0) > 360.) # Changed from 180 to 360 to be able to span only 90-270 (left hand side) + # Changed from 180 to 360 to be able to span only + # 90-270 (left hand side) + lat -= 360. * ((lat - lat0) > 360.) lon_min, lon_max = np.nanmin(lon), np.nanmax(lon) lat_min, lat_max = np.nanmin(lat), np.nanmax(lat) @@ -38,6 +45,7 @@ def __call__(self, transform_xy, x1, y1, x2, y2): return lon_min, lon_max, lat_min, lat_max + def sgrid(): # From matplotlib demos: # https://matplotlib.org/gallery/axisartist/demo_curvelinear_grid.html @@ -52,21 +60,17 @@ def sgrid(): # 20, 20 : number of sampling points along x, y direction sampling_points = 20 - extreme_finder = ModifiedExtremeFinderCycle(sampling_points, sampling_points, - lon_cycle=360, - lat_cycle=None, - lon_minmax=(90,270), - lat_minmax=(0, np.inf),) + extreme_finder = ModifiedExtremeFinderCycle( + sampling_points, sampling_points, lon_cycle=360, lat_cycle=None, + lon_minmax=(90, 270), lat_minmax=(0, np.inf),) grid_locator1 = angle_helper.LocatorDMS(15) tick_formatter1 = FormatterDMS() - grid_helper = GridHelperCurveLinear(tr, - extreme_finder=extreme_finder, - grid_locator1=grid_locator1, - tick_formatter1=tick_formatter1 - ) + grid_helper = GridHelperCurveLinear( + tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, + tick_formatter1=tick_formatter1) - fig = plt.figure() + fig = plt.gcf() ax = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) # make ticklabels of right invisible, and top axis visible. @@ -97,24 +101,25 @@ def sgrid(): fig.add_subplot(ax) - ### RECTANGULAR X Y AXES WITH SCALE - #par2 = ax.twiny() - #par2.axis["top"].toggle(all=False) - #par2.axis["right"].toggle(all=False) - #new_fixed_axis = par2.get_grid_helper().new_fixed_axis - #par2.axis["left"] = new_fixed_axis(loc="left", + # RECTANGULAR X Y AXES WITH SCALE + # par2 = ax.twiny() + # par2.axis["top"].toggle(all=False) + # par2.axis["right"].toggle(all=False) + # new_fixed_axis = par2.get_grid_helper().new_fixed_axis + # par2.axis["left"] = new_fixed_axis(loc="left", # axes=par2, # offset=(0, 0)) - #par2.axis["bottom"] = new_fixed_axis(loc="bottom", + # par2.axis["bottom"] = new_fixed_axis(loc="bottom", # axes=par2, # offset=(0, 0)) - ### FINISH RECTANGULAR + # FINISH RECTANGULAR - ax.grid(True, zorder=0,linestyle='dotted') + ax.grid(True, zorder=0, linestyle='dotted') _final_setup(ax) return ax, fig + def _final_setup(ax): ax.set_xlabel('Real') ax.set_ylabel('Imaginary') @@ -122,17 +127,19 @@ def _final_setup(ax): ax.axvline(x=0, color='black', lw=1) plt.axis('equal') + def nogrid(): - f = plt.figure() + f = plt.gcf() ax = plt.axes() _final_setup(ax) return ax, f + def zgrid(zetas=None, wns=None): '''Draws discrete damping and frequency grid''' - fig = plt.figure() + fig = plt.gcf() ax = fig.gca() # Constant damping lines @@ -141,42 +148,43 @@ def zgrid(zetas=None, wns=None): for zeta in zetas: # Calculate in polar coordinates factor = zeta/sqrt(1-zeta**2) - x = linspace(0, sqrt(1-zeta**2),200) + x = linspace(0, sqrt(1-zeta**2), 200) ang = pi*x mag = exp(-pi*factor*x) # Draw upper part in retangular coordinates xret = mag*cos(ang) yret = mag*sin(ang) - ax.plot(xret,yret, 'k:', lw=1) + ax.plot(xret, yret, 'k:', lw=1) # Draw lower part in retangular coordinates xret = mag*cos(-ang) yret = mag*sin(-ang) - ax.plot(xret,yret,'k:', lw=1) + ax.plot(xret, yret, 'k:', lw=1) # Annotation an_i = int(len(xret)/2.5) an_x = xret[an_i] an_y = yret[an_i] - ax.annotate(str(round(zeta,2)), xy=(an_x, an_y), xytext=(an_x, an_y), size=7) + ax.annotate(str(round(zeta, 2)), xy=(an_x, an_y), + xytext=(an_x, an_y), size=7) # Constant natural frequency lines if wns is None: wns = linspace(0, 1, 10) for a in wns: # Calculate in polar coordinates - x = linspace(-pi/2,pi/2,200) + x = linspace(-pi/2, pi/2, 200) ang = pi*a*sin(x) mag = exp(-pi*a*cos(x)) # Draw in retangular coordinates xret = mag*cos(ang) yret = mag*sin(ang) - ax.plot(xret,yret,'k:', lw=1) + ax.plot(xret, yret, 'k:', lw=1) # Annotation an_i = -1 an_x = xret[an_i] an_y = yret[an_i] num = '{:1.1f}'.format(a) - ax.annotate(r"$\frac{"+num+r"\pi}{T}$", xy=(an_x, an_y), xytext=(an_x, an_y), size=9) + ax.annotate(r"$\frac{"+num+r"\pi}{T}$", xy=(an_x, an_y), + xytext=(an_x, an_y), size=9) _final_setup(ax) return ax, fig - diff --git a/control/nichols.py b/control/nichols.py index 48abffa0a..c8a98ed5e 100644 --- a/control/nichols.py +++ b/control/nichols.py @@ -60,7 +60,7 @@ # Default parameters values for the nichols module _nichols_defaults = { - 'nichols.grid':True, + 'nichols.grid': True, } @@ -156,12 +156,13 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted'): # Default chart magnitudes # The key set of magnitudes are always generated, since this # guarantees a recognizable Nichols chart grid. - key_cl_mags = np.array([-40.0, -20.0, -12.0, -6.0, -3.0, -1.0, -0.5, 0.0, - 0.25, 0.5, 1.0, 3.0, 6.0, 12.0]) + key_cl_mags = np.array([-40.0, -20.0, -12.0, -6.0, -3.0, -1.0, -0.5, + 0.0, 0.25, 0.5, 1.0, 3.0, 6.0, 12.0]) + # Extend the range of magnitudes if necessary. The extended arange - # will end up empty if no extension is required. Assumes that closed-loop - # magnitudes are approximately aligned with open-loop magnitudes beyond - # the value of np.min(key_cl_mags) + # will end up empty if no extension is required. Assumes that + # closed-loop magnitudes are approximately aligned with open-loop + # magnitudes beyond the value of np.min(key_cl_mags) cl_mag_step = -20.0 # dB extended_cl_mags = np.arange(np.min(key_cl_mags), ol_mag_min + cl_mag_step, cl_mag_step) @@ -171,7 +172,8 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted'): if cl_phases is None: # Choose a reasonable set of default phases (denser if the open-loop # data is restricted to a relatively small range of phases). - key_cl_phases = np.array([-0.25, -45.0, -90.0, -180.0, -270.0, -325.0, -359.75]) + key_cl_phases = np.array([-0.25, -45.0, -90.0, -180.0, -270.0, + -325.0, -359.75]) if np.abs(ol_phase_max - ol_phase_min) < 90.0: other_cl_phases = np.arange(-10.0, -360.0, -10.0) else: @@ -181,7 +183,8 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted'): assert ((-360.0 < np.min(cl_phases)) and (np.max(cl_phases) < 0.0)) # Find the M-contours - m = m_circles(cl_mags, phase_min=np.min(cl_phases), phase_max=np.max(cl_phases)) + m = m_circles(cl_mags, phase_min=np.min(cl_phases), + phase_max=np.max(cl_phases)) m_mag = 20*sp.log10(np.abs(m)) m_phase = sp.mod(sp.degrees(sp.angle(m)), -360.0) # Unwrap @@ -208,9 +211,11 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted'): linestyle=line_style, zorder=0) # Add magnitude labels - for x, y, m in zip(m_phase[:][-1] + phase_offset, m_mag[:][-1], cl_mags): + for x, y, m in zip(m_phase[:][-1] + phase_offset, m_mag[:][-1], + cl_mags): align = 'right' if m < 0.0 else 'left' - plt.text(x, y, str(m) + ' dB', size='small', ha=align, color='gray') + plt.text(x, y, str(m) + ' dB', size='small', ha=align, + color='gray') # Fit axes to generated chart plt.axis([phase_offset_min - 360.0, phase_offset_max - 360.0, diff --git a/control/pzmap.py b/control/pzmap.py index a8fb990b5..82960270f 100644 --- a/control/pzmap.py +++ b/control/pzmap.py @@ -1,7 +1,7 @@ # pzmap.py - computations involving poles and zeros # # Author: Richard M. Murray -# Date: 7 Sep 09 +# Date: 7 Sep 2009 # # This file contains functions that compute poles, zeros and related # quantities for a linear system. @@ -38,7 +38,6 @@ # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $Id:pzmap.py 819 2009-05-29 21:28:07Z murray $ from numpy import real, imag, linspace, exp, cos, sin, sqrt from math import pi @@ -51,15 +50,15 @@ # Define default parameter values for this module _pzmap_defaults = { - 'pzmap.grid':False, # Plot omega-damping grid - 'pzmap.Plot':True, # Generate plot using Matplotlib + 'pzmap.grid': False, # Plot omega-damping grid + 'pzmap.plot': True, # Generate plot using Matplotlib } # TODO: Implement more elegant cross-style axes. See: # http://matplotlib.sourceforge.net/examples/axes_grid/demo_axisline_style.html # http://matplotlib.sourceforge.net/examples/axes_grid/demo_curvelinear_grid.html -def pzmap(sys, Plot=True, grid=False, title='Pole Zero Map'): +def pzmap(sys, plot=True, grid=False, title='Pole Zero Map', **kwargs): """ Plot a pole/zero map for a linear system. @@ -67,7 +66,7 @@ def pzmap(sys, Plot=True, grid=False, title='Pole Zero Map'): ---------- sys: LTI (StateSpace or TransferFunction) Linear system for which poles and zeros are computed. - Plot: bool + plot: bool If ``True`` a graph is generated with Matplotlib, otherwise the poles and zeros are only computed and returned. grid: boolean (default = False) @@ -80,17 +79,24 @@ def pzmap(sys, Plot=True, grid=False, title='Pole Zero Map'): zeros: array The system's zeros. """ + # Check to see if legacy 'Plot' keyword was used + if 'Plot' in kwargs: + import warnings + warnings.warn("'Plot' keyword is deprecated in pzmap; use 'plot'", + FutureWarning) + plot = kwargs['Plot'] + # Get parameter values - Plot = config._get_param('rlocus', 'Plot', Plot, True) + plot = config._get_param('rlocus', 'plot', plot, True) grid = config._get_param('rlocus', 'grid', grid, False) - + if not isinstance(sys, LTI): raise TypeError('Argument ``sys``: must be a linear system.') poles = sys.pole() zeros = sys.zero() - if (Plot): + if (plot): import matplotlib.pyplot as plt if grid: @@ -103,11 +109,11 @@ def pzmap(sys, Plot=True, grid=False, title='Pole Zero Map'): # Plot the locations of the poles and zeros if len(poles) > 0: - ax.scatter(real(poles), imag(poles), s=50, marker='x', facecolors='k') + ax.scatter(real(poles), imag(poles), s=50, marker='x', + facecolors='k') if len(zeros) > 0: ax.scatter(real(zeros), imag(zeros), s=50, marker='o', - facecolors='none', edgecolors='k') - + facecolors='none', edgecolors='k') plt.title(title) diff --git a/control/rlocus.py b/control/rlocus.py index 0c115c26e..955c5c56d 100644 --- a/control/rlocus.py +++ b/control/rlocus.py @@ -62,16 +62,16 @@ # Default values for module parameters _rlocus_defaults = { - 'rlocus.grid':True, - 'rlocus.plotstr':'b' if int(matplotlib.__version__[0]) == 1 else 'C0', - 'rlocus.PrintGain':True, - 'rlocus.Plot':True + 'rlocus.grid': True, + 'rlocus.plotstr': 'b' if int(matplotlib.__version__[0]) == 1 else 'C0', + 'rlocus.print_gain': True, + 'rlocus.plot': True } # Main function: compute a root locus diagram def root_locus(sys, kvect=None, xlim=None, ylim=None, - plotstr=None, Plot=True, PrintGain=None, grid=None, **kwargs): + plotstr=None, plot=True, print_gain=None, grid=None, **kwargs): """Root locus plot @@ -89,9 +89,9 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, Set limits of x axis, normally with tuple (see matplotlib.axes). ylim : tuple or list, optional Set limits of y axis, normally with tuple (see matplotlib.axes). - Plot : boolean, optional + plot : boolean, optional If True (default), plot root locus diagram. - PrintGain : bool + print_gain : bool If True (default), report mouse clicks when close to the root locus branches, calculate gain, damping and print. grid : bool @@ -104,11 +104,27 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, klist : ndarray or list Gains used. Same as klist keyword argument if provided. """ + # Check to see if legacy 'Plot' keyword was used + if 'Plot' in kwargs: + import warnings + warnings.warn("'Plot' keyword is deprecated in root_locus; " + "use 'plot'", FutureWarning) + # Map 'Plot' keyword to 'plot' keyword + plot = kwargs.pop('Plot') + + # Check to see if legacy 'PrintGain' keyword was used + if 'PrintGain' in kwargs: + import warnings + warnings.warn("'PrintGain' keyword is deprecated in root_locus; " + "use 'print_gain'", FutureWarning) + # Map 'PrintGain' keyword to 'print_gain' keyword + print_gain = kwargs.pop('PrintGain') + # Get parameter values plotstr = config._get_param('rlocus', 'plotstr', plotstr, _rlocus_defaults) grid = config._get_param('rlocus', 'grid', grid, _rlocus_defaults) - PrintGain = config._get_param( - 'rlocus', 'PrintGain', PrintGain, _rlocus_defaults) + print_gain = config._get_param( + 'rlocus', 'print_gain', print_gain, _rlocus_defaults) # Convert numerator and denominator to polynomials if they aren't (nump, denp) = _systopoly1d(sys) @@ -125,7 +141,7 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, sisotool = False if 'sisotool' not in kwargs else True # Create the Plot - if Plot: + if plot: if sisotool: f = kwargs['fig'] ax = f.axes[1] @@ -143,7 +159,7 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, f = pylab.figure(new_figure_name) ax = pylab.axes() - if PrintGain and not sisotool: + if print_gain and not sisotool: f.canvas.mpl_connect( 'button_release_event', partial(_RLClickDispatcher, sys=sys, fig=f, diff --git a/control/statesp.py b/control/statesp.py index 85d48882a..1779dfbfd 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -70,7 +70,7 @@ # Define module default parameter values _statesp_defaults = { - 'statesp.use_numpy_matrix':True, + 'statesp.use_numpy_matrix': True, } diff --git a/control/tests/config_test.py b/control/tests/config_test.py index c0fc9755b..7b70fdc00 100644 --- a/control/tests/config_test.py +++ b/control/tests/config_test.py @@ -107,8 +107,8 @@ def test_matlab_bode(self): mag_data = mag_line[0].get_data() mag_x, mag_y = mag_data - # Make sure the x-axis is in Hertz and y-axis is in dB - np.testing.assert_almost_equal(mag_x[0], 0.001 / (2*pi), decimal=6) + # Make sure the x-axis is in rad/sec and y-axis is in dB + np.testing.assert_almost_equal(mag_x[0], 0.001, decimal=6) np.testing.assert_almost_equal(mag_y[0], 20*log10(10), decimal=3) # Get the phase line @@ -117,8 +117,8 @@ def test_matlab_bode(self): phase_data = phase_line[0].get_data() phase_x, phase_y = phase_data - # Make sure the x-axis is in Hertz and y-axis is in degrees - np.testing.assert_almost_equal(phase_x[-1], 1000 / (2*pi), decimal=1) + # Make sure the x-axis is in rad/sec and y-axis is in degrees + np.testing.assert_almost_equal(phase_x[-1], 1000, decimal=1) np.testing.assert_almost_equal(phase_y[-1], -180, decimal=0) # Override the defaults and make sure that works as well diff --git a/control/tests/convert_test.py b/control/tests/convert_test.py index 0340fa718..17766b186 100644 --- a/control/tests/convert_test.py +++ b/control/tests/convert_test.py @@ -108,7 +108,7 @@ def testConvert(self): ssorig_mag, ssorig_phase, ssorig_omega = \ bode(_mimo2siso(ssOriginal, \ inputNum, outputNum), \ - deg=False, Plot=False) + deg=False, plot=False) ssorig_real = ssorig_mag * np.cos(ssorig_phase) ssorig_imag = ssorig_mag * np.sin(ssorig_phase) @@ -121,7 +121,7 @@ def testConvert(self): tforig_mag, tforig_phase, tforig_omega = \ bode(tforig, ssorig_omega, \ - deg=False, Plot=False) + deg=False, plot=False) tforig_real = tforig_mag * np.cos(tforig_phase) tforig_imag = tforig_mag * np.sin(tforig_phase) @@ -137,7 +137,7 @@ def testConvert(self): bode(_mimo2siso(ssTransformed, \ inputNum, outputNum), \ ssorig_omega, \ - deg=False, Plot=False) + deg=False, plot=False) ssxfrm_real = ssxfrm_mag * np.cos(ssxfrm_phase) ssxfrm_imag = ssxfrm_mag * np.sin(ssxfrm_phase) np.testing.assert_array_almost_equal( \ @@ -152,7 +152,7 @@ def testConvert(self): tfxfrm = tf(num, den) tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \ bode(tfxfrm, ssorig_omega, \ - deg=False, Plot=False) + deg=False, plot=False) tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase) tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase) diff --git a/control/tests/matlab_test.py b/control/tests/matlab_test.py index 0e7060bea..fdbad744e 100644 --- a/control/tests/matlab_test.py +++ b/control/tests/matlab_test.py @@ -132,7 +132,7 @@ def testPZmap(self): # pzmap(self.siso_ss2); not implemented pzmap(self.siso_tf1); pzmap(self.siso_tf2); - pzmap(self.siso_tf2, Plot=False); + pzmap(self.siso_tf2, plot=False); def testStep(self): t = np.linspace(0, 1, 10) @@ -326,7 +326,7 @@ def testBode(self): bode(self.siso_ss1) bode(self.siso_tf1) bode(self.siso_tf2) - (mag, phase, freq) = bode(self.siso_tf2, Plot=False) + (mag, phase, freq) = bode(self.siso_tf2, plot=False) bode(self.siso_tf1, self.siso_tf2) w = logspace(-3, 3); bode(self.siso_ss1, w) @@ -339,7 +339,7 @@ def testRlocus(self): rlocus(self.siso_tf1) rlocus(self.siso_tf2) klist = [1, 10, 100] - rlist, klist_out = rlocus(self.siso_tf2, klist, Plot=False) + rlist, klist_out = rlocus(self.siso_tf2, klist, plot=False) np.testing.assert_equal(len(rlist), len(klist)) np.testing.assert_array_equal(klist, klist_out) @@ -349,7 +349,7 @@ def testNyquist(self): nyquist(self.siso_tf2) w = logspace(-3, 3); nyquist(self.siso_tf2, w) - (real, imag, freq) = nyquist(self.siso_tf2, w, Plot=False) + (real, imag, freq) = nyquist(self.siso_tf2, w, plot=False) def testNichols(self): nichols(self.siso_ss1) diff --git a/control/tests/rlocus_test.py b/control/tests/rlocus_test.py index 464f04066..4b2112ea3 100644 --- a/control/tests/rlocus_test.py +++ b/control/tests/rlocus_test.py @@ -35,14 +35,14 @@ def testRootLocus(self): """Basic root locus plot""" klist = [-1, 0, 1] for sys in self.systems: - roots, k_out = root_locus(sys, klist, Plot=False) + roots, k_out = root_locus(sys, klist, plot=False) np.testing.assert_equal(len(roots), len(klist)) np.testing.assert_array_equal(klist, k_out) self.check_cl_poles(sys, roots, klist) def test_without_gains(self): for sys in self.systems: - roots, kvect = root_locus(sys, Plot=False) + roots, kvect = root_locus(sys, plot=False) self.check_cl_poles(sys, roots, kvect) def test_root_locus_zoom(self): diff --git a/control/tests/slycot_convert_test.py b/control/tests/slycot_convert_test.py index eab178954..1c121b1f6 100644 --- a/control/tests/slycot_convert_test.py +++ b/control/tests/slycot_convert_test.py @@ -154,19 +154,19 @@ def testFreqResp(self): for inputNum in range(inputs): for outputNum in range(outputs): [ssOriginalMag, ssOriginalPhase, freq] =\ - matlab.bode(ssOriginal, Plot=False) + matlab.bode(ssOriginal, plot=False) [tfOriginalMag, tfOriginalPhase, freq] =\ matlab.bode(matlab.tf( numOriginal[outputNum][inputNum], - denOriginal[outputNum]), Plot=False) + denOriginal[outputNum]), plot=False) [ssTransformedMag, ssTransformedPhase, freq] =\ matlab.bode(ssTransformed, - freq, Plot=False) + freq, plot=False) [tfTransformedMag, tfTransformedPhase, freq] =\ matlab.bode(matlab.tf( numTransformed[outputNum][inputNum], denTransformed[outputNum]), - freq, Plot=False) + freq, plot=False) # print('numOrig=', # numOriginal[outputNum][inputNum]) # print('denOrig=', diff --git a/examples/pvtol-nested.py b/examples/pvtol-nested.py index 56685599b..7efce9ccd 100644 --- a/examples/pvtol-nested.py +++ b/examples/pvtol-nested.py @@ -26,10 +26,6 @@ Pi = tf([r], [J, 0, 0]) # inner loop (roll) Po = tf([1], [m, c, 0]) # outer loop (position) -# Use state space versions -Pi = tf2ss(Pi) -Po = tf2ss(Po) - # # Inner loop control design # @@ -170,7 +166,7 @@ plt.figure(10) plt.clf() -P, Z = pzmap(T, Plot=True) +P, Z = pzmap(T, plot=True, grid=True) print("Closed loop poles and zeros: ", P, Z) # Gang of Four
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: