示例#1
0
 def test_discrete_bode(self):
     # Create a simple discrete time system and check the calculation
     sys = TransferFunction([1], [1, 0.5], 1)
     omega = [1, 2, 3]
     mag_out, phase_out, omega_out = bode(sys, omega)
     H_z = list(map(lambda w: 1./(np.exp(1.j * w) + 0.5), omega))
     np.testing.assert_array_almost_equal(omega, omega_out)
     np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z))
     np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z))
示例#2
0
 def test_discrete_bode(self):
     # Create a simple discrete time system and check the calculation
     sys = TransferFunction([1], [1, 0.5], 1)
     omega = [1, 2, 3]
     mag_out, phase_out, omega_out = bode(sys, omega)
     H_z = list(map(lambda w: 1. / (np.exp(1.j * w) + 0.5), omega))
     np.testing.assert_array_almost_equal(omega, omega_out)
     np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z))
     np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z))
示例#3
0
def bode(*args, **keywords):
    """Bode plot of the frequency response

    Examples
    --------
    >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") 
    >>> mag, phase, omega = bode(sys)
    
    .. todo:: 
    
        Document these use cases
    
        * >>> bode(sys, w)
        * >>> bode(sys1, sys2, ..., sysN)
        * >>> bode(sys1, sys2, ..., sysN, w)
        * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN')
    """

    # If the first argument is a list, then assume python-control calling format
    if (getattr(args[0], '__iter__', False)):
        return freqplot.bode(*args, **keywords)

    # Otherwise, run through the arguments and collect up arguments
    syslist = []; plotstyle=[]; omega=None;
    i = 0; 
    while i < len(args):
        # Check to see if this is a system of some sort
        if (ctrlutil.issys(args[i])): 
            # Append the system to our list of systems
            syslist.append(args[i])
            i += 1

            # See if the next object is a plotsytle (string)
            if (i < len(args) and isinstance(args[i], str)):
                plotstyle.append(args[i])
                i += 1

            # Go on to the next argument
            continue

        # See if this is a frequency list
        elif (isinstance(args[i], (list, np.ndarray))):
            omega = args[i]
            i += 1
            break

        else:
            raise ControlArgument("unrecognized argument type")

    # Check to make sure that we processed all arguments
    if (i < len(args)):
        raise ControlArgument("not all arguments processed")

    # Check to make sure we got the same number of plotstyles as systems
    if (len(plotstyle) != 0 and len(syslist) != len(plotstyle)):
        raise ControlArgument("number of systems and plotstyles should be equal")

    # Warn about unimplemented plotstyles
    #! TODO: remove this when plot styles are implemented in bode()
    #! TODO: uncomment unit test code that tests this out
    if (len(plotstyle) != 0):
        print("Warning (matabl.bode): plot styles not implemented");

    # Call the bode command
    return freqplot.bode(syslist, omega, **keywords)
示例#4
0
    def testConvert(self):
        """Test state space to transfer function conversion."""
        verbose = self.debug

        # print __doc__

        # Machine precision for floats.
        # eps = np.finfo(float).eps

        for states in range(1, self.maxStates):
            for inputs in range(1, self.maxIO):
                for outputs in range(1, self.maxIO):
                    # start with a random SS system and transform to TF then
                    # back to SS, check that the matrices are the same.
                    ssOriginal = matlab.rss(states, outputs, inputs)
                    if (verbose):
                        self.printSys(ssOriginal, 1)

                    # Make sure the system is not degenerate
                    Cmat = ctrb(ssOriginal.A, ssOriginal.B)
                    if (np.linalg.matrix_rank(Cmat) != states):
                        if (verbose):
                            print("  skipping (not reachable)")
                        continue
                    Omat = obsv(ssOriginal.A, ssOriginal.C)
                    if (np.linalg.matrix_rank(Omat) != states):
                        if (verbose):
                            print("  skipping (not observable)")
                        continue

                    tfOriginal = matlab.tf(ssOriginal)
                    if (verbose):
                        self.printSys(tfOriginal, 2)

                    ssTransformed = matlab.ss(tfOriginal)
                    if (verbose):
                        self.printSys(ssTransformed, 3)

                    tfTransformed = matlab.tf(ssTransformed)
                    if (verbose):
                        self.printSys(tfTransformed, 4)

                    # Check to see if the state space systems have same dim
                    if (ssOriginal.states != ssTransformed.states):
                        print("WARNING: state space dimension mismatch: " + \
                            "%d versus %d" % \
                            (ssOriginal.states, ssTransformed.states))

                    # Now make sure the frequency responses match
                    # Since bode() only handles SISO, go through each I/O pair
                    # For phase, take sine and cosine to avoid +/- 360 offset
                    for inputNum in range(inputs):
                        for outputNum in range(outputs):
                            if (verbose):
                                print("Checking input %d, output %d" \
                                    % (inputNum, outputNum))
                            ssorig_mag, ssorig_phase, ssorig_omega = \
                                bode(_mimo2siso(ssOriginal, \
                                                        inputNum, outputNum), \
                                                 deg=False, Plot=False)
                            ssorig_real = ssorig_mag * np.cos(ssorig_phase)
                            ssorig_imag = ssorig_mag * np.sin(ssorig_phase)

                            #
                            # Make sure TF has same frequency response
                            #
                            num = tfOriginal.num[outputNum][inputNum]
                            den = tfOriginal.den[outputNum][inputNum]
                            tforig = tf(num, den)

                            tforig_mag, tforig_phase, tforig_omega = \
                                bode(tforig, ssorig_omega, \
                                                 deg=False, Plot=False)

                            tforig_real = tforig_mag * np.cos(tforig_phase)
                            tforig_imag = tforig_mag * np.sin(tforig_phase)
                            np.testing.assert_array_almost_equal( \
                                ssorig_real, tforig_real)
                            np.testing.assert_array_almost_equal( \
                                ssorig_imag, tforig_imag)

                            #
                            # Make sure xform'd SS has same frequency response
                            #
                            ssxfrm_mag, ssxfrm_phase, ssxfrm_omega = \
                                bode(_mimo2siso(ssTransformed, \
                                                        inputNum, outputNum), \
                                                 ssorig_omega, \
                                                 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( \
                            ssorig_real, ssxfrm_real)
                            np.testing.assert_array_almost_equal( \
                            ssorig_imag, ssxfrm_imag)
                            #
                            # Make sure xform'd TF has same frequency response
                            #
                            num = tfTransformed.num[outputNum][inputNum]
                            den = tfTransformed.den[outputNum][inputNum]
                            tfxfrm = tf(num, den)
                            tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \
                                bode(tfxfrm, ssorig_omega, \
                                                 deg=False, Plot=False)

                            tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase)
                            tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase)
                            np.testing.assert_array_almost_equal( \
                                ssorig_real, tfxfrm_real)
                            np.testing.assert_array_almost_equal( \
                                ssorig_imag, tfxfrm_imag)
示例#5
0
    def testConvert(self):
        """Test state space to transfer function conversion."""
        verbose = self.debug

        # print __doc__

        # Machine precision for floats.
        # eps = np.finfo(float).eps

        for states in range(1, self.maxStates):
            for inputs in range(1, self.maxIO):
                for outputs in range(1, self.maxIO):
                    # start with a random SS system and transform to TF then
                    # back to SS, check that the matrices are the same.
                    ssOriginal = matlab.rss(states, outputs, inputs)
                    if (verbose):
                        self.printSys(ssOriginal, 1)

                    # Make sure the system is not degenerate
                    Cmat = ctrb(ssOriginal.A, ssOriginal.B)
                    if (np.linalg.matrix_rank(Cmat) != states):
                        if (verbose):
                            print("  skipping (not reachable)")
                        continue
                    Omat = obsv(ssOriginal.A, ssOriginal.C)
                    if (np.linalg.matrix_rank(Omat) != states):
                        if (verbose):
                            print("  skipping (not observable)")
                        continue

                    tfOriginal = matlab.tf(ssOriginal)
                    if (verbose):
                        self.printSys(tfOriginal, 2)

                    ssTransformed = matlab.ss(tfOriginal)
                    if (verbose):
                        self.printSys(ssTransformed, 3)

                    tfTransformed = matlab.tf(ssTransformed)
                    if (verbose):
                        self.printSys(tfTransformed, 4)

                    # Check to see if the state space systems have same dim
                    if (ssOriginal.states != ssTransformed.states):
                        print("WARNING: state space dimension mismatch: " + \
                            "%d versus %d" % \
                            (ssOriginal.states, ssTransformed.states))

                    # Now make sure the frequency responses match
                    # Since bode() only handles SISO, go through each I/O pair
                    # For phase, take sine and cosine to avoid +/- 360 offset
                    for inputNum in range(inputs):
                        for outputNum in range(outputs):
                            if (verbose):
                                print("Checking input %d, output %d" \
                                    % (inputNum, outputNum))
                            ssorig_mag, ssorig_phase, ssorig_omega = \
                                bode(_mimo2siso(ssOriginal, \
                                                        inputNum, outputNum), \
                                                 deg=False, plot=False)
                            ssorig_real = ssorig_mag * np.cos(ssorig_phase)
                            ssorig_imag = ssorig_mag * np.sin(ssorig_phase)

                            #
                            # Make sure TF has same frequency response
                            #
                            num = tfOriginal.num[outputNum][inputNum]
                            den = tfOriginal.den[outputNum][inputNum]
                            tforig = tf(num, den)

                            tforig_mag, tforig_phase, tforig_omega = \
                                bode(tforig, ssorig_omega, \
                                                 deg=False, plot=False)

                            tforig_real = tforig_mag * np.cos(tforig_phase)
                            tforig_imag = tforig_mag * np.sin(tforig_phase)
                            np.testing.assert_array_almost_equal( \
                                ssorig_real, tforig_real)
                            np.testing.assert_array_almost_equal( \
                                ssorig_imag, tforig_imag)

                            #
                            # Make sure xform'd SS has same frequency response
                            #
                            ssxfrm_mag, ssxfrm_phase, ssxfrm_omega = \
                                bode(_mimo2siso(ssTransformed, \
                                                        inputNum, outputNum), \
                                                 ssorig_omega, \
                                                 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( \
                            ssorig_real, ssxfrm_real)
                            np.testing.assert_array_almost_equal( \
                            ssorig_imag, ssxfrm_imag)
                            #
                            # Make sure xform'd TF has same frequency response
                            #
                            num = tfTransformed.num[outputNum][inputNum]
                            den = tfTransformed.den[outputNum][inputNum]
                            tfxfrm = tf(num, den)
                            tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \
                                bode(tfxfrm, ssorig_omega, \
                                                 deg=False, plot=False)

                            tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase)
                            tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase)
                            np.testing.assert_array_almost_equal( \
                                ssorig_real, tfxfrm_real)
                            np.testing.assert_array_almost_equal( \
                                ssorig_imag, tfxfrm_imag)
示例#6
0
    def bode(self, axes=None, pairs=None, label='bode',
             title=None, colors=['b', 'g', 'r', 'c', 'm', 'y', 'k'],
             styles=[(None,None), (3,3), (1,1), (3,2,1,2)], **kwargs):
        """Create a Bode plot of the system's response.

        The Bode plots of a MIMO system are overlayed. This is different than
        MATLAB\ :sup:`®`, which creates an array of subplots.

        **Arguments:**

        - *axes*: Tuple (pair) of axes for the magnitude and phase plots

             If *axes* is not provided, then axes will be created in a new
             figure.

        - *pairs*: List of (input index, output index) tuples of each transfer
          function to be evaluated

             If not provided, all of the transfer functions will be plotted.

        - *label*: Label for the figure (ignored if *axes* is provided)

             This will be used as the base filename if the figure is saved.

        - *title*: Title for the figure

             If *title* is *None* (default), then the title will be "Bode Plot
             of *fbase*", where *fbase* is the base filename of the data.  Use
             '' for no title.

        - *colors*: Color or list of colors that will be used sequentially

             Each may be a character, grayscale, or rgb value.

             .. Seealso:: http://matplotlib.sourceforge.net/api/colors_api.html

        - *styles*: Line/dash style or list of line/dash styles that will be
          used sequentially

             Each style is a string representing a linestyle (e.g., "--") or a
             tuple of on/off lengths representing dashes.  Use "" for no line
             and "-" for a solid line.

             .. Seealso::
                http://matplotlib.sourceforge.net/api/collections_api.html

        - *\*\*kwargs*: Additional arguments for :meth:`control.freqplot.bode`

        **Returns:**

        1. *axes*: Tuple (pair) of axes for the magnitude and phase plots

        **Example:**

        .. code-block:: python

           >>> from modelicares import LinRes, save
           >>> from numpy import pi, logspace

           >>> lin = LinRes('examples/PID.mat')
           >>> lin.bode(label='examples/PID-bode', omega=2*pi*logspace(-2, 3),
           ...          title="Bode Plot of Modelica.Blocks.Continuous.PID") # doctest: +ELLIPSIS
           (<matplotlib.axes._subplots.AxesSubplot object at 0x...>, <matplotlib.axes._subplots.AxesSubplot object at 0x...>)
           >>> save()
           Saved examples/PID-bode.pdf
           Saved examples/PID-bode.png

        .. only:: html

           .. image:: ../examples/PID-bode.png
              :scale: 70 %
              :alt: example for LinRes.bode()

        .. only:: latex

           .. figure:: ../examples/PID-bode.pdf
              :scale: 80 %

              Results of example for :meth:`LinRes.bode`.
        """
        # Create axes if necessary.
        if axes is None or (None, None):
            fig = base.figure(label)
            axes = (fig.add_subplot(211), fig.add_subplot(212))

        # Create a title if necessary.
        if title is None:
            title = r"Bode Plot of %s" % self.fbase

        # Set up the color(s) and line style(s).
        if not iterable(colors):
            # Use the single color for all plots.
            colors = (colors,)
        if not iterable(styles):
            # Use the single line style for all plots.
            styles = [styles]
        elif type(styles[0]) is int:
            # One dashes tuple has been provided; use its value for all plots.
            styles = [styles]
        n_colors = len(colors)
        n_styles = len(styles)

        # If input/output pair(s) aren't specified, generate a list of all
        # pairs.
        if not pairs:
            pairs = [(i_u, i_y) for i_u in range(self.sys.inputs)
                     for i_y in range(self.sys.outputs)]

        # Create the plots.
        for i, (i_u, i_y) in enumerate(pairs):
            # Extract the SISO TF. TODO: Is there a better way to do this?
            sys = ss(self.sys.A, self.sys.B[:, i_u], self.sys.C[i_y, :],
                     self.sys.D[i_y, i_u])
            bode(sys, Hz=True, label=r'$Y_{%i}/U_{%i}$' % (i_y, i_u),
                 color=colors[np.mod(i, n_colors)], axes=axes,
                 style=styles[np.mod(i, n_styles)], **kwargs)
            # Note: controls.freqplot.bode() is currently only implemented for
            # SISO systems.
            # 5/23/11: Since controls.freqplot.bode() already uses subplots for
            # the magnitude and phase plots, it would be difficult to modify
            # the code to put the Bode plots of a MIMO system into an array of
            # subfigures like MATLAB does.

        # Decorate and finish.
        axes[0].set_title(title)
        if len(pairs) > 1:
            axes[0].legend()
            axes[1].legend()
        return axes
示例#7
0
def stability_margins(sysdata, deg=True):
    """Calculate gain, phase and stability margins and associated
    crossover frequencies.

    Usage
    -----
    gm, pm, sm, wg, wp, ws = stability_margins(sysdata, deg=True)
    
    Parameters
    ----------
    sysdata: linsys or (mag, phase, omega) sequence 
        sys : linsys
            Linear SISO system
        mag, phase, omega : sequence of array_like
            Input magnitude, phase, and frequencies (rad/sec) sequence from 
            bode frequency response data 
    deg=True: boolean  
        If true, all input and output phases in degrees, else in radians
        
    Returns
    -------
    gm, pm, sm, wg, wp, ws: float
        Gain margin gm, phase margin pm, stability margin sm, and 
        associated crossover
        frequencies wg, wp, and ws of SISO open-loop. If more than
        one crossover frequency is detected, returns the lowest corresponding
        margin. 
    """
    #TODO do this precisely without the effects of discretization of frequencies?
    #TODO assumes SISO
    #TODO unit tests, margin plot

    if (not getattr(sysdata, '__iter__', False)):
        sys = sysdata

        # TODO: implement for discrete time systems
        if (isdtime(sys, strict=True)):
            raise NotImplementedError("Function not implemented in discrete time")

        mag, phase, omega = bode(sys, deg=deg, Plot=False)
    elif len(sysdata) == 3:
        # TODO: replace with FRD object type?
        mag, phase, omega = sysdata
    else: 
        raise ValueError("Margin sysdata must be either a linear system or a 3-sequence of mag, phase, omega.")
        
    if deg:
        cycle = 360. 
        crossover = 180. 
    else:
        cycle = 2 * np.pi
        crossover = np.pi
        
    wrapped_phase = -np.mod(phase, cycle)
    
    # phase margin from minimum phase among all gain crossovers
    neg_mag_crossings_i = np.nonzero(np.diff(mag < 1) > 0)[0]
    mag_crossings_p = wrapped_phase[neg_mag_crossings_i]
    if len(neg_mag_crossings_i) == 0:
        if mag[0] < 1: # gain always less than one
            wp = np.nan
            pm = np.inf
        else: # gain always greater than one
            print("margin: no magnitude crossings found")
            wp = np.nan
            pm = np.nan
    else:
        min_mag_crossing_i = neg_mag_crossings_i[np.argmin(mag_crossings_p)]
        wp = omega[min_mag_crossing_i]
        pm = crossover + phase[min_mag_crossing_i] 
        if pm < 0:
            print("warning: system unstable: negative phase margin")
    
    # gain margin from minimum gain margin among all phase crossovers
    neg_phase_crossings_i = np.nonzero(np.diff(wrapped_phase < -crossover) > 0)[0]
    neg_phase_crossings_g = mag[neg_phase_crossings_i]
    if len(neg_phase_crossings_i) == 0:
        wg = np.nan
        gm = np.inf
    else:
        min_phase_crossing_i = neg_phase_crossings_i[
            np.argmax(neg_phase_crossings_g)]
        wg = omega[min_phase_crossing_i]
        gm = abs(1/mag[min_phase_crossing_i])
        if gm < 1: 
            print("warning: system unstable: gain margin < 1")

    # stability margin from minimum abs distance from -1 point
    if deg:
        phase_rad = phase * np.pi / 180.
    else:
        phase_rad = phase
    L = mag * np.exp(1j * phase_rad) # complex loop response to -1 pt
    min_Lplus1_i = np.argmin(np.abs(L + 1))
    sm = np.abs(L[min_Lplus1_i] + 1)
    ws = phase[min_Lplus1_i]

    return gm, pm, sm, wg, wp, ws 
    def testConvert(self, fixedseed, states, inputs, outputs):
        """Test state space to transfer function conversion.

        start with a random SS system and transform to TF then
        back to SS, check that the matrices are the same.
        """
        ssOriginal = rss(states, outputs, inputs)
        if verbose:
            self.printSys(ssOriginal, 1)

        # Make sure the system is not degenerate
        Cmat = ctrb(ssOriginal.A, ssOriginal.B)
        if (np.linalg.matrix_rank(Cmat) != states):
            pytest.skip("not reachable")
        Omat = obsv(ssOriginal.A, ssOriginal.C)
        if (np.linalg.matrix_rank(Omat) != states):
            pytest.skip("not observable")

        tfOriginal = tf(ssOriginal)
        if (verbose):
            self.printSys(tfOriginal, 2)

        ssTransformed = ss(tfOriginal)
        if (verbose):
            self.printSys(ssTransformed, 3)

        tfTransformed = tf(ssTransformed)
        if (verbose):
            self.printSys(tfTransformed, 4)

        # Check to see if the state space systems have same dim
        if (ssOriginal.nstates != ssTransformed.nstates) and verbose:
            print("WARNING: state space dimension mismatch: %d versus %d" %
                  (ssOriginal.nstates, ssTransformed.nstates))

        # Now make sure the frequency responses match
        # Since bode() only handles SISO, go through each I/O pair
        # For phase, take sine and cosine to avoid +/- 360 offset
        for inputNum in range(inputs):
            for outputNum in range(outputs):
                if (verbose):
                    print("Checking input %d, output %d"
                          % (inputNum, outputNum))
                ssorig_mag, ssorig_phase, ssorig_omega = \
                    bode(_mimo2siso(ssOriginal, inputNum, outputNum),
                         deg=False, plot=False)
                ssorig_real = ssorig_mag * np.cos(ssorig_phase)
                ssorig_imag = ssorig_mag * np.sin(ssorig_phase)

                #
                # Make sure TF has same frequency response
                #
                num = tfOriginal.num[outputNum][inputNum]
                den = tfOriginal.den[outputNum][inputNum]
                tforig = tf(num, den)

                tforig_mag, tforig_phase, tforig_omega = \
                    bode(tforig, ssorig_omega,
                         deg=False, plot=False)

                tforig_real = tforig_mag * np.cos(tforig_phase)
                tforig_imag = tforig_mag * np.sin(tforig_phase)
                np.testing.assert_array_almost_equal(
                    ssorig_real, tforig_real)
                np.testing.assert_array_almost_equal(
                    ssorig_imag, tforig_imag)

                #
                # Make sure xform'd SS has same frequency response
                #
                ssxfrm_mag, ssxfrm_phase, ssxfrm_omega = \
                    bode(_mimo2siso(ssTransformed,
                                    inputNum, outputNum),
                         ssorig_omega,
                         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(
                    ssorig_real, ssxfrm_real, decimal=5)
                np.testing.assert_array_almost_equal(
                    ssorig_imag, ssxfrm_imag, decimal=5)

                # Make sure xform'd TF has same frequency response
                #
                num = tfTransformed.num[outputNum][inputNum]
                den = tfTransformed.den[outputNum][inputNum]
                tfxfrm = tf(num, den)
                tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \
                    bode(tfxfrm, ssorig_omega,
                         deg=False, plot=False)

                tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase)
                tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase)
                np.testing.assert_array_almost_equal(
                    ssorig_real, tfxfrm_real, decimal=5)
                np.testing.assert_array_almost_equal(
                    ssorig_imag, tfxfrm_imag, decimal=5)
示例#9
0
def bode(*args, **keywords):
    """Bode plot of the frequency response

    Plots a bode gain and phase diagram

    Parameters
    ----------
    sys : Lti, or list of Lti
        System for which the Bode response is plotted and give. Optionally
        a list of systems can be entered, or several systems can be 
        specified (i.e. several parameters). The sys arguments may also be
        interspersed with format strings. A frequency argument (array_like) 
        may also be added, some examples:
        * >>> bode(sys, w)                    # one system, freq vector
        * >>> bode(sys1, sys2, ..., sysN)     # several systems
        * >>> bode(sys1, sys2, ..., sysN, w)
        * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # + plot formats
    omega: freq_range
        Range of frequencies in rad/s
    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

    Examples
    --------
    >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") 
    >>> mag, phase, omega = bode(sys)
    
    .. todo:: 
    
        Document these use cases
    
        * >>> bode(sys, w)
        * >>> bode(sys1, sys2, ..., sysN)
        * >>> bode(sys1, sys2, ..., sysN, w)
        * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN')
    """

    # If the first argument is a list, then assume python-control calling format
    if (getattr(args[0], '__iter__', False)):
        return freqplot.bode(*args, **keywords)

    # Otherwise, run through the arguments and collect up arguments
    syslist = []; plotstyle=[]; omega=None;
    i = 0; 
    while i < len(args):
        # Check to see if this is a system of some sort
        if (ctrlutil.issys(args[i])): 
            # Append the system to our list of systems
            syslist.append(args[i])
            i += 1

            # See if the next object is a plotsytle (string)
            if (i < len(args) and isinstance(args[i], str)):
                plotstyle.append(args[i])
                i += 1

            # Go on to the next argument
            continue

        # See if this is a frequency list
        elif (isinstance(args[i], (list, np.ndarray))):
            omega = args[i]
            i += 1
            break

        else:
            raise ControlArgument("unrecognized argument type")

    # Check to make sure that we processed all arguments
    if (i < len(args)):
        raise ControlArgument("not all arguments processed")

    # Check to make sure we got the same number of plotstyles as systems
    if (len(plotstyle) != 0 and len(syslist) != len(plotstyle)):
        raise ControlArgument("number of systems and plotstyles should be equal")

    # Warn about unimplemented plotstyles
    #! TODO: remove this when plot styles are implemented in bode()
    #! TODO: uncomment unit test code that tests this out
    if (len(plotstyle) != 0):
        print("Warning (matabl.bode): plot styles not implemented");

    # Call the bode command
    return freqplot.bode(syslist, omega, **keywords)
示例#10
0
def bode(*args, **keywords):
    """Bode plot of the frequency response

    Plots a bode gain and phase diagram

    Parameters
    ----------
    sys : Lti, or list of Lti
        System for which the Bode response is plotted and give. Optionally
        a list of systems can be entered, or several systems can be 
        specified (i.e. several parameters). The sys arguments may also be
        interspersed with format strings. A frequency argument (array_like) 
        may also be added, some examples:
        * >>> bode(sys, w)                    # one system, freq vector
        * >>> bode(sys1, sys2, ..., sysN)     # several systems
        * >>> bode(sys1, sys2, ..., sysN, w)
        * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # + plot formats
    omega: freq_range
        Range of frequencies in rad/s
    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

    Examples
    --------
    >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") 
    >>> mag, phase, omega = bode(sys)
    
    .. todo:: 
    
        Document these use cases
    
        * >>> bode(sys, w)
        * >>> bode(sys1, sys2, ..., sysN)
        * >>> bode(sys1, sys2, ..., sysN, w)
        * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN')
    """

    # If the first argument is a list, then assume python-control calling format
    if (getattr(args[0], '__iter__', False)):
        return freqplot.bode(*args, **keywords)

    # Otherwise, run through the arguments and collect up arguments
    syslist = []; plotstyle=[]; omega=None;
    i = 0; 
    while i < len(args):
        # Check to see if this is a system of some sort
        if (ctrlutil.issys(args[i])): 
            # Append the system to our list of systems
            syslist.append(args[i])
            i += 1

            # See if the next object is a plotsytle (string)
            if (i < len(args) and isinstance(args[i], str)):
                plotstyle.append(args[i])
                i += 1

            # Go on to the next argument
            continue

        # See if this is a frequency list
        elif (isinstance(args[i], (list, np.ndarray))):
            omega = args[i]
            i += 1
            break

        else:
            raise ControlArgument("unrecognized argument type")

    # Check to make sure that we processed all arguments
    if (i < len(args)):
        raise ControlArgument("not all arguments processed")

    # Check to make sure we got the same number of plotstyles as systems
    if (len(plotstyle) != 0 and len(syslist) != len(plotstyle)):
        raise ControlArgument("number of systems and plotstyles should be equal")

    # Warn about unimplemented plotstyles
    #! TODO: remove this when plot styles are implemented in bode()
    #! TODO: uncomment unit test code that tests this out
    if (len(plotstyle) != 0):
        print("Warning (matabl.bode): plot styles not implemented");

    # Call the bode command
    return freqplot.bode(syslist, omega, **keywords)
示例#11
0
def multibode(lins, axes=None, pair=(0, 0), label='bode', title="Bode Plot",
              labels='', colors=['b', 'g', 'r', 'c', 'm', 'y', 'k'],
              styles=[(None,None), (3,3), (1,1), (3,2,1,2)], leg_kwargs={},
              **kwargs):
    r"""Plot multiple linearizations onto a single Bode diagram.

    **Arguments:**

    - *lins*: Linearization result or list of results (instances of
      :class:`linres.LinRes`)

    - *axes*: Tuple (pair) of axes for the magnitude and phase plots

         If *axes* is not provided, then axes will be created in a new figure.

    - *pair*: Tuple of (input index, output index) for the transfer function
      to be chosen from each system (applied to all)

         This is ignored if the system is SISO.

    - *label*: Label for the figure (ignored if axes is provided)

         This will be used as the base filename if the figure is saved.

    - *title*: Title for the figure

    - *labels*: Label or list of labels for the legends

         If *labels* is *None*, then no label will be used.  If it is an
         empty string (''), then the base filenames will be used.

    - *colors*: Color or list of colors that will be used sequentially

         Each may be a character, grayscale, or rgb value.

         .. Seealso:: http://matplotlib.sourceforge.net/api/colors_api.html

    - *styles*: Line/dash style or list of line/dash styles that will be
      used sequentially

         Each style is a string representing a linestyle (e.g., "--") or a
         tuple of on/off lengths representing dashes.  Use "" for no line
         and "-" for a solid line.

         .. Seealso::
            http://matplotlib.sourceforge.net/api/collections_api.html

    - *leg_kwargs*: Dictionary of keyword arguments for
      :meth:`matplotlib.pyplot.legend`

         If *leg_kwargs* is *None*, then no legend will be shown.

    - *\*\*kwargs*: Additional arguments for :meth:`control.freqplot.bode`

    **Returns:**

    1. *axes*: Tuple (pair) of axes for the magnitude and phase plots

    **Example:**

    .. testsetup::
       >>> from modelicares import closeall
       >>> closeall()

    .. code-block:: python

       >>> import os

       >>> from glob import glob
       >>> from modelicares import LinRes, multibode, save, read_params
       >>> from numpy import pi, logspace

       >>> lins = map(LinRes, glob('examples/PID/*/*.mat'))
       >>> labels = ["Ti=%g" % read_params('Ti', os.path.join(lin.dir, 'dsin.txt'))
       ...           for lin in lins]
       >>> multibode(lins,
       ...           title="Bode Plot of Modelica.Blocks.Continuous.PID",
       ...           label='examples/PIDs-bode', omega=2*pi*logspace(-2, 3),
       ...           labels=labels, leg_kwargs=dict(loc='lower right')) # doctest: +ELLIPSIS
       (<matplotlib.axes._subplots.AxesSubplot object at 0x...>, <matplotlib.axes._subplots.AxesSubplot object at 0x...>)

       >>> save()
       Saved examples/PIDs-bode.pdf
       Saved examples/PIDs-bode.png

    .. only:: html

       .. image:: ../examples/PIDs-bode.png
          :scale: 70 %
          :alt: Bode plot of PID with varying parameters

    .. only:: latex

       .. figure:: ../examples/PIDs-bode.pdf
          :scale: 70 %

          Bode plot of PID with varying parameters
    """
    # Create axes if necessary.
    if not axes:
        fig = figure(label)
        axes = (fig.add_subplot(211), fig.add_subplot(212))

    # Process the lins input.
    if not iterable(lins):
        lins = [lins]

    # Process the labels input.
    if labels == '':
        labels = [lin.fbase for lin in lins]
    elif labels == None:
        labels = ['']*len(lins)

    # Set up the color(s) and line style(s).
    if not iterable(colors):
        # Use the single color for all plots.
        colors = (colors,)
    if not iterable(styles):
        # Use the single line style for all plots.
        styles = [styles]
    elif type(styles[0]) is int:
        # One dashes tuple has been provided; use its value for all plots.
        styles = [styles]
    n_colors = len(colors)
    n_styles = len(styles)

    # Create the plots.
    for i, (lin, label) in enumerate(zip(lins, labels)):
        if lin.sys.inputs > 1 or lin.sys.outputs > 1:
            # Extract the SISO TF. TODO: Is there a better way to do this?
            sys = ss(self.sys.A, self.sys.B[:, pair[0]], self.sys.C[pair[1], :],
                     self.sys.D[pair[1], pair[0]])
        else:
            sys = lin.sys
        bode(sys, Hz=True, label=label,
             color=colors[np.mod(i, n_colors)], axes=axes,
             style=styles[np.mod(i, n_styles)], **kwargs)

    # Decorate and finish.
    axes[0].set_title(title)
    if leg_kwargs is not None:
        loc = leg_kwargs.pop('loc', 'best')
        axes[0].legend(loc=loc, **leg_kwargs)
        axes[1].legend(loc=loc, **leg_kwargs)
    return axes