GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — python-control/control/nichols.py at 0.9.1 · python-control/python-control Expand file tree/ Copy pathnichols.pyMore file actions 308 lines (256 loc) · 10.7 KB / Copy pathnichols.pyFile metadata and controls308 lines (256 loc) · 10.7 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 """nichols.py Functions for plotting Black-Nichols charts. Routines in this module: nichols.nichols_plot aliased as nichols.nichols nichols.nichols_grid """ # nichols.py - Nichols plot # # Contributed by Allan McInnes <[email protected]> # # This file contains some standard control system plots: Bode plots, # Nyquist plots, Nichols plots and pole-zero diagrams # # Copyright (c) 2010 by California Institute of Technology # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # 3. Neither the name of the California Institute of Technology nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH # OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $Id: freqplot.py 139 2011-03-30 16:19:59Z murrayrm $ import numpy as np import matplotlib.pyplot as plt from .ctrlutil import unwrap from .freqplot import _default_frequency_range from . import config __all__ = ['nichols_plot', 'nichols', 'nichols_grid'] # Default parameters values for the nichols module _nichols_defaults = { 'nichols.grid': True, } def nichols_plot(sys_list, omega=None, grid=None): """Nichols plot for a system Plots a Nichols plot for the system over a (optional) frequency range. Parameters ---------- sys_list : list of LTI, or LTI List of linear input/output systems (single system is OK) omega : array_like Range of frequencies (list or bounds) in rad/sec grid : boolean, optional True if the plot should include a Nichols-chart grid. Default is True. Returns ------- None """ # Get parameter values grid = config._get_param('nichols', 'grid', grid, True) # If argument was a singleton, turn it into a list if not getattr(sys_list, '__iter__', False): sys_list = (sys_list,) # Select a default range if none is provided if omega is None: omega = _default_frequency_range(sys_list) for sys in sys_list: # Get the magnitude and phase of the system mag_tmp, phase_tmp, omega = sys.frequency_response(omega) mag = np.squeeze(mag_tmp) phase = np.squeeze(phase_tmp) # Convert to Nichols-plot format (phase in degrees, # and magnitude in dB) x = unwrap(np.degrees(phase), 360) y = 20*np.log10(mag) # Generate the plot plt.plot(x, y) plt.xlabel('Phase (deg)') plt.ylabel('Magnitude (dB)') plt.title('Nichols Plot') # Mark the -180 point plt.plot([-180], [0], 'r+') # Add grid if grid: nichols_grid() def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted'): """Nichols chart grid Plots a Nichols chart grid on the current axis, or creates a new chart if no plot already exists. Parameters ---------- cl_mags : array-like (dB), optional Array of closed-loop magnitudes defining the iso-gain lines on a custom Nichols chart. cl_phases : array-like (degrees), optional Array of closed-loop phases defining the iso-phase lines on a custom Nichols chart. Must be in the range -360 < cl_phases < 0 line_style : string, optional :doc:`Matplotlib linestyle \ <matplotlib:gallery/lines_bars_and_markers/linestyles>` """ # Default chart size ol_phase_min = -359.99 ol_phase_max = 0.0 ol_mag_min = -40.0 ol_mag_max = default_ol_mag_max = 50.0 # Find bounds of the current dataset, if there is one. if plt.gcf().gca().has_data(): ol_phase_min, ol_phase_max, ol_mag_min, ol_mag_max = plt.axis() # M-circle magnitudes. if cl_mags is None: # 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]) # 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) 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) cl_mags = np.concatenate((extended_cl_mags, key_cl_mags)) # N-circle phases (should be in the range -360 to 0) 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]) if np.abs(ol_phase_max - ol_phase_min) < 90.0: other_cl_phases = np.arange(-10.0, -360.0, -10.0) else: other_cl_phases = np.arange(-10.0, -360.0, -20.0) cl_phases = np.concatenate((key_cl_phases, other_cl_phases)) else: 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_mag = 20*np.log10(np.abs(m)) m_phase = np.mod(np.degrees(np.angle(m)), -360.0) # Unwrap # Find the N-contours n = n_circles(cl_phases, mag_min=np.min(cl_mags), mag_max=np.max(cl_mags)) n_mag = 20*np.log10(np.abs(n)) n_phase = np.mod(np.degrees(np.angle(n)), -360.0) # Unwrap # Plot the contours behind other plot elements. # The "phase offset" is used to produce copies of the chart that cover # the entire range of the plotted data, starting from a base chart computed # over the range -360 < phase < 0. Given the range # the base chart is computed over, the phase offset should be 0 # for -360 < ol_phase_min < 0. phase_offset_min = 360.0*np.ceil(ol_phase_min/360.0) phase_offset_max = 360.0*np.ceil(ol_phase_max/360.0) + 360.0 phase_offsets = np.arange(phase_offset_min, phase_offset_max, 360.0) for phase_offset in phase_offsets: # Draw M and N contours plt.plot(m_phase + phase_offset, m_mag, color='lightgray', linestyle=line_style, zorder=0) plt.plot(n_phase + phase_offset, n_mag, color='lightgray', linestyle=line_style, zorder=0) # Add magnitude labels 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') # Fit axes to generated chart plt.axis([phase_offset_min - 360.0, phase_offset_max - 360.0, np.min(cl_mags), np.max([ol_mag_max, default_ol_mag_max])]) # # Utility functions # # This section of the code contains some utility functions for # generating Nichols plots # def closed_loop_contours(Gcl_mags, Gcl_phases): """Contours of the function Gcl = Gol/(1+Gol), where Gol is an open-loop transfer function, and Gcl is a corresponding closed-loop transfer function. Parameters ---------- Gcl_mags : array-like Array of magnitudes of the contours Gcl_phases : array-like Array of phases in radians of the contours Returns ------- contours : complex array Array of complex numbers corresponding to the contours. """ # Compute the contours in Gcl-space. Since we're given closed-loop # magnitudes and phases, this is just a case of converting them into # a complex number. Gcl = Gcl_mags*np.exp(1.j*Gcl_phases) # Invert Gcl = Gol/(1+Gol) to map the contours into the open-loop space return Gcl/(1.0 - Gcl) def m_circles(mags, phase_min=-359.75, phase_max=-0.25): """Constant-magnitude contours of the function Gcl = Gol/(1+Gol), where Gol is an open-loop transfer function, and Gcl is a corresponding closed-loop transfer function. Parameters ---------- mags : array-like Array of magnitudes in dB of the M-circles phase_min : degrees Minimum phase in degrees of the N-circles phase_max : degrees Maximum phase in degrees of the N-circles Returns ------- contours : complex array Array of complex numbers corresponding to the contours. """ # Convert magnitudes and phase range into a grid suitable for # building contours phases = np.radians(np.linspace(phase_min, phase_max, 2000)) Gcl_mags, Gcl_phases = np.meshgrid(10.0**(mags/20.0), phases) return closed_loop_contours(Gcl_mags, Gcl_phases) def n_circles(phases, mag_min=-40.0, mag_max=12.0): """Constant-phase contours of the function Gcl = Gol/(1+Gol), where Gol is an open-loop transfer function, and Gcl is a corresponding closed-loop transfer function. Parameters ---------- phases : array-like Array of phases in degrees of the N-circles mag_min : dB Minimum magnitude in dB of the N-circles mag_max : dB Maximum magnitude in dB of the N-circles Returns ------- contours : complex array Array of complex numbers corresponding to the contours. """ # Convert phases and magnitude range into a grid suitable for # building contours mags = np.linspace(10**(mag_min/20.0), 10**(mag_max/20.0), 2000) Gcl_phases, Gcl_mags = np.meshgrid(np.radians(phases), mags) return closed_loop_contours(Gcl_mags, Gcl_phases) # Function aliases nichols = nichols_plot |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |