def check_unwrap_array(self, angle, period=None): if period is None: angle_mod = angle % (2 * np.pi) angle_unwrap = unwrap(angle_mod) else: angle_mod = angle % period angle_unwrap = unwrap(angle_mod, period) np.testing.assert_array_almost_equal(angle_unwrap, angle)
def nichols_plot(syslist, omega=None, grid=True): """Nichols plot for a system Plots a Nichols plot for the system over a (optional) frequency range. Parameters ---------- syslist : 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 """ # If argument was a singleton, turn it into a list if (not getattr(syslist, '__iter__', False)): syslist = (syslist,) # Select a default range if none is provided if omega is None: omega = default_frequency_range(syslist) for sys in syslist: # Get the magnitude and phase of the system mag_tmp, phase_tmp, omega = sys.freqresp(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(sp.degrees(phase), 360) y = 20*sp.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 plot(self, syslist, omega=None, dB=None, Hz=None, deg=None, Plot=True, *args, **kwargs): self.axes1.clear() self.axes1.grid(True, color='gray') self.axes1.set_ylabel("Magnitude (dB)" if dB else "Magnitude") self.axes2.clear() self.axes2.grid(True, color='gray') self.axes2.set_ylabel("Phase (deg)" if deg else "Phase (rad)") self.axes2.set_xlabel( "Frequency (Hz)" if Hz else "Frequency (rad/sec)") if (dB is None): dB = control.config.bode_dB if (deg is None): deg = control.config.bode_deg if (Hz is None): Hz = control.config.bode_Hz if (not getattr(syslist, '__iter__', False)): syslist = (syslist, ) mags, phases, omegas = [], [], [] if (omega == None): omega = default_frequency_range(syslist) mag_tmp, phase_tmp, omega = syslist[0].freqresp(omega) mag = np.atleast_1d(np.squeeze(mag_tmp)) phase = np.atleast_1d(np.squeeze(phase_tmp)) phase = unwrap(phase) if Hz: omega = omega / (2 * sp.pi) if dB: mag = 20 * sp.log10(mag) if deg: phase = phase * 180 / sp.pi mags.append(mag) phases.append(phase) omegas.append(omega) if dB: self.axes1.semilogx(omega, mag, *args, **kwargs) else: self.axes1.loglog(omega, mag, *args, **kwargs) self.axes2.semilogx(omega, phase, *args, **kwargs) self.canvas = FigCanvas(self, -1, self.fig) self.canvas.draw()
def plot(self, syslist, omega=None, dB=None, Hz=None, deg=None, Plot=True, *args , **kwargs): self.axes1.clear() self.axes1.grid(True , color='gray') self.axes1.set_ylabel("Magnitude (dB)" if dB else "Magnitude") self.axes2.clear() self.axes2.grid(True , color='gray') self.axes2.set_ylabel("Phase (deg)" if deg else "Phase (rad)") self.axes2.set_xlabel("Frequency (Hz)" if Hz else "Frequency (rad/sec)") if (dB is None): dB = control.config.bode_dB if (deg is None): deg = control.config.bode_deg if (Hz is None): Hz = control.config.bode_Hz if (not getattr(syslist, '__iter__', False)): syslist = (syslist,) mags, phases, omegas = [], [], [] if (omega == None): omega = default_frequency_range(syslist) mag_tmp, phase_tmp, omega = syslist[0].freqresp(omega) mag = np.atleast_1d(np.squeeze(mag_tmp)) phase = np.atleast_1d(np.squeeze(phase_tmp)) phase = unwrap(phase) if Hz: omega = omega/(2*sp.pi) if dB: mag = 20*sp.log10(mag) if deg: phase = phase * 180 / sp.pi mags.append(mag) phases.append(phase) omegas.append(omega) if dB: self.axes1.semilogx(omega,mag,*args,**kwargs) else: self.axes1.loglog(omega,mag,*args,**kwargs) self.axes2.semilogx(omega,phase,*args,**kwargs) self.canvas = FigCanvas(self, -1, self.fig) self.canvas.draw()
def bode_plot(mag, phase, f, in_Hz=True, in_dB=True, in_deg=True, label=None, axes=None, *args, **kwargs): r"""Create a Bode plot for a system. **Arguments:** - *sys*: Linear input/output system (Lti) - *freqs*: List or frequencies or tuple of (min, max) frequencies over which to plot the system response. If *freqs* is *None*, then an appropriate range will be determined automatically. - *in_Hz*: If *True*, the frequencies (*freqs*) are in Hz and should be plotted in Hz (otherwise, rad/s) - *in_dB*: If *True*, plot the magnitude in dB - *in_deg*: If *True*, plot the phase in degrees (otherwise, radians) - *label*: Label for the legend, if added - *axes*: Tuple (pair) of axes to plot into If *None* or (*None*, None*), then axes are created - *\*args*, *\*\*kwargs*: Additional options to matplotlib (color, linestyle, etc.) **Returns:** 1. Axes of the magnitude and phase plots (tuple (pair) of matplotlib axes) **Example:** .. plot:: :include-source: >>> from control.matlab import ss >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> axes = bode_plot(sys) """ phase = unwrap(phase) freq_unit = Hz if in_Hz else rad / s # Create axes if necessary. if axes is None or (None, None): axes = (plt.subplot(211), plt.subplot(212)) # Magnitude plot axes[0].semilogx(f / freq_unit, to_dB(mag) if in_dB else mag, label=label, *args, **kwargs) # Add a grid and labels. axes[0].grid(True) axes[0].grid(True, which='minor') axes[0].set_ylabel("Magnitude in dB" if in_dB else "Magnitude") # Phase plot axes[1].semilogx(f / freq_unit, phase / (deg if in_deg else rad), label=label, *args, **kwargs) # Add a grid and labels. axes[1].grid(True) axes[1].grid(True, which='minor') axes[1].set_xlabel(number_label("Frequency", "Hz" if in_Hz else "rad/s")) axes[1].set_ylabel(number_label("Phase", "deg" if in_deg else "rad")) return axes
def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None, Plot=True, *args, **kwargs): """Bode plot for a system Plots a Bode plot for the system over a (optional) frequency range. Parameters ---------- syslist : linsys List of linear input/output systems (single system is OK) omega : freq_range Range of frequencies (list or bounds) in rad/sec dB : boolean If True, plot result in dB Hz : boolean If True, plot frequency in Hz (omega must be provided in rad/sec) deg : boolean If True, return phase in degrees (else radians) Plot : boolean If True, plot magnitude and phase *args, **kwargs: Additional options to matplotlib (color, linestyle, etc) Returns ------- mag : array (list if len(syslist) > 1) magnitude phase : array (list if len(syslist) > 1) phase omega : array (list if len(syslist) > 1) frequency Notes ----- 1. Alternatively, you may use the lower-level method (mag, phase, freq) = sys.freqresp(freq) to generate the frequency response for a system, but it returns a MIMO response. 2. If a discrete time model is given, the frequency response is plotted along the upper branch of the unit circle, using the mapping z = exp(j \omega dt) where omega ranges from 0 to pi/dt and dt is the discrete time base. If not timebase is specified (dt = True), dt is set to 1. Examples -------- >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> mag, phase, omega = bode(sys) """ # Set default values for options import control.config if (dB is None): dB = control.config.bode_dB if (deg is None): deg = control.config.bode_deg if (Hz is None): Hz = control.config.bode_Hz # If argument was a singleton, turn it into a list if (not getattr(syslist, '__iter__', False)): syslist = (syslist, ) mags, phases, omegas = [], [], [] for sys in syslist: if (sys.inputs > 1 or sys.outputs > 1): #TODO: Add MIMO bode plots. raise NotImplementedError( "Bode is currently only implemented for SISO systems.") else: if (omega == None): # Select a default range if none is provided omega = default_frequency_range(syslist) # Get the magnitude and phase of the system mag_tmp, phase_tmp, omega = sys.freqresp(omega) mag = np.atleast_1d(np.squeeze(mag_tmp)) phase = np.atleast_1d(np.squeeze(phase_tmp)) phase = unwrap(phase) if Hz: omega = omega / (2 * sp.pi) if dB: mag = 20 * sp.log10(mag) if deg: phase = phase * 180 / sp.pi mags.append(mag) phases.append(phase) omegas.append(omega) # Get the dimensions of the current axis, which we will divide up #! TODO: Not current implemented; just use subplot for now if (Plot): # Magnitude plot plt.subplot(211) if dB: plt.semilogx(omega, mag, *args, **kwargs) else: plt.loglog(omega, mag, *args, **kwargs) plt.hold(True) # Add a grid to the plot + labeling plt.grid(True) plt.grid(True, which='minor') plt.ylabel("Magnitude (dB)" if dB else "Magnitude") # Phase plot plt.subplot(212) plt.semilogx(omega, phase, *args, **kwargs) plt.hold(True) # Add a grid to the plot + labeling plt.grid(True) plt.grid(True, which='minor') plt.ylabel("Phase (deg)" if deg else "Phase (rad)") # Label the frequency axis plt.xlabel("Frequency (Hz)" if Hz else "Frequency (rad/sec)") if len(syslist) == 1: return mags[0], phases[0], omegas[0] else: return mags, phases, omegas
def test_unwrap_list(self): angle = [0, 2.2, 5.4, -0.4] angle_unwrapped = [0, 0.2, 0.4, 0.6] np.testing.assert_array_almost_equal(unwrap(angle, 1.0), angle_unwrapped)
def test_unwrap_large_skips(self): angle = np.array([0., 4 * np.pi, -2 * np.pi]) np.testing.assert_array_almost_equal(unwrap(angle), [0., 0., 0.])
def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None, Plot=True, *args, **kwargs): """Bode plot for a system Plots a Bode plot for the system over a (optional) frequency range. Parameters ---------- syslist : linsys List of linear input/output systems (single system is OK) omega : freq_range Range of frequencies (list or bounds) in rad/sec dB : boolean If True, plot result in dB Hz : boolean If True, plot frequency in Hz (omega must be provided in rad/sec) deg : boolean If True, return phase in degrees (else radians) Plot : boolean If True, plot magnitude and phase *args, **kwargs: Additional options to matplotlib (color, linestyle, etc) Returns ------- mag : array (list if len(syslist) > 1) magnitude phase : array (list if len(syslist) > 1) phase omega : array (list if len(syslist) > 1) frequency Notes ----- 1. Alternatively, you may use the lower-level method (mag, phase, freq) = sys.freqresp(freq) to generate the frequency response for a system, but it returns a MIMO response. 2. If a discrete time model is given, the frequency response is plotted along the upper branch of the unit circle, using the mapping z = exp(j \omega dt) where omega ranges from 0 to pi/dt and dt is the discrete time base. If not timebase is specified (dt = True), dt is set to 1. Examples -------- >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> mag, phase, omega = bode(sys) """ # Set default values for options import control.config if (dB is None): dB = control.config.bode_dB if (deg is None): deg = control.config.bode_deg if (Hz is None): Hz = control.config.bode_Hz # If argument was a singleton, turn it into a list if (not getattr(syslist, '__iter__', False)): syslist = (syslist,) mags, phases, omegas = [], [], [] for sys in syslist: if (sys.inputs > 1 or sys.outputs > 1): #TODO: Add MIMO bode plots. raise NotImplementedError("Bode is currently only implemented for SISO systems.") else: if (omega == None): # Select a default range if none is provided omega = default_frequency_range(syslist) # Get the magnitude and phase of the system mag_tmp, phase_tmp, omega = sys.freqresp(omega) mag = np.atleast_1d(np.squeeze(mag_tmp)) phase = np.atleast_1d(np.squeeze(phase_tmp)) phase = unwrap(phase) if Hz: omega = omega/(2*sp.pi) if dB: mag = 20*sp.log10(mag) if deg: phase = phase * 180 / sp.pi mags.append(mag) phases.append(phase) omegas.append(omega) # Get the dimensions of the current axis, which we will divide up #! TODO: Not current implemented; just use subplot for now if (Plot): # Magnitude plot plt.subplot(211); if dB: plt.semilogx(omega, mag, *args, **kwargs) else: plt.loglog(omega, mag, *args, **kwargs) plt.hold(True); # Add a grid to the plot + labeling plt.grid(True) plt.grid(True, which='minor') plt.ylabel("Magnitude (dB)" if dB else "Magnitude") # Phase plot plt.subplot(212); plt.semilogx(omega, phase, *args, **kwargs) plt.hold(True); # Add a grid to the plot + labeling plt.grid(True) plt.grid(True, which='minor') plt.ylabel("Phase (deg)" if deg else "Phase (rad)") # Label the frequency axis plt.xlabel("Frequency (Hz)" if Hz else "Frequency (rad/sec)") if len(syslist) == 1: return mags[0], phases[0], omegas[0] else: return mags, phases, omegas
def bode_plot(mag, phase, f, in_Hz=True, in_dB=True, in_deg=True, label=None, axes=None, *args, **kwargs): r"""Create a Bode plot for a system. **Parameters:** - *sys*: Linear input/output system (Lti) - *freqs*: List or frequencies or tuple of (min, max) frequencies over which to plot the system response. If *freqs* is *None*, then an appropriate range will be determined automatically. - *in_Hz*: If *True*, the frequencies (*freqs*) are in Hz and should be plotted in Hz (otherwise, rad/s) - *in_dB*: If *True*, plot the magnitude in dB - *in_deg*: If *True*, plot the phase in degrees (otherwise, radians) - *label*: Label for the legend, if added - *axes*: Tuple (pair) of axes to plot into If *None* or (*None*, None*), then axes are created - *\*args*, *\*\*kwargs*: Additional options to matplotlib (color, linestyle, etc.) **Returns:** 1. Axes of the magnitude and phase plots (tuple (pair) of matplotlib axes) **Example:** .. plot:: :include-source: >>> from control.matlab import ss >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> axes = bode_plot(sys) """ phase = unwrap(phase) freq_unit = Hz if in_Hz else rad / s # Create axes if necessary. if axes is None or (None, None): axes = (plt.subplot(211), plt.subplot(212)) # Magnitude plot axes[0].semilogx(f / freq_unit, to_dB(mag) if in_dB else mag, label=label, *args, **kwargs) # Add a grid and labels. axes[0].grid(True) axes[0].grid(True, which='minor') axes[0].set_ylabel("Magnitude in dB" if in_dB else "Magnitude") # Phase plot axes[1].semilogx(f / freq_unit, phase / (deg if in_deg else rad), label=label, *args, **kwargs) # Add a grid and labels. axes[1].grid(True) axes[1].grid(True, which='minor') axes[1].set_xlabel(number_label("Frequency", "Hz" if in_Hz else "rad/s")) axes[1].set_ylabel(number_label("Phase", "deg" if in_deg else "rad")) return axes
def bode_plot(syslist, omega=None, dB=False, Hz=False, deg=True, # ModelicaRes 7/2/13: # Plot=True, *args, **kwargs): Plot=True, style='-', label=None, axes=None, *args, **kwargs): # ModelicaRes 7/5/13: Added description of axes argument and output """Bode plot for a system Plots a Bode plot for the system over a (optional) frequency range. Parameters ---------- syslist : linsys List of linear input/output systems (single system is OK) omega : freq_range Range of frequencies (list or bounds) in rad/sec dB : boolean If True, plot result in dB Hz : boolean If True, plot frequency in Hz (omega must be provided in rad/sec) deg : boolean If True, return phase in degrees (else radians) Plot : boolean If True, plot magnitude and phase axes : tuple (pair) of axes to plot into If None or (None, None), then axes are created *args, **kwargs: Additional options to matplotlib (color, linestyle, etc) Returns ------- mag : array (list if len(syslist) > 1) magnitude phase : array (list if len(syslist) > 1) phase omega : array (list if len(syslist) > 1) frequency axes tuple (pair) of axes for the magnitude and phase plots Notes ----- 1. Alternatively, you may use the lower-level method (mag, phase, freq) = sys.freqresp(freq) to generate the frequency response for a system, but it returns a MIMO response. 2. If a discrete time model is given, the frequency response is plotted along the upper branch of the unit circle, using the mapping z = exp(j \omega dt) where omega ranges from 0 to pi/dt and dt is the discrete time base. If not timebase is specified (dt = True), dt is set to 1. Examples -------- >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> mag, phase, omega = bode(sys) """ # If argument was a singleton, turn it into a list if (not getattr(syslist, '__iter__', False)): syslist = (syslist,) mags, phases, omegas = [], [], [] for sys in syslist: if (sys.inputs > 1 or sys.outputs > 1): #TODO: Add MIMO bode plots. raise NotImplementedError("Bode is currently only implemented for SISO systems.") else: if (omega == None): # Select a default range if none is provided omega = default_frequency_range(syslist) # Get the magnitude and phase of the system mag_tmp, phase_tmp, omega = sys.freqresp(omega) mag = np.atleast_1d(np.squeeze(mag_tmp)) phase = np.atleast_1d(np.squeeze(phase_tmp)) phase = unwrap(phase) if Hz: omega = omega/(2*sp.pi) if dB: mag = 20*sp.log10(mag) if deg: phase = phase * 180 / sp.pi mags.append(mag) phases.append(phase) omegas.append(omega) # Get the dimensions of the current axis, which we will divide up #! TODO: Not current implemented; just use subplot for now if (Plot): # ModelicaRes 7/5/13: # Create axes if necessary. if axes is None or (None, None): axes = (plt.subplot(211), plt.subplot(212)) # Magnitude plot # ModelicaRes 7/5/13: #plt.subplot(211); if dB: # ModelicaRes 7/5/13: #plt.semilogx(omega, mag, *args, **kwargs) if type(style) is str: axes[0].semilogx(omega, mag, linestyle=style, label=label, *args, **kwargs) else: axes[0].semilogx(omega, mag, dashes=style, label=label, *args, **kwargs) else: # ModelicaRes 7/5/13: #plt.loglog(omega, mag, *args, **kwargs) if type(style) is str: axes[0].loglog(omega, mag, linestyle=style, label=label, *args, **kwargs) else: axes[0].loglog(omega, mag, dashes=style, label=label, *args, **kwargs) # ModelicaRes 7/5/13: #plt.hold(True); # Add a grid to the plot + labeling # ModelicaRes 7/5/13: #plt.grid(True) #plt.grid(True, which='minor') #plt.ylabel("Magnitude (dB)" if dB else "Magnitude") axes[0].grid(True) axes[0].grid(True, which='minor') axes[0].set_ylabel("Magnitude in dB" if dB else "Magnitude") # Phase plot # ModelicaRes 7/5/13: #plt.subplot(212); # ModelicaRes 7/5/13: #plt.semilogx(omega, phase, *args, **kwargs) if type(style) is str: axes[1].semilogx(omega, phase, linestyle=style, label=label, *args, **kwargs) else: axes[1].semilogx(omega, phase, dashes=style, label=label, *args, **kwargs) # ModelicaRes 7/5/13: #plt.hold(True); # Add a grid to the plot + labeling # ModelicaRes 7/2/13: #plt.grid(True) #plt.grid(True, which='minor') #plt.ylabel("Phase (deg)" if deg else "Phase (rad)") axes[1].grid(True) axes[1].grid(True, which='minor') axes[1].set_ylabel("Phase / deg" if deg else "Phase / rad") # Label the frequency axis # ModelicaRes 7/5/13: #plt.xlabel("Frequency (Hz)" if Hz else "Frequency (rad/sec)") axes[1].set_xlabel("Frequency / Hz" if Hz else "Frequency / rad s$^{-1}$") if len(syslist) == 1: # ModelicaRes 7/5/13: #return mags[0], phases[0], omegas[0] return mags[0], phases[0], omegas[0], axes else: # ModelicaRes 7/5/13: #return mags, phases, omegas return mags, phases, omegas, axes