Пример #1
0
 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)
Пример #2
0
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()
Пример #3
0
 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()
Пример #4
0
	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()
Пример #5
0
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
Пример #6
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
Пример #7
0
 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)
Пример #8
0
 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.])
Пример #9
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
Пример #10
0
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
Пример #11
0
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