From 4f864248ceaa05b83692b820db1cb1766bba25c8 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 20 Jan 2020 08:01:25 -0800 Subject: [PATCH 1/7] fix sgrid, zgrid to use existing axes if they exist + PEP8 cleanup --- control/grid.py | 80 +++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 36 deletions(-) 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 - From 1e6c3630552ad95c80fbe7d40f5b087f8c20eaff Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 20 Jan 2020 08:03:18 -0800 Subject: [PATCH 2/7] change plot and print_gain keywords to lower case, with deprecation warning + fix bugs in gangof4 using dB + PEP8, docstring cleanup --- control/freqplot.py | 97 ++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/control/freqplot.py b/control/freqplot.py index c8b513943..e0a528eff 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,11 +110,11 @@ 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`) - +w Returns ------- mag : array (list if len(syslist) > 1) @@ -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, labelFreq=0, + arrowhead_length=0.1, arrowhead_width=0.1, color=None, *args, **kwargs): """ Nyquist plot for a system @@ -455,9 +464,9 @@ def nyquist_plot(syslist, omega=None, Plot=True, 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,14 @@ 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') + # If argument was a singleton, turn it into a list if not getattr(syslist, '__iter__', False): syslist = (syslist,) @@ -507,7 +524,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() @@ -550,7 +567,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 +575,7 @@ def nyquist_plot(syslist, omega=None, Plot=True, return x, y, omega + # # Gang of Four plot # @@ -575,6 +593,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 +610,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 +648,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 +787,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 +820,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 From 06c2b6641bf1cf673c3e2b7f7bee6884ca7909c0 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 20 Jan 2020 08:04:21 -0800 Subject: [PATCH 3/7] remove converstion to state space that was giving spurious zero at infinity --- examples/pvtol-nested.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 From 4450cf8bd45b9bf27e56e1ce8f1b62654b73b303 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 20 Jan 2020 08:05:07 -0800 Subject: [PATCH 4/7] change plot and print_gain keywords to lower case, with deprecation warning --- control/pzmap.py | 25 ++++++++++++-------- control/rlocus.py | 34 ++++++++++++++++++++-------- control/tests/convert_test.py | 8 +++---- control/tests/matlab_test.py | 8 +++---- control/tests/rlocus_test.py | 4 ++-- control/tests/slycot_convert_test.py | 8 +++---- 6 files changed, 55 insertions(+), 32 deletions(-) diff --git a/control/pzmap.py b/control/pzmap.py index a8fb990b5..f91e6fa28 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 @@ -52,14 +51,14 @@ # Define default parameter values for this module _pzmap_defaults = { 'pzmap.grid':False, # Plot omega-damping grid - 'pzmap.Plot':True, # Generate plot using Matplotlib + '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,8 +79,15 @@ 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): @@ -90,7 +96,7 @@ def pzmap(sys, Plot=True, grid=False, title='Pole Zero Map'): poles = sys.pole() zeros = sys.zero() - if (Plot): + if (plot): import matplotlib.pyplot as plt if grid: @@ -103,10 +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..562f54899 100644 --- a/control/rlocus.py +++ b/control/rlocus.py @@ -64,14 +64,14 @@ _rlocus_defaults = { 'rlocus.grid':True, 'rlocus.plotstr':'b' if int(matplotlib.__version__[0]) == 1 else 'C0', - 'rlocus.PrintGain':True, - 'rlocus.Plot':True + '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/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=', From dcf12fd496d3e93dfe57bafc3a0f747939f2a797 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 20 Jan 2020 08:12:49 -0800 Subject: [PATCH 5/7] PEP8 cleanup --- control/nichols.py | 25 +++++++++++++++---------- control/pzmap.py | 7 +++---- control/rlocus.py | 10 +++++----- control/statesp.py | 2 +- 4 files changed, 24 insertions(+), 20 deletions(-) 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 f91e6fa28..82960270f 100644 --- a/control/pzmap.py +++ b/control/pzmap.py @@ -50,8 +50,8 @@ # 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 } @@ -89,7 +89,7 @@ def pzmap(sys, plot=True, grid=False, title='Pole Zero Map', **kwargs): # Get parameter values 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.') @@ -115,7 +115,6 @@ def pzmap(sys, plot=True, grid=False, title='Pole Zero Map', **kwargs): ax.scatter(real(zeros), imag(zeros), s=50, marker='o', facecolors='none', edgecolors='k') - plt.title(title) # Return locations of poles and zeros as a tuple diff --git a/control/rlocus.py b/control/rlocus.py index 562f54899..955c5c56d 100644 --- a/control/rlocus.py +++ b/control/rlocus.py @@ -62,10 +62,10 @@ # Default values for module parameters _rlocus_defaults = { - 'rlocus.grid':True, - 'rlocus.plotstr':'b' if int(matplotlib.__version__[0]) == 1 else 'C0', - 'rlocus.print_gain':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 } @@ -110,7 +110,7 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, warnings.warn("'Plot' keyword is deprecated in root_locus; " "use 'plot'", FutureWarning) # Map 'Plot' keyword to 'plot' keyword - plot= kwargs.pop('Plot') + plot = kwargs.pop('Plot') # Check to see if legacy 'PrintGain' keyword was used if 'PrintGain' in kwargs: 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, } From 249d85e854e76c40e8b0cafdc4a6a4108ae7dcce Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 21 Mar 2020 09:42:55 -0700 Subject: [PATCH 6/7] use Hz=False for MATLAB + docstring corrections --- control/config.py | 6 +++--- control/tests/config_test.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) 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/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 From 4267eea09fa33db9485e2937268fc72183399a8f Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 21 Mar 2020 18:42:04 -0700 Subject: [PATCH 7/7] labelFreq -> label_freq in nyquist_plot() w/ deprecation warning + remove extraneous docstring text --- control/freqplot.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/control/freqplot.py b/control/freqplot.py index e0a528eff..a1772fea7 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -114,7 +114,7 @@ def bode_plot(syslist, omega=None, Additional arguments for `matplotlib` plots (color, linestyle, etc) **kwargs : `matplotlib` plot keyword properties, optional Additional keywords (passed to `matplotlib`) -w + Returns ------- mag : array (list if len(syslist) > 1) @@ -442,7 +442,7 @@ def gen_zero_centered_series(val_min, val_max, period): # Nyquist plot # -def nyquist_plot(syslist, omega=None, plot=True, labelFreq=0, +def nyquist_plot(syslist, omega=None, plot=True, label_freq=0, arrowhead_length=0.1, arrowhead_width=0.1, color=None, *args, **kwargs): """ @@ -460,7 +460,7 @@ def nyquist_plot(syslist, omega=None, plot=True, labelFreq=0, 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 @@ -492,6 +492,14 @@ def nyquist_plot(syslist, omega=None, plot=True, labelFreq=0, # 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,) @@ -544,8 +552,8 @@ def nyquist_plot(syslist, omega=None, plot=True, labelFreq=0, 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) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy