예제 #1
0
def ref_perfect_const_plot(G, R, wr, w_start=-2, w_end=2, axlim=None,
                           points=1000, plot_type='all'):
    """
    Use these plots to determine the constraints for perfect control in terms
    of combined reference changes. Equation 6.52 (p241) calculates the
    minimal requirement for input saturation to check in terms of set point
    tracking. A more tighter bounds is calculated with equation 6.53 (p241).

    Parameters
    ----------
    G : tf
        Plant transfer function.
    R : numpy matrix (n x n)
        Reference changes (usually diagonal with all elements larger than 1)
    wr : float
        Frequency up to witch reference tracking is required
    type_eq : string
        Type of plot:

        =========      ==================================
        plot_type      Type of plot
        =========      ==================================
        minimal        Minimal requirement, equation 6.52
        tighter        Tighter requirement, equation 6.53
        allo           All requirements
        =========      ==================================

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    All the plots in this function needs to be larger than 1 for perfect
    control, otherwise input saturation will occur.
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    lab1 = '$\sigma_{min} (G(jw))$'
    bound1 = [utils.sigmas(G(si), 'min') for si in s]
    lab2 = '$\sigma_{min} (R^{-1}G(jw))$'
    bound2 = [utils.sigmas(numpy.linalg.pinv(R) * G(si), 'min') for si in s]

    if plot_type == 'all':
        plt.loglog(w, bound1, label=lab1)
        plt.loglog(w, bound2, label=lab2)
    elif plot_type == 'minimal':
        plt.loglog(w, bound1, label=lab1)
    elif plot_type == 'tighter':
        plt.loglog(w, bound2, label=lab2)

    else:
        raise ValueError('Invalid plot_type parameter.')

    mm = bound1 + bound2 + [1]  # ensures that whole graph is visible
    plt.loglog([wr, wr], [0.5 * numpy.min(mm), 5 * numpy.max(mm)],
               'r', ls=':', label='Ref tracked')
    plt.loglog([w[0], w[-1]], [1, 1], 'r', label='Bound')
    plt.legend()
예제 #2
0
def input_acceptable_const_plot(G, Gd, w_start=-2, w_end=2, axlim=None,
                                points=1000, modified=False):
    """
    Subbplots for input constraints for accepatable control. Applies equation
    6.55 (p241).

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    Gd : numpy matrix
        Plant disturbance model.
    modified : boolean
        If true, the arguments in the equation are change to :math:`\sigma_1
        (G) + 1 \geq |u_i^H g_d|`. This is to avoid a negative log scale.

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    This condition only holds for :math:`|u_i^H g_d|>1`.
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    freqresp = [G(si) for si in s]
    sig = numpy.array([utils.sigmas(Gfr) for Gfr in freqresp])
    one = numpy.ones(points)

    plot_No = 1

    dimGd = numpy.shape(Gd(0))[1]
    dimG = numpy.shape(G(0))[0]
    acceptable_control = numpy.zeros((dimGd, dimG, points))
    for j in range(dimGd):
        for i in range(dimG):
            for k in range(points):
                U, _, _ = utils.SVD(G(s[k]))
                acceptable_control[j, i, k] = numpy.abs(U[:, i].H *
                                                        Gd(s[k])[:, j])
            plt.subplot(dimG, dimGd, plot_No)
            if not modified:
                plt.loglog(w, sig[:, i],
                           label=('$\sigma_%s$' % (i + 1)))
                plt.plot(w, acceptable_control[j, i] - one,
                         label=('$|u_%s^H.g_{d%s}|-1$' % (i + 1, j + 1)))
            else:
                plt.loglog(w, sig[:, i] + one,
                           label=('$\sigma_%s+1$' % (i + 1)))
                plt.plot(w, acceptable_control[j, i],
                         label=('$|u_%s^H.g_{d%s}|$' % (i + 1, j + 1)))
                plt.loglog([w[0], w[-1]], [1, 1], 'r',
                           ls=':', label='Applicable')
            plt.xlabel('Frequency [rad/unit time]')
            plt.grid(True)
            plt.axis(axlim)
            plt.legend()
            plot_No += 1
예제 #3
0
def condtn_nm_plot(G, w_start=-2, w_end=2, axlim=None, points=1000):
    """
    Plot of the condition number, the maximum over the minimum singular value

    Parameters
    ----------
    G : numpy matrix
        Plant model.

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    A condition number over 10 may indicate sensitivity to uncertainty and
    control problems

    With a small condition number, Gamma =1, the system is insensitive to
    input uncertainty, irrespective of controller (p248).
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    def cndtn_nm(G):
        return utils.sigmas(G)[0]/utils.sigmas(G)[-1]

    freqresp = [G(si) for si in s]

    plt.loglog(w, [cndtn_nm(Gfr) for Gfr in freqresp], label='$\gamma (G)$')
    plt.axis(axlim)
    plt.ylabel('$\gamma (G)$')
    plt.xlabel('Frequency [rad/unit time]')
    plt.axhline(10., color='red', ls=':', label='"Large" $\gamma (G) = 10$')
    plt.legend()
예제 #4
0
def bode(G, w_start=-2, w_end=2, axlim=None, points=1000, margin=False):
    """
    Shows the bode plot for a plant model

    Parameters
    ----------
    G : tf
        Plant transfer function.
    margin : boolean
        Show the cross over frequencies on the plot (optional).

    Returns
    -------
    GM : array containing a real number
        Gain margin.
    PM : array containing a real number
        Phase margin.
    Plot : matplotlib figure
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)
    plt.clf()

    GM, PM, wc, w_180 = utils.margins(G)

    # plotting of Bode plot and with corresponding frequencies for PM and GM
#    if ((w2 < numpy.log(w_180)) and margin):
#        w2 = numpy.log(w_180)

    # Magnitude of G(jw)
    plt.subplot(2, 1, 1)
    gains = numpy.abs(G(s))
    plt.loglog(w, gains)
    if margin:
        plt.axvline(w_180, color='black')
        plt.text(w_180,
                 numpy.average([numpy.max(gains), numpy.min(gains)]),
                 r'$\angle$G(jw) = -180$\degree$')
    plt.axhline(1., color='red', linestyle='--')
    plt.axis(axlim)
    plt.grid()
    plt.ylabel('Magnitude')

    # Phase of G(jw)
    plt.subplot(2, 1, 2)
    phaseangle = utils.phase(G(s), deg=True)
    plt.semilogx(w, phaseangle)
    if margin:
        plt.axvline(wc, color='black')
        plt.text(wc,
                 numpy.average([numpy.max(phaseangle), numpy.min(phaseangle)]),
                 '|G(jw)| = 1')
    plt.axhline(-180., color='red', linestyle='--')
    plt.axis(axlim)
    plt.grid()
    plt.ylabel('Phase')
    plt.xlabel('Frequency [rad/unit time]')

    return GM, PM
예제 #5
0
def sv_dir_plot(G, plot_type, w_start=-2, w_end=2, axlim=None, points=1000):
    """
    Plot the input and output singular vectors associated with the minimum and
    maximum singular values.

    Parameters
    ----------
    G : matrix
        Plant model or sensitivity function.
    plot_type : string
        Type of plot.

        =========      ============================
        plot_type      Type of plot
        =========      ============================
        input          Plots input vectors
        output         Plots output vectors
        =========      ============================

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    Can be used with the plant matrix G and the sensitivity function S
    for controlability analysis
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    freqresp = [G(si) for si in s]

    if plot_type == 'input':
        vec = numpy.array([V for _, _, V in map(utils.SVD, freqresp)])
        d = 'v'
    elif plot_type == 'output':
        vec = numpy.array([U for U, _, _ in map(utils.SVD, freqresp)])
        d = 'u'
    else:
        raise ValueError('Invalid plot_type parameter.')

    dim = numpy.shape(vec)[1]
    for i in range(dim):
        plt.subplot(dim*2, 1, 2*i + 1)
        plt.semilogx(w, abs(vec[:, i, 0]), label='$%s_{max}$' % d, lw=2)
        plt.semilogx(w, abs(vec[:, i, -1]), label='$%s_{min}$' % d, lw=2)
        plt.axhline(0, color='red', ls=':')
        plt.axis(axlim)
        plt.ylabel('$|{0}_{1}|$'.format(d, i + 1))
        plt.subplot(dim*2, 1, 2*i + 2)
        plt.semilogx(w, utils.phase(vec[:, i, 0], deg=True), label='$%s_{max}$' % d, lw=2)
        plt.semilogx(w, utils.phase(vec[:, i, -1], deg=True), label='$%s_{min}$' % d, lw=2)
        plt.axhline(0, color='red', ls=':')
        plt.axis(axlim)
        plt.ylabel(r'$\angle {0}_{1}$'.format(d, i + 1))

    plt.xlabel('Frequency [rad/unit time]')
예제 #6
0
def mino_nyquist_plot(L, w_start=-2, w_end=2, axlim=None, points=1000):
    """
    Nyquist stability plot for MIMO system.

    Parameters
    ----------
    L : numpy matrix
        Closed loop transfer function matrix as a function of s, i.e. def L(s).

    Returns
    -------
    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>> Kc = numpy.array([[0.1, 0.],
    ...                   [0., 0.1]])*6
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1)
    ...
    >>> def L(s):
    ...     return Kc*G(s)
    ...
    >>> #MIMOnyqPlot(L, 2)

    """

    _, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    Lin = numpy.zeros((len(w)), dtype=complex)
    x = numpy.zeros((len(w)))
    y = numpy.zeros((len(w)))
    dim = numpy.shape(L(0.1))
    for i in range(len(w)):
        Lin[i] = numpy.linalg.det(numpy.eye(dim[0]) + L(w[i]*1j))
        x[i] = numpy.real(Lin[i])
        y[i] = numpy.imag(Lin[i])
    plt.plot(x, y, 'k-', lw=1)
    plt.axis(axlim)
    plt.xlabel('Re G(wj)')
    plt.ylabel('Im G(wj)')

    # plotting a unit circle
    x = numpy.linspace(-1, 1, 200)
    y_up = numpy.sqrt(1 - x**2)
    y_down = -1*numpy.sqrt(1 - x**2)
    plt.plot(x, y_up, 'b:', x, y_down, 'b:', lw=2)
    plt.plot(0, 0, 'r*', ms=10)
    plt.grid(True)
    plt.axis('equal')   # Ensure the unit circle remains round on resizing the figure
예제 #7
0
def step_response_plot(Y, U, t_end=50, initial_val=0, timedim='sec',
                       axlim=None, points=1000, constraint=None,
                       method='numeric'):
    """
    A plot of the step response of a transfer function

    Parameters
    ----------
    Y : tf
        Output transfer function.
    U : tf
        Input transfer function.
    t_end : integer
        Time period which the step response should occur (optional).
    initial_val : integer
        Starting value to evaluate step response (optional).
    constraint : float
        The upper limit the step response cannot exceed. is only calculated
        if a value is specified (optional).
    method : ['numeric','analytic']
        The method that is used to calculate a constrained response. A
        constraint value is required (optional).

    Returns
    -------
    Plot : matplotlib figure

    """

    axlim = df.frequency_plot_setup(axlim)

    [t, y] = utils.tf_step(Y, t_end, initial_val)
    plt.plot(t, y)

    [t, y] = utils.tf_step(U, t_end, initial_val)
    plt.plot(t, y)

    if constraint is None:
        plt.legend(['$y(t)$', '$u(t)$'])
    else:
        [t, y] = utils.tf_step(U, t_end, initial_val,
                               points, constraint, Y, method)
        plt.plot(t, y[0])
        plt.plot(t, y[1])
        # con = constraint
        plt.legend(['$y(t)$', '$u(t)$', '$u(t) const$', '$y(t) const$'])

    plt.plot([0, t_end], numpy.ones(2), '--')

    plt.axis(axlim)
    plt.xlabel('Time [' + timedim + ']')
예제 #8
0
def input_perfect_const_plot(G,
                             Gd,
                             w_start=-2,
                             w_end=2,
                             axlim=None,
                             points=1000,
                             simultaneous=False):
    """
    Plot for input constraints for perfect control. Applies equation 6.50
    (p240).

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    Gd : numpy matrix
        Plant disturbance model.
    simultaneous : boolean.
        If true, the induced max-norm is calculated for simultaneous
        disturbances (optional).

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    The boundary conditions is values below 1 (p240).
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = numpy.shape(Gd(0))[1]
    perfect_control = numpy.zeros((dim, points))
    # if simultaneous: imn = np.zeros(points)
    for i in range(dim):
        for k in range(points):
            Ginv = numpy.linalg.inv(G(s[k]))
            perfect_control[i, k] = numpy.max(numpy.abs(Ginv * Gd(s[k])[:, i]))
            # if simultaneous:
            # TODO induced max-norm
        plt.loglog(w, perfect_control[i], label=('$g_{d%s}$' % (i + 1)))

    plt.axhline(1., color='red', ls=':')
    plt.ylabel(r'$||G^{-1}.g_d||_{max}$')
    plt.xlabel('Frequency [rad/unit time]')
    plt.grid(True)
    plt.axis(axlim)
    plt.legend()
예제 #9
0
def step_response_plot(Y, U, t_end=50, initial_val=0, timedim='sec', axlim=None, points=1000, constraint=None, method='numeric'):
    """
    A plot of the step response of a transfer function

    Parameters
    ----------
    Y : tf
        Output transfer function.
    U : tf
        Input transfer function.
    t_end : integer
        Time period which the step response should occur (optional).
    initial_val : integer
        Starting value to evaluate step response (optional).
    constraint : float
        The upper limit the step response cannot exceed. is only calculated
        if a value is specified (optional).
    method : ['numeric','analytic']
        The method that is used to calculate a constrained response. A
        constraint value is required (optional).

    Returns
    -------
    Plot : matplotlib figure

    """

    axlim = df.frequency_plot_setup(axlim)

    [t, y] = utils.tf_step(Y, t_end, initial_val)
    plt.plot(t, y)

    [t, y] = utils.tf_step(U, t_end, initial_val)
    plt.plot(t, y)

    if constraint is None:
        plt.legend(['$y(t)$', '$u(t)$'])
    else:
        [t, y] = utils.tf_step(U, t_end, initial_val, points, constraint, Y, method)
        plt.plot(t, y[0])
        plt.plot(t, y[1])

        plt.legend(['$y(t)$', '$u(t)$', '$u(t) const$', '$y(t) const$'])  # con = constraint

    plt.plot([0, t_end], numpy.ones(2), '--')

    plt.axis(axlim)
    plt.xlabel('Time [' + timedim + ']')
예제 #10
0
def bodeclosedloop(G,
                   K,
                   w_start=-2,
                   w_end=2,
                   axlim=None,
                   points=1000,
                   margin=False):
    """
    Shows the bode plot for a controller model

    Parameters
    ----------
    G : tf
        Plant transfer function.
    K : tf
        Controller transfer function.
    margin : boolean
        Show the cross over frequencies on the plot (optional).
    """

    _, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    L = G(1j * w) * K(1j * w)
    S = utils.feedback(1, L)
    T = utils.feedback(L, 1)

    plt.subplot(2, 1, 1)
    plt.loglog(w, abs(L))
    plt.loglog(w, abs(S))
    plt.loglog(w, abs(T))
    plt.axis(axlim)
    plt.grid()
    plt.ylabel("Magnitude")
    plt.legend(["L", "S", "T"])

    if margin:
        plt.plot(w, 1 / numpy.sqrt(2) * numpy.ones(len(w)), linestyle='dotted')

    plt.subplot(2, 1, 2)
    plt.semilogx(w, utils.phase(L, deg=True))
    plt.semilogx(w, utils.phase(S, deg=True))
    plt.semilogx(w, utils.phase(T, deg=True))
    plt.axis(axlim)
    plt.grid()
    plt.ylabel("Phase")
    plt.xlabel("Frequency [rad/s]")
예제 #11
0
def input_perfect_const_plot(G, Gd, w_start=-2, w_end=2, axlim=None,
                             points=1000, simultaneous=False):
    """
    Plot for input constraints for perfect control. Applies equation 6.50
    (p240).

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    Gd : numpy matrix
        Plant disturbance model.
    simultaneous : boolean.
        If true, the induced max-norm is calculated for simultaneous
        disturbances (optional).

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    The boundary conditions is values below 1 (p240).
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = numpy.shape(Gd(0))[1]
    perfect_control = numpy.zeros((dim, points))
    # if simultaneous: imn = np.zeros(points)
    for i in range(dim):
        for k in range(points):
            Ginv = numpy.linalg.inv(G(s[k]))
            perfect_control[i, k] = numpy.max(numpy.abs(Ginv * Gd(s[k])[:, i]))
            # if simultaneous:
            # TODO induced max-norm
        plt.loglog(w, perfect_control[i], label=('$g_{d%s}$' % (i + 1)))

    plt.axhline(1., color='red', ls=':')
    plt.ylabel(r'$||G^{-1}.g_d||_{max}$')
    plt.xlabel('Frequency [rad/unit time]')
    plt.grid(True)
    plt.axis(axlim)
    plt.legend()
예제 #12
0
def bodeclosedloop(G, K, w_start=-2, w_end=2,
                   axlim=None, points=1000, margin=False):
    """
    Shows the bode plot for a controller model

    Parameters
    ----------
    G : tf
        Plant transfer function.
    K : tf
        Controller transfer function.
    margin : boolean
        Show the cross over frequencies on the plot (optional).
    """

    _, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    L = G(1j*w) * K(1j*w)
    S = utils.feedback(1, L)
    T = utils.feedback(L, 1)

    plt.subplot(2, 1, 1)
    plt.loglog(w, abs(L))
    plt.loglog(w, abs(S))
    plt.loglog(w, abs(T))
    plt.axis(axlim)
    plt.grid()
    plt.ylabel("Magnitude")
    plt.legend(["L", "S", "T"])

    if margin:
        plt.plot(w, 1/numpy.sqrt(2) * numpy.ones(len(w)), linestyle='dotted')

    plt.subplot(2, 1, 2)
    plt.semilogx(w, utils.phase(L, deg=True))
    plt.semilogx(w, utils.phase(S, deg=True))
    plt.semilogx(w, utils.phase(T, deg=True))
    plt.axis(axlim)
    plt.grid()
    plt.ylabel("Phase")
    plt.xlabel("Frequency [rad/s]")
예제 #13
0
def mimo_bode(G,
              w_start=-2,
              w_end=2,
              axlim=None,
              points=1000,
              Kin=None,
              text=False,
              sv_all=False):
    """
    Plots the max and min singular values of G and computes the crossover
    frequency.

    If a controller is specified, the max and min singular values of S are also
    plotted and the bandwidth frequency computed (p81).

    Parameters
    ----------
    G : numpy matrix
        Matrix of plant transfer functions.
    Kin : numpy matrix
        Controller matrix (optional).
    text : boolean
        If true, the crossover and bandwidth frequencies are
        plotted (optional).
    sv_all : boolean
        If true, plot all the singular values of the plant (optional).

    Returns
    -------
    wC : real
        Crossover frequency.

    wB : real
        Bandwidth frequency.

    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])*10
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1.)
    >>>
    >>> def Kc(s):
    ...     return numpy.array([[0.1, 0.],
    ...                         [0., 0.1]])*10.
    >>> mimo_bode(G, -3, 3, Kc)
    Bandwidth is a tuple of wC, wB
    (0.55557762223988783, 1.3650078065460138)

    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    if Kin is not None:
        plt.subplot(2, 1, 1)

    dim = numpy.shape(G(0.00001))[0]

    def subbode(A, text, crossover, labB, labP):
        Sv = numpy.zeros((len(w), dim), dtype=complex)
        f = False
        wA = 0
        for i in range(len(w)):
            Sv[i, :] = utils.sigmas(G(s[i]))
            if not f:
                if ((labB == 'wC' and Sv[i, -1] < 1)
                        or (labB == 'wB' and Sv[i, 0] > 0.707)):
                    wA = w[i]
                    f = True
        ymin = numpy.min(Sv[:, -1])

        if not sv_all:
            plt.loglog(w, Sv[:, 0], 'b', label=('$\sigma_{max}(%s)$') % labP)
            plt.loglog(w, Sv[:, -1], 'g', label=('$\sigma_{min}(%s)$') % labP)
        else:
            for j in range(dim):
                plt.loglog(w,
                           Sv[:, j],
                           label=('$\sigma_{%s}(%s)$' % (j, labP)))
        plt.axhline(crossover, ls=':', lw=2, color='r')
        if text:
            plt.axvline(wA, ls=':', lw=2, color='r')
            plt.text(wA * 1.1, ymin * 1.1, labB, color='r')
        plt.axis(axlim)
        plt.grid()
        plt.xlabel('Frequency [rad/unit time]')
        plt.ylabel('$\sigma$')
        plt.legend()
        return wA

    wC = subbode(G, text, 1, 'wC', 'G')

    if Kin is None:
        Bandwidth = wC
        if text:
            print('wC = {:.3}'.format(wC))
    else:
        L = Kin(s) * G(s)
        S = numpy.linalg.inv(numpy.eye(dim) + L)  # SVD of S = 1/(I + L)

        wB = subbode(S, text, 0.707, 'wC', 'G')

        Bandwidth = wC, wB
        if text:
            print('(wC = {1}, wB = {2}'.format(wC, wB))

    return Bandwidth
예제 #14
0
def perf_Wp_plot(S,
                 wB_req,
                 maxSSerror,
                 w_start,
                 w_end,
                 axlim=None,
                 points=1000):
    """
    MIMO sensitivity S and performance weight Wp plotting funtion.

    Parameters
    ----------
    S : numpy array
        Sensitivity transfer function matrix as function of s => S(s)
    wB_req : float
        The design or require bandwidth of the plant in rad/time.
        1/time eg: wB_req = 1/20sec = 0.05rad/s
    maxSSerror : float
        The maximum stead state tracking error required of the plant.
    wStart : float
        Minimum power of w for the frequency range in rad/time.
        eg: for w starting at 10e-3, wStart = -3.
    wEnd : float
        Maximum value of w for the frequency range in rad/time.
        eg: for w ending at 10e3, wStart = 3.

    Returns
    -------
    wB : float
        The actually plant bandwidth in rad/time given the specified controller
        used to generate the sensitivity matrix S(s).
    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>> Kc = numpy.array([[0.1, 0.],
    ...                   [0., 0.1]])*10
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1)
    ...
    >>> def L(s):
    ...     return Kc*G(s)
    ...
    >>> def S(s):
    ... # SVD of S = 1/(I + L)
    ...     return numpy.linalg.inv((numpy.eye(2) + L(s)))
    >>> perf_Wp(S, 0.05, 0.2, -3, 1)
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    magPlotS1 = numpy.zeros((len(w)))
    magPlotS3 = numpy.zeros((len(w)))
    Wpi = numpy.zeros((len(w)))
    f = 0  # f for flag
    for i in range(len(w)):
        _, Sv, _ = utils.SVD(S(s[i]))
        magPlotS1[i] = Sv[0]
        magPlotS3[i] = Sv[-1]
        if f < 1 and magPlotS1[i] > 0.707:
            wB = w[i]
            f = 1
    for i in range(len(w)):
        Wpi[i] = utils.Wp(wB_req, maxSSerror, s[i])

    plt.subplot(2, 1, 1)
    plt.loglog(w, magPlotS1, 'r-', label='Max $\sigma$(S)')
    plt.loglog(w, 1. / Wpi, 'k:', label='|1/W$_P$|', lw=2.)
    plt.axhline(0.707, color='green', ls=':', lw=2, label='|S| = 0.707')
    plt.axvline(wB_req, color='blue', ls=':', lw=2)
    plt.text(wB_req * 1.1, 7, 'req wB', color='blue', fontsize=10)
    plt.axvline(wB, color='green')
    plt.text(wB * 1.1,
             0.12,
             'wB = %0.3f rad/s' % wB,
             color='green',
             fontsize=10)
    plt.axis(axlim)
    plt.grid(True)
    plt.xlabel('Frequency [rad/unit time]')
    plt.ylabel('Magnitude')
    plt.legend(loc='upper left', fontsize=10, ncol=1)

    plt.subplot(2, 1, 2)
    plt.semilogx(w, magPlotS1 * Wpi, 'r-', label='|W$_P$S|')
    plt.axhline(1, color='blue', ls=':', lw=2)
    plt.axvline(wB_req, color='blue', ls=':', lw=2, label='|W$_P$S| = 1')
    plt.text(wB_req * 1.1,
             numpy.max(magPlotS1 * Wpi) * 0.95,
             'req wB',
             color='blue',
             fontsize=10)
    plt.axvline(wB, color='green')
    plt.text(wB * 1.1,
             0.12,
             'wB = %0.3f rad/s' % wB,
             color='green',
             fontsize=10)
    plt.axis(axlim)
    plt.xlabel('Frequency [rad/unit time]')
    plt.ylabel('Magnitude')
    plt.legend(loc='upper right', fontsize=10, ncol=1)

    return wB
예제 #15
0
def freq_step_response_plot(G,
                            K,
                            Kc,
                            t_end=50,
                            freqtype='S',
                            w_start=-2,
                            w_end=2,
                            axlim=None,
                            points=1000):
    """
    A subplot function for both the frequency response and step response for a
    controlled plant

    Parameters
    ----------
    G : tf
        Plant transfer function.
    K : tf
        Controller transfer function.
    Kc : integer
        Controller constant.
    t_end : integer
        Time period which the step response should occur.
    freqtype : string (optional)
        Type of function to plot:

        ========    ==================================
        freqtype    Type of function to plot
        ========    ==================================
        S           Sensitivity function
        T           Complementary sensitivity function
        L           Loop function
        ========    ==================================

    Returns
    -------
    Plot : matplotlib figure

    """

    _, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    plt.subplot(1, 2, 1)

    # Controllers transfer function with various controller gains
    Ks = [(kc * K) for kc in Kc]
    Ts = [utils.feedback(G * Kss, 1) for Kss in Ks]

    if freqtype == 'S':
        Fs = [(1 - Tss) for Tss in Ts]
        plt.title('(a) Sensitivity function')
        plt.ylabel('Magnitude $|S|$')
    elif freqtype == 'T':
        Fs = Ts
        plt.title('(a) Complementary sensitivity function')
        plt.ylabel('Magnitude $|T|$')
    else:  # freqtype=='L'
        Fs = [(G * Kss) for Kss in Ks]
        plt.title('(a) Loop function')
        plt.ylabel('Magnitude $|L|$')

    wi = w * 1j
    i = 0
    for F in Fs:
        plt.loglog(w, abs(F(wi)), label='Kc={d%s}=' % Kc[i])
        i += 1
    plt.axis(axlim)
    plt.grid(b=None, which='both', axis='both')
    plt.xlabel('Frequency [rad/unit time]')
    plt.legend(["Kc = %1.2f" % k for k in Kc], loc=4)

    plt.subplot(1, 2, 2)
    plt.title('(b) Response to step in reference')
    tspan = numpy.linspace(0, t_end, points)
    for T in Ts:
        [t, y] = T.step(0, tspan)
        plt.plot(t, y)
    plt.plot(tspan, 0 * numpy.ones(points), ls='--')
    plt.plot(tspan, 1 * numpy.ones(points), ls='--')
    plt.axis(axlim)
    plt.xlabel('Time')
    plt.ylabel('$y(t)$')
예제 #16
0
def ref_perfect_const_plot(G,
                           R,
                           wr,
                           w_start=-2,
                           w_end=2,
                           axlim=None,
                           points=1000,
                           plot_type='all'):
    """
    Use these plots to determine the constraints for perfect control in terms
    of combined reference changes. Equation 6.52 (p241) calculates the
    minimal requirement for input saturation to check in terms of set point
    tracking. A more tighter bounds is calculated with equation 6.53 (p241).

    Parameters
    ----------
    G : tf
        Plant transfer function.
    R : numpy matrix (n x n)
        Reference changes (usually diagonal with all elements larger than 1)
    wr : float
        Frequency up to witch reference tracking is required
    type_eq : string
        Type of plot:

        =========      ==================================
        plot_type      Type of plot
        =========      ==================================
        minimal        Minimal requirement, equation 6.52
        tighter        Tighter requirement, equation 6.53
        allo           All requirements
        =========      ==================================

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    All the plots in this function needs to be larger than 1 for perfect
    control, otherwise input saturation will occur.
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    lab1 = '$\sigma_{min} (G(jw))$'
    bound1 = [utils.sigmas(G(si), 'min') for si in s]
    lab2 = '$\sigma_{min} (R^{-1}G(jw))$'
    bound2 = [utils.sigmas(numpy.linalg.pinv(R) * G(si), 'min') for si in s]

    if plot_type == 'all':
        plt.loglog(w, bound1, label=lab1)
        plt.loglog(w, bound2, label=lab2)
    elif plot_type == 'minimal':
        plt.loglog(w, bound1, label=lab1)
    elif plot_type == 'tighter':
        plt.loglog(w, bound2, label=lab2)

    else:
        raise ValueError('Invalid plot_type parameter.')

    mm = bound1 + bound2 + [1]  # ensures that whole graph is visible
    plt.loglog([wr, wr], [0.5 * numpy.min(mm), 5 * numpy.max(mm)],
               'r',
               ls=':',
               label='Ref tracked')
    plt.loglog([w[0], w[-1]], [1, 1], 'r', label='Bound')
    plt.legend()
예제 #17
0
def rga_nm_plot(G,
                pairing_list=None,
                pairing_names=None,
                w_start=-2,
                w_end=2,
                axlim=None,
                points=1000,
                plot_type='all'):
    """
    Plots the RGA number for a specified pairing

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    pairing_list : List of sparse numpy matrices of the same shape as G.
        An array of zeros with a 1. at each required output-input pairing.
        The default is a diagonal pairing with 1.'s on the diagonal.
    plot_type : string
        Type of plot:

        =========      =============================
        plot_type      Type of plot
        =========      =============================
        all            All the pairings on one plot
        element        Each pairing has its own plot
        =========      =============================

    Returns
    -------
    Plot : matplotlib figure

    Example
    -------
    # Adapted from example 3.11 pg 86 S. Skogestad
    >>> def G(s):
    ...     G = 0.01**(-5*s)/((s + 1.72e-4)*(4.32*s + 1))*numpy.matrix([[-34.54*(s + 0.0572), 1.913], [-30.22*s, -9.188*(s + 6.95e-4)]])
    ...     return G
    >>> pairing = numpy.matrix([[1., 0.], [0., 1.]])
    >>> rga_nm_plot(G, [pairing], w_start=-5, w_end=2, axlim=[None, None, 0., 1.])

    Note
    ----
    This plotting function can only be used on square systems
    """
    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = numpy.shape(G(0))  # Number of rows & columns in SS transfer function
    freqresp = [G(si) for si in s]
    # Setting a blank entry to the default of a diagonal comparison
    if pairing_list is None:
        pairing_list = numpy.identity(dim[0])
        pairing_names = 'Diagonal pairing'
    else:
        for pairing in pairing_list:
            if pairing.shape != dim:
                raise ValueError('Make sure input matrix is square')

    plot_No = 0

    if plot_type == 'all':
        for p in pairing_list:
            plt.semilogx(w, [utils.RGAnumber(Gfr, p) for Gfr in freqresp],
                         label=pairing_names[plot_No])
            plot_No += 1
        plt.axis(axlim)
        plt.ylabel('||$\Lambda$(G) - I||$_{sum}$')
        plt.xlabel('Frequency [rad/unit time]')
        plt.legend()

    elif plot_type == 'element':
        # pairing_list.count not accessible
        pcount = numpy.shape(pairing_list)[0]
        for p in pairing_list:
            plot_No += 1
            plt.subplot(1, pcount, plot_No)
            plt.semilogx(w, [utils.RGAnumber(Gfr, p) for Gfr in freqresp])
            plt.axis(axlim)
            plt.title(pairing_names[plot_No - 1])
            plt.xlabel('Frequency [rad/unit time]')
            if plot_No == 1:
                plt.ylabel('||$\Lambda$(G) - I||$_{sum}$')
    else:
        raise ValueError("Invalid plot_type parameter.")
예제 #18
0
def rga_plot(G, w_start=-2, w_end=2, axlim=None, points=1000, fig=0, plot_type='elements', input_label=None, output_label=None):
    """
    Plots the relative gain interaction between each output and input pairing

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    plot_type : string
        Type of plot.

        =========      ============================
        plot_type      Type of plot
        =========      ============================
        all            All the RGAs on one plot
        output         Plots grouped by output
        input          Plots grouped by input
        element        Each element has its own plot
        =========      ============================

    Returns
    -------
    Plot : matplotlib figure

    Example
    -------
    Adapted from example 3.11 pg 86 S. Skogestad

    >>> def G(s):
    ...     G = 0.01**(-5*s)/((s + 1.72e-4)*(4.32*s + 1))*numpy.matrix([[-34.54*(s + 0.0572), 1.913], [-30.22*s, -9.188*(s + 6.95e-4)]])
    ...     return G
    >>> rga_plot(G, w_start=-5, w_end=2, axlim=[None, None, 0., 1.])
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = G(0).shape # Number of rows and columns in SS transfer function
    freqresp = [G(si) for si in s]

    plot_No = 1

    if (input_label is None) and (output_label is None):
        labels = False
    elif numpy.shape(input_label)[0] == numpy.shape(output_label)[0]:
        labels = True
    else:
        raise ValueError('Input and output label count is not equal')

    if plot_type == 'elements':
        fig = adjust_spine('Frequency [rad/unit time]', 'RGA magnitude', -0.05, -0.03, 0.8, 0.9)
        for i in range(dim[0]):
            for j in range(dim[1]):
                ax = fig.add_subplot(dim[0], dim[1], plot_No)
                if labels:
                    ax.set_title('Output (%s) vs. Input (%s)' % (output_label[i], input_label[j]))
                else:
                    ax.set_title('Output %s vs. Input %s' % (i + 1, j + 1))
                ax.semilogx(w, numpy.array(numpy.abs(([utils.RGA(Gfr)[i, j] for Gfr in freqresp]))))
                plot_No += 1

                ax.axis(axlim)
                ax.set_ylabel('$|\lambda$$_{%s, %s}|$' % (i + 1, j + 1))
                box = ax.get_position()
                ax.set_position([box.x0, box.y0,
                                 box.width * 0.8, box.height * 0.9])

    elif plot_type == 'outputs':  # i
        fig = adjust_spine('Frequency [rad/unit time]','RGA magnitude', -0.05, -0.03, 1, 0.9)
        for i in range(dim[0]):
            ax = fig.add_subplot(dim[1], 1, plot_No)
            ax.set_title('Output %s vs. input j' % (i + 1))
            rgamax = []
            for j in range(dim[1]):
                rgas = numpy.array(numpy.abs(([utils.RGA(Gfr)[i, j] for Gfr in freqresp])))
                ax.semilogx(w, rgas, label='$\lambda$$_{%s, %s}$' % (i + 1, j + 1))
                rgamax.append(max(rgas))

                if j == dim[1] - 1:  # self-scaling algorithm
                    if axlim is not None:
                        ax.axis(axlim)
                    else:
                        ax.axis([None, None, None, max(rgamax)])

            ax.set_ylabel('$|\lambda$$_{%s, j}|$' % (i + 1))
            box = ax.get_position()
            ax.set_position([box.x0, box.y0,
                             box.width, box.height * 0.9])
            ax.legend()
            plot_No += 1

    elif plot_type == 'inputs':  # j
        fig = adjust_spine('Frequency [rad/unit time]','RGA magnitude', -0.05, -0.03, 1, 0.9)
        for j in range(dim[1]):
            ax = fig.add_subplot(dim[0], 1, plot_No)
            ax.set_title('Output i vs. input %s' % (j + 1))
            rgamax = []
            for i in range(dim[0]):
                rgas = numpy.array(numpy.abs(([utils.RGA(Gfr)[i, j] for Gfr in freqresp])))
                ax.semilogx(w, rgas, label='$\lambda$$_{%s, %s}$' % (i + 1, j + 1))
                rgamax.append(max(rgas))

                if i == dim[1] - 1:  # self-scaling algorithm
                    if axlim is not None:
                        ax.axis(axlim)
                    else:
                        ax.axis([None, None, None, max(rgamax)])

            ax.set_ylabel('$|\lambda$$_{i, %s}|$' % (j + 1))
            box = ax.get_position()
            ax.set_position([box.x0, box.y0,
                             box.width, box.height * 0.9])
            ax.legend()
            plot_No += 1

    elif plot_type == 'all':
        for i in range(dim[0]):
            for j in range(dim[1]):
                plt.semilogx(w, numpy.array(numpy.abs(([utils.RGA(Gfr)[i, j] for Gfr in freqresp]))))
                plt.axis(axlim)
                plt.ylabel('|$\lambda$$_{i,j}$|')
                plt.xlabel('Frequency [rad/unit time]')

    else:
        raise ValueError("Invalid plot_type parameter.")
예제 #19
0
def mimo_bode(G, w_start=-2, w_end=2,
              axlim=None, points=1000,
              Kin=None, text=False, sv_all=False, legend_loc='best'):
    """
    Plots the max and min singular values of G and computes the crossover
    frequency.

    If a controller is specified, the max and min singular values of S are also
    plotted and the bandwidth frequency computed (p81).

    Parameters
    ----------
    G : numpy matrix
        Matrix of plant transfer functions.
    Kin : numpy matrix
        Controller matrix (optional).
    text : boolean
        If true, the crossover and bandwidth frequencies are
        plotted (optional).
    sv_all : boolean
        If true, plot all the singular values of the plant (optional).
    legend_loc : string
        String that sets the legend location. See matplotlib documentation for options.

    Returns
    -------
    wC : real
        Crossover frequency.

    wB : real
        Bandwidth frequency.

    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])*10
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1.)
    >>>
    >>> def Kc(s):
    ...     return numpy.array([[1., 0.],
    ...                         [0., 1.]])*10.
    >>> mimo_bode(G, -3, 3, Kc)
    0.5555776222398878

    """
    # TODO: The previous example returned (at some point) the following: (0.55557762223988783, 1.3650078065460138)
    # however, Kc is not assigned to anything inside of this function currently.
    # from the current expected result, it is obvious that wC is correct, however wB should be
    # determined properly.
    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    if Kin is not None:
        plt.subplot(2, 1, 1)

    dim = numpy.shape(G(0.00001))[0]

    def subbode(A, text, crossover, labB, labP):
        Sv = numpy.zeros((len(w), dim), dtype=complex)
        f = False
        wA = 0
        for i in range(len(w)):
            Sv[i, :] = utils.sigmas(G(s[i]))
            if not f:
                if ((labB == 'wC' and Sv[i, -1] < 1) or
                        (labB == 'wB' and Sv[i, 0] > 0.707)):
                    wA = w[i]
                    f = True
        ymin = numpy.min(Sv[:, -1])

        if not sv_all:
            plt.loglog(w, Sv[:, 0], 'b', label=('$\sigma_{max}(%s)$') % labP)
            plt.loglog(w, Sv[:, -1], 'g', label=('$\sigma_{min}(%s)$') % labP)
        else:
            for j in range(dim):
                plt.loglog(w, Sv[:, j],
                           label=('$\sigma_{%s}(%s)$' % (j, labP)))
        plt.axhline(crossover, ls=':', lw=2, color='r')
        if text:
            plt.axvline(wA, ls=':', lw=2, color='r')
            plt.text(wA*1.1, ymin*1.1, labB, color='r')
        # This function does not seem to do anything, since axlim is a list of
        # none values. See doc_func.py.frequency_spot_setup
        # plt.axis(axlim)
        plt.grid()
        plt.xlabel('Frequency [rad/unit time]')
        plt.ylabel('$\sigma$')
        plt.legend(loc=legend_loc)
        return wA

    wC = subbode(G, text, 1, 'wC', 'G')

    if Kin is None:
        Bandwidth = wC
        if text:
            print('wC = {:.3}'.format(wC))
    else:
        L = Kin(s) * G(s)
        S = numpy.linalg.inv(numpy.eye(dim) + L)  # SVD of S = 1/(I + L)

        wB = subbode(S, text, 0.707, 'wC', 'G')

        Bandwidth = wC, wB
        if text:
            print('(wC = {1}, wB = {2}'.format(wC, wB))

    return Bandwidth
예제 #20
0
def mimo_bode(G, w_start=-2, w_end=2, axlim=None, points=1000, Kin=None, text=False, sv_all=False):
    """
    Plots the max and min singular values of G and computes the crossover
    frequency.

    If a controller is specified, the max and min singular values of S are also
    plotted and the bandwidth frequency computed (p81).

    Parameters
    ----------
    G : numpy matrix
        Matrix of plant transfer functions.
    Kin : numpy matrix
        Controller matrix (optional).
    text : boolean
        If true, the crossover and bandwidth frequencies are plotted (optional).
    sv_all : boolean
        If true, plot all the singular values of the plant (optional).

    Returns
    -------
    wC : real
        Crossover frequency.

    wB : real
        Bandwidth frequency.

    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])*10
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1.)
    >>>
    >>> def Kc(s):
    ...     return numpy.array([[0.1, 0.],
    ...                         [0., 0.1]])*10.
    >>> mimo_bode(G, -3, 3, Kc)
    Bandwidth is a tuple of wC, wB
    (0.55557762223988783, 1.3650078065460138)

    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    if Kin is not None:
        plt.subplot(2, 1, 1)

    dim = numpy.shape(G(0.00001))[0]

    def subbode(A, text, crossover, labB, labP):
        Sv = numpy.zeros((len(w), dim), dtype=complex)
        f = False
        wA = 0
        for i in range(len(w)):
            Sv[i, :] = utils.sigmas(G(s[i]))
            if not f:
                if (labB == 'wC' and Sv[i, -1] < 1) or (labB == 'wB' and Sv[i, 0] > 0.707):
                    wA = w[i]
                    f = True
        ymin = numpy.min(Sv[:, -1])

        if not sv_all:
            plt.loglog(w, Sv[:, 0], 'b', label=('$\sigma_{max}(%s)$') % labP)
            plt.loglog(w, Sv[:, -1], 'g', label=('$\sigma_{min}(%s)$') % labP)
        else:
            for j in range(dim):
                plt.loglog(w, Sv[:, j], label=('$\sigma_{%s}(%s)$' % (j, labP)))
        plt.axhline(crossover, ls=':', lw=2, color='r')
        if text:
            plt.axvline(wA, ls=':', lw=2, color='r')
            plt.text(wA*1.1, ymin*1.1, labB, color='r')
        plt.axis(axlim)
        plt.grid()
        plt.xlabel('Frequency [rad/unit time]')
        plt.ylabel('$\sigma$')
        plt.legend()
        return wA

    wC = subbode(G, text, 1, 'wC', 'G')

    if Kin is None:
        Bandwidth = wC
        if text:
            print('wC = {:.3}'.format(wC))
    else:
        L = Kin(s) * G(s)
        S = numpy.linalg.inv(numpy.eye(dim) + L)  # SVD of S = 1/(I + L)

        wB = subbode(S, text, 0.707, 'wC', 'G')

        Bandwidth = wC, wB
        if text:
            print('(wC = {1}, wB = {2}'.format(wC, wB))

    return Bandwidth
예제 #21
0
def perf_Wp_plot(S, wB_req, maxSSerror, w_start, w_end, axlim=None, points=1000):
    """
    MIMO sensitivity S and performance weight Wp plotting funtion.

    Parameters
    ----------
    S : numpy array
        Sensitivity transfer function matrix as function of s => S(s)
    wB_req : float
        The design or require bandwidth of the plant in rad/time.
        1/time eg: wB_req = 1/20sec = 0.05rad/s
    maxSSerror : float
        The maximum stead state tracking error required of the plant.
    wStart : float
        Minimum power of w for the frequency range in rad/time.
        eg: for w starting at 10e-3, wStart = -3.
    wEnd : float
        Maximum value of w for the frequency range in rad/time.
        eg: for w ending at 10e3, wStart = 3.

    Returns
    -------
    wB : float
        The actually plant bandwidth in rad/time given the specified controller
        used to generate the sensitivity matrix S(s).
    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>> Kc = numpy.array([[0.1, 0.],
    ...                   [0., 0.1]])*10
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1)
    ...
    >>> def L(s):
    ...     return Kc*G(s)
    ...
    >>> def S(s):
    ... # SVD of S = 1/(I + L)
    ...     return numpy.linalg.inv((numpy.eye(2) + L(s)))
    >>> perf_Wp(S, 0.05, 0.2, -3, 1)
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    magPlotS1 = numpy.zeros((len(w)))
    magPlotS3 = numpy.zeros((len(w)))
    Wpi = numpy.zeros((len(w)))
    f = 0                                    # f for flag
    for i in range(len(w)):
        _, Sv, _ = utils.SVD(S(s[i]))
        magPlotS1[i] = Sv[0]
        magPlotS3[i] = Sv[-1]
        if f < 1 and magPlotS1[i] > 0.707:
            wB = w[i]
            f = 1
    for i in range(len(w)):
        Wpi[i] = utils.Wp(wB_req, maxSSerror, s[i])

    plt.subplot(2, 1, 1)
    plt.loglog(w, magPlotS1, 'r-', label='Max $\sigma$(S)')
    plt.loglog(w, 1./Wpi, 'k:', label='|1/W$_P$|', lw=2.)
    plt.axhline(0.707, color='green', ls=':', lw=2, label='|S| = 0.707')
    plt.axvline(wB_req, color='blue', ls=':', lw=2)
    plt.text(wB_req*1.1, 7, 'req wB', color='blue', fontsize=10)
    plt.axvline(wB, color='green')
    plt.text(wB*1.1, 0.12, 'wB = %0.3f rad/s' % wB, color='green', fontsize=10)
    plt.axis(axlim)
    plt.grid(True)
    plt.xlabel('Frequency [rad/unit time]')
    plt.ylabel('Magnitude')
    plt.legend(loc='upper left', fontsize=10, ncol=1)

    plt.subplot(2, 1, 2)
    plt.semilogx(w, magPlotS1*Wpi, 'r-', label='|W$_P$S|')
    plt.axhline(1, color='blue', ls=':', lw=2)
    plt.axvline(wB_req, color='blue', ls=':', lw=2, label='|W$_P$S| = 1')
    plt.text(wB_req*1.1, numpy.max(magPlotS1*Wpi)*0.95, 'req wB', color='blue', fontsize=10)
    plt.axvline(wB, color='green')
    plt.text(wB*1.1, 0.12, 'wB = %0.3f rad/s' % wB, color='green', fontsize=10)
    plt.axis(axlim)
    plt.xlabel('Frequency [rad/unit time]')
    plt.ylabel('Magnitude')
    plt.legend(loc='upper right', fontsize=10, ncol=1)

    return wB
예제 #22
0
def mimo_bode(G,
              w_start=-2,
              w_end=2,
              axlim=None,
              points=1000,
              Kin=None,
              text=False,
              sv_all=False,
              legend_loc='best'):
    """
    Plots the max and min singular values of G and computes the crossover
    frequency.

    If a controller is specified, the max and min singular values of S are also
    plotted and the bandwidth frequency computed (p81).

    Parameters
    ----------
    G : numpy matrix
        Matrix of plant transfer functions.
    Kin : numpy matrix
        Controller matrix (optional).
    text : boolean
        If true, the crossover and bandwidth frequencies are
        plotted (optional).
    sv_all : boolean
        If true, plot all the singular values of the plant (optional).
    legend_loc : string
        String that sets the legend location. See matplotlib documentation for options.

    Returns
    -------
    wC : real
        Crossover frequency.

    wB : real
        Bandwidth frequency.

    Plot : matplotlib figure

    Example
    -------
    >>> K = numpy.array([[1., 2.],
    ...                  [3., 4.]])*10
    >>> t1 = numpy.array([[5., 5.],
    ...                   [5., 5.]])
    >>> t2 = numpy.array([[5., 6.],
    ...                   [7., 8.]])
    >>>
    >>> def G(s):
    ...     return K*numpy.exp(-t1*s)/(t2*s + 1.)
    >>>
    >>> def Kc(s):
    ...     return numpy.array([[1., 0.],
    ...                         [0., 1.]])*10.
    >>> mimo_bode(G, -3, 3, Kc)
    0.5555776222398878

    """
    # TODO: The previous example returned (at some point) the following: (0.55557762223988783, 1.3650078065460138)
    # however, Kc is not assigned to anything inside of this function currently.
    # from the current expected result, it is obvious that wC is correct, however wB should be
    # determined properly.
    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    if Kin is not None:
        plt.subplot(2, 1, 1)

    dim = numpy.shape(G(0.00001))[0]

    def subbode(A, text, crossover, labB, labP):
        Sv = numpy.zeros((len(w), dim), dtype=complex)
        f = False
        wA = 0
        for i in range(len(w)):
            Sv[i, :] = utils.sigmas(G(s[i]))
            if not f:
                if ((labB == 'wC' and Sv[i, -1] < 1)
                        or (labB == 'wB' and Sv[i, 0] > 0.707)):
                    wA = w[i]
                    f = True
        ymin = numpy.min(Sv[:, -1])

        if not sv_all:
            plt.loglog(w, Sv[:, 0], 'b', label=('$\sigma_{max}(%s)$') % labP)
            plt.loglog(w, Sv[:, -1], 'g', label=('$\sigma_{min}(%s)$') % labP)
        else:
            for j in range(dim):
                plt.loglog(w,
                           Sv[:, j],
                           label=('$\sigma_{%s}(%s)$' % (j, labP)))
        plt.axhline(crossover, ls=':', lw=2, color='r')
        if text:
            plt.axvline(wA, ls=':', lw=2, color='r')
            plt.text(wA * 1.1, ymin * 1.1, labB, color='r')
        # This function does not seem to do anything, since axlim is a list of
        # none values. See doc_func.py.frequency_spot_setup
        # plt.axis(axlim)
        plt.grid()
        plt.xlabel('Frequency [rad/unit time]')
        plt.ylabel('$\sigma$')
        plt.legend(loc=legend_loc)
        return wA

    wC = subbode(G, text, 1, 'wC', 'G')

    if Kin is None:
        Bandwidth = wC
        if text:
            print('wC = {:.3}'.format(wC))
    else:
        L = Kin(s) * G(s)
        S = numpy.linalg.inv(numpy.eye(dim) + L)  # SVD of S = 1/(I + L)

        wB = subbode(S, text, 0.707, 'wC', 'G')

        Bandwidth = wC, wB
        if text:
            print('(wC = {1}, wB = {2}'.format(wC, wB))

    return Bandwidth
예제 #23
0
def sv_dir_plot(G, plot_type, w_start=-2, w_end=2, axlim=None, points=1000):
    """
    Plot the input and output singular vectors associated with the minimum and
    maximum singular values.

    Parameters
    ----------
    G : matrix
        Plant model or sensitivity function.
    plot_type : string
        Type of plot.

        =========      ============================
        plot_type      Type of plot
        =========      ============================
        input          Plots input vectors
        output         Plots output vectors
        =========      ============================

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    Can be used with the plant matrix G and the sensitivity function S
    for controlability analysis
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    freqresp = [G(si) for si in s]

    if plot_type == 'input':
        vec = numpy.array([V for _, _, V in map(utils.SVD, freqresp)])
        d = 'v'
    elif plot_type == 'output':
        vec = numpy.array([U for U, _, _ in map(utils.SVD, freqresp)])
        d = 'u'
    else:
        raise ValueError('Invalid plot_type parameter.')

    dim = numpy.shape(vec)[1]
    for i in range(dim):
        plt.subplot(dim * 2, 1, 2 * i + 1)
        plt.semilogx(w, abs(vec[:, i, 0]), label='$%s_{max}$' % d, lw=2)
        plt.semilogx(w, abs(vec[:, i, -1]), label='$%s_{min}$' % d, lw=2)
        plt.axhline(0, color='red', ls=':')
        plt.axis(axlim)
        plt.ylabel('$|{0}_{1}|$'.format(d, i + 1))
        plt.subplot(dim * 2, 1, 2 * i + 2)
        plt.semilogx(w,
                     utils.phase(vec[:, i, 0], deg=True),
                     label='$%s_{max}$' % d,
                     lw=2)
        plt.semilogx(w,
                     utils.phase(vec[:, i, -1], deg=True),
                     label='$%s_{min}$' % d,
                     lw=2)
        plt.axhline(0, color='red', ls=':')
        plt.axis(axlim)
        plt.ylabel(r'$\angle {0}_{1}$'.format(d, i + 1))

    plt.xlabel('Frequency [rad/unit time]')
예제 #24
0
def rga_plot(G,
             w_start=-2,
             w_end=2,
             axlim=None,
             points=1000,
             fig=0,
             plot_type='elements',
             input_label=None,
             output_label=None):
    """
    Plots the relative gain interaction between each output and input pairing

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    plot_type : string
        Type of plot.

        =========      ============================
        plot_type      Type of plot
        =========      ============================
        all            All the RGAs on one plot
        output         Plots grouped by output
        input          Plots grouped by input
        element        Each element has its own plot
        =========      ============================

    Returns
    -------
    Plot : matplotlib figure

    Example
    -------
    Adapted from example 3.11 pg 86 S. Skogestad

    >>> def G(s):
    ...     G = 0.01**(-5*s)/((s + 1.72e-4)*(4.32*s + 1))*numpy.matrix([[-34.54*(s + 0.0572), 1.913], [-30.22*s, -9.188*(s + 6.95e-4)]])
    ...     return G
    >>> rga_plot(G, w_start=-5, w_end=2, axlim=[None, None, 0., 1.])
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = G(0).shape  # Number of rows and columns in SS transfer function
    freqresp = [G(si) for si in s]

    plot_No = 1

    if (input_label is None) and (output_label is None):
        labels = False
    elif numpy.shape(input_label)[0] == numpy.shape(output_label)[0]:
        labels = True
    else:
        raise ValueError('Input and output label count is not equal')

    if plot_type == 'elements':
        fig = adjust_spine('Frequency [rad/unit time]', 'RGA magnitude', -0.05,
                           -0.03, 0.8, 0.9)
        for i in range(dim[0]):
            for j in range(dim[1]):
                ax = fig.add_subplot(dim[0], dim[1], plot_No)
                if labels:
                    ax.set_title('Output (%s) vs. Input (%s)' %
                                 (output_label[i], input_label[j]))
                else:
                    ax.set_title('Output %s vs. Input %s' % (i + 1, j + 1))
                ax.semilogx(
                    w,
                    numpy.array(
                        numpy.abs(
                            ([utils.RGA(Gfr)[i, j] for Gfr in freqresp]))))
                plot_No += 1

                ax.axis(axlim)
                ax.set_ylabel('$|\lambda$$_{%s, %s}|$' % (i + 1, j + 1))
                box = ax.get_position()
                ax.set_position(
                    [box.x0, box.y0, box.width * 0.8, box.height * 0.9])

    elif plot_type == 'outputs':  # i
        fig = adjust_spine('Frequency [rad/unit time]', 'RGA magnitude', -0.05,
                           -0.03, 1, 0.9)
        for i in range(dim[0]):
            ax = fig.add_subplot(dim[1], 1, plot_No)
            ax.set_title('Output %s vs. input j' % (i + 1))
            rgamax = []
            for j in range(dim[1]):
                rgas = numpy.array(
                    numpy.abs(([utils.RGA(Gfr)[i, j] for Gfr in freqresp])))
                ax.semilogx(w,
                            rgas,
                            label='$\lambda$$_{%s, %s}$' % (i + 1, j + 1))
                rgamax.append(max(rgas))

                if j == dim[1] - 1:  # self-scaling algorithm
                    if axlim is not None:
                        ax.axis(axlim)
                    else:
                        ax.axis([None, None, None, max(rgamax)])

            ax.set_ylabel('$|\lambda$$_{%s, j}|$' % (i + 1))
            box = ax.get_position()
            ax.set_position([box.x0, box.y0, box.width, box.height * 0.9])
            ax.legend()
            plot_No += 1

    elif plot_type == 'inputs':  # j
        fig = adjust_spine('Frequency [rad/unit time]', 'RGA magnitude', -0.05,
                           -0.03, 1, 0.9)
        for j in range(dim[1]):
            ax = fig.add_subplot(dim[0], 1, plot_No)
            ax.set_title('Output i vs. input %s' % (j + 1))
            rgamax = []
            for i in range(dim[0]):
                rgas = numpy.array(
                    numpy.abs(([utils.RGA(Gfr)[i, j] for Gfr in freqresp])))
                ax.semilogx(w,
                            rgas,
                            label='$\lambda$$_{%s, %s}$' % (i + 1, j + 1))
                rgamax.append(max(rgas))

                if i == dim[1] - 1:  # self-scaling algorithm
                    if axlim is not None:
                        ax.axis(axlim)
                    else:
                        ax.axis([None, None, None, max(rgamax)])

            ax.set_ylabel('$|\lambda$$_{i, %s}|$' % (j + 1))
            box = ax.get_position()
            ax.set_position([box.x0, box.y0, box.width, box.height * 0.9])
            ax.legend()
            plot_No += 1

    elif plot_type == 'all':
        for i in range(dim[0]):
            for j in range(dim[1]):
                plt.semilogx(
                    w,
                    numpy.array(
                        numpy.abs([utils.RGA(Gfr)[i, j] for Gfr in freqresp])))
                plt.axis(axlim)
                plt.ylabel('|$\lambda$$_{i,j}$|')
                plt.xlabel('Frequency [rad/unit time]')

    else:
        raise ValueError("Invalid plot_type parameter.")
예제 #25
0
def rga_nm_plot(G, pairing_list=None, pairing_names=None, w_start=-2, w_end=2, axlim=None, points=1000, plot_type='all'):
    """
    Plots the RGA number for a specified pairing

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    pairing_list : List of sparse numpy matrices of the same shape as G.
        An array of zeros with a 1. at each required output-input pairing.
        The default is a diagonal pairing with 1.'s on the diagonal.
    plot_type : string
        Type of plot:

        =========      =============================
        plot_type      Type of plot
        =========      =============================
        all            All the pairings on one plot
        element        Each pairing has its own plot
        =========      =============================

    Returns
    -------
    Plot : matplotlib figure

    Example
    -------
    # Adapted from example 3.11 pg 86 S. Skogestad
    >>> def G(s):
    ...     G = 0.01**(-5*s)/((s + 1.72e-4)*(4.32*s + 1))*numpy.matrix([[-34.54*(s + 0.0572), 1.913], [-30.22*s, -9.188*(s + 6.95e-4)]])
    ...     return G
    >>> pairing = numpy.matrix([[1., 0.], [0., 1.]])
    >>> rga_nm_plot(G, [pairing], w_start=-5, w_end=2, axlim=[None, None, 0., 1.])

    Note
    ----
    This plotting function can only be used on square systems
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = numpy.shape(G(0))  # Number of rows and columns in SS transfer function
    freqresp = [G(si) for si in s]

    if pairing_list is None:  # Setting a blank entry to the default of a diagonal comparison
        pairing_list = numpy.identity(dim[0])
        pairing_names = 'Diagonal pairing'
    else:
        for pairing in pairing_list:
            if pairing.shape != dim:
                raise ValueError('RGA_Number_Plot on plots square n by n matrices, make sure input matrix is square')

    plot_No = 0

    if plot_type == 'all':
        for p in pairing_list:
            plt.semilogx(w, [utils.RGAnumber(Gfr, p) for Gfr in freqresp], label=pairing_names[plot_No])
            plot_No += 1
        plt.axis(axlim)
        plt.ylabel('||$\Lambda$(G) - I||$_{sum}$')
        plt.xlabel('Frequency [rad/unit time]')
        plt.legend()

    elif plot_type == 'element':
        pcount = numpy.shape(pairing_list)[0]  # pairing_list.count not accessible
        for p in pairing_list:
            plot_No += 1
            plt.subplot(1, pcount, plot_No)
            plt.semilogx(w, [utils.RGAnumber(Gfr, p) for Gfr in freqresp])
            plt.axis(axlim)
            plt.title(pairing_names[plot_No - 1])
            plt.xlabel('Frequency [rad/unit time]')
            if plot_No == 1:
                plt.ylabel('||$\Lambda$(G) - I||$_{sum}$')
    else:
        raise ValueError("Invalid plot_type parameter.")
예제 #26
0
def dis_rejctn_plot(G,
                    Gd,
                    S=None,
                    w_start=-2,
                    w_end=2,
                    axlim=None,
                    points=1000):
    """
    A subplot of disturbance condition number to check for input saturation
    (equation 6.43, p238). Two more subplots indicate if the disturbances fall
    withing the bounds of S, applying equations 6.45 and 6.46 (p239).

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    Gd : numpy matrix
        Plant disturbance model.
    S : numpy matrix
        Sensitivity function (optional, if available).
    # TODO test S condition
    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    The disturbance condition number provides a measure of how a disturbance gd
    is aligned with the plant G. Alignment can vary between 1 and the condition
    number.

    For acceptable performance the singular values of S must fall below the
    inverse 2-norm of gd.
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = numpy.shape(Gd(0))[1]  # column count
    inv_norm_gd = numpy.zeros((dim, points))
    # row count
    yd = numpy.zeros((dim, points, numpy.shape(Gd(0))[0]), dtype=complex)
    condtn_nm_gd = numpy.zeros((dim, points))
    for i in range(dim):
        for k in range(points):
            inv_norm_gd[i, k], yd[i, k, :], condtn_nm_gd[i, k] = utils.distRej(
                G(s[k]),
                Gd(s[k])[:, i])

    if S is None:
        sub = 2
    else:
        sub = 3

    # Equation 6.43
    plt.subplot(sub, 1, 1)
    for i in range(dim):
        plt.loglog(w, condtn_nm_gd[i], label=('$\gamma_{d%s} (G)$' % (i + 1)))
    plt.axhline(1., color='red', ls=':')
    plt.axis(axlim)
    plt.xlabel('Frequency [rad/unit time]')
    plt.ylabel('$\gamma$$_d (G)$')
    plt.axhline(1., color='red', ls=':')
    plt.legend()

    # Equation 6.44
    plt.subplot(sub, 1, 2)
    for i in range(dim):
        plt.loglog(w, inv_norm_gd[i], label=('$1/||g_{d%s}||_2$' % (i + 1)))
        if S is not None:
            S_yd = numpy.array([
                numpy.linalg.norm(S(p) * yd[i, p, :].T, 2)
                for p in range(points)
            ])
            plt.loglog(w, S_yd, label='$||Sy_d||_2$')
    plt.axis(axlim)
    plt.xlabel('Frequency [rad/unit time]')
    plt.legend()

    if S is not None:  # subplot should not be repeated with S is not available
        # Equation 6.45
        plt.subplot(3, 1, 3)
        for i in range(dim):
            plt.loglog(w,
                       inv_norm_gd[i],
                       label=('$1/||g_{d%s}||_2$' % (i + 1)))
            s_min = numpy.array(
                [utils.sigmas(S(s[p]), 'min') for p in range(points)])
            s_max = numpy.array(
                [utils.sigmas(S(s[p]), 'max') for p in range(points)])
            plt.loglog(w, s_min, label='$\sigma_{min}$')
            plt.loglog(w, s_max, label='$\sigma_{max}$')
        plt.axis(axlim)
        plt.xlabel('Frequency [rad/unit time]')
        plt.legend()
예제 #27
0
def dis_rejctn_plot(G, Gd, S=None, w_start=-2, w_end=2, axlim=None, points=1000):
    """
    A subplot of disturbance condition number to check for input saturation
    (equation 6.43, p238). Two more subplots indicate if the disturbances fall
    withing the bounds of S, applying equations 6.45 and 6.46 (p239).

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    Gd : numpy matrix
        Plant disturbance model.
    S : numpy matrix
        Sensitivity function (optional, if available).
    # TODO test S condition
    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    The disturbance condition number provides a measure of how a disturbance gd
    is aligned with the plant G. Alignment can vary between 1 and the condition
    number.

    For acceptable performance the singular values of S must fall below the
    inverse 2-norm of gd.
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    dim = numpy.shape(Gd(0))[1]  # column count
    inv_norm_gd = numpy.zeros((dim, points))
    yd = numpy.zeros((dim, points, numpy.shape(Gd(0))[0]), dtype=complex)  # row count
    condtn_nm_gd = numpy.zeros((dim, points))
    for i in range(dim):
        for k in range(points):
            inv_norm_gd[i, k], yd[i, k, :], condtn_nm_gd[i, k] = utils.distRej(G(s[k]), Gd(s[k])[:, i])

    if S is None:
        sub = 2
    else:
        sub = 3

    # Equation 6.43
    plt.subplot(sub, 1, 1)
    for i in range(dim):
        plt.loglog(w, condtn_nm_gd[i], label=('$\gamma_{d%s} (G)$' % (i+1)))
    plt.axhline(1., color='red', ls=':')
    plt.axis(axlim)
    plt.xlabel('Frequency [rad/unit time]')
    plt.ylabel('$\gamma$$_d (G)$')
    plt.axhline(1., color='red', ls=':')
    plt.legend()

    # Equation 6.44
    plt.subplot(sub, 1, 2)
    for i in range(dim):
        plt.loglog(w, inv_norm_gd[i], label=('$1/||g_{d%s}||_2$' % (i+1)))
        if not S is None:
            S_yd = numpy.array([numpy.linalg.norm(S(p) * yd[i, p, :].T, 2) for p in range(points)])
            plt.loglog(w, S_yd, label='$||Sy_d||_2$')
    plt.axis(axlim)
    plt.xlabel('Frequency [rad/unit time]')
    plt.legend()

    if S is not None:  # this subplot should not be repeated with S is not available
        # Equation 6.45
        plt.subplot(3, 1, 3)
        for i in range(dim):
            plt.loglog(w, inv_norm_gd[i], label=('$1/||g_{d%s}||_2$' % (i+1)))
            s_min = numpy.array([utils.sigmas(S(s[p]), 'min') for p in range(points)])
            s_max = numpy.array([utils.sigmas(S(s[p]), 'max') for p in range(points)])
            plt.loglog(w, s_min, label='$\sigma_{min}$')
            plt.loglog(w, s_max, label='$\sigma_{max}$')
        plt.axis(axlim)
        plt.xlabel('Frequency [rad/unit time]')
        plt.legend()
예제 #28
0
def input_acceptable_const_plot(G,
                                Gd,
                                w_start=-2,
                                w_end=2,
                                axlim=None,
                                points=1000,
                                modified=False):
    """
    Subbplots for input constraints for accepatable control. Applies equation
    6.55 (p241).

    Parameters
    ----------
    G : numpy matrix
        Plant model.
    Gd : numpy matrix
        Plant disturbance model.
    modified : boolean
        If true, the arguments in the equation are change to :math:`\sigma_1
        (G) + 1 \geq |u_i^H g_d|`. This is to avoid a negative log scale.

    Returns
    -------
    Plot : matplotlib figure

    Note
    ----
    This condition only holds for :math:`|u_i^H g_d|>1`.
    """

    s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    freqresp = [G(si) for si in s]
    sig = numpy.array([utils.sigmas(Gfr) for Gfr in freqresp])
    one = numpy.ones(points)

    plot_No = 1

    dimGd = numpy.shape(Gd(0))[1]
    dimG = numpy.shape(G(0))[0]
    acceptable_control = numpy.zeros((dimGd, dimG, points))
    for j in range(dimGd):
        for i in range(dimG):
            for k in range(points):
                U, _, _ = utils.SVD(G(s[k]))
                acceptable_control[j, i,
                                   k] = numpy.abs(U[:, i].H * Gd(s[k])[:, j])
            plt.subplot(dimG, dimGd, plot_No)
            if not modified:
                plt.loglog(w, sig[:, i], label=('$\sigma_%s$' % (i + 1)))
                plt.plot(w,
                         acceptable_control[j, i] - one,
                         label=('$|u_%s^H.g_{d%s}|-1$' % (i + 1, j + 1)))
            else:
                plt.loglog(w,
                           sig[:, i] + one,
                           label=('$\sigma_%s+1$' % (i + 1)))
                plt.plot(w,
                         acceptable_control[j, i],
                         label=('$|u_%s^H.g_{d%s}|$' % (i + 1, j + 1)))
                plt.loglog([w[0], w[-1]], [1, 1],
                           'r',
                           ls=':',
                           label='Applicable')
            plt.xlabel('Frequency [rad/unit time]')
            plt.grid(True)
            plt.axis(axlim)
            plt.legend()
            plot_No += 1
예제 #29
0
def freq_step_response_plot(G, K, Kc, t_end=50, freqtype='S', w_start=-2, w_end=2, axlim=None, points=1000):
    """
    A subplot function for both the frequency response and step response for a
    controlled plant

    Parameters
    ----------
    G : tf
        Plant transfer function.
    K : tf
        Controller transfer function.
    Kc : integer
        Controller constant.
    t_end : integer
        Time period which the step response should occur.
    freqtype : string (optional)
        Type of function to plot:

        ========    ==================================
        freqtype    Type of function to plot
        ========    ==================================
        S           Sensitivity function
        T           Complementary sensitivity function
        L           Loop function
        ========    ==================================

    Returns
    -------
    Plot : matplotlib figure

    """

    _, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points)

    plt.subplot(1, 2, 1)

    # Controllers transfer function with various controller gains
    Ks = [(kc * K) for kc in Kc]
    Ts = [utils.feedback(G * Kss, 1) for Kss in Ks]

    if freqtype == 'S':
        Fs = [(1 - Tss) for Tss in Ts]
        plt.title('(a) Sensitivity function')
        plt.ylabel('Magnitude $|S|$')
    elif freqtype == 'T':
        Fs = Ts
        plt.title('(a) Complementary sensitivity function')
        plt.ylabel('Magnitude $|T|$')
    else:  # freqtype=='L'
        Fs = [(G*Kss) for Kss in Ks]
        plt.title('(a) Loop function')
        plt.ylabel('Magnitude $|L|$')

    wi = w * 1j
    i = 0
    for F in Fs:
        plt.loglog(w, abs(F(wi)), label='Kc={d%s}=' % Kc[i])
        i += 1
    plt.axis(axlim)
    plt.grid(b=None, which='both', axis='both')
    plt.xlabel('Frequency [rad/unit time]')
    plt.legend(["Kc = %1.2f" % k for k in Kc],loc=4)

    plt.subplot(1, 2, 2)
    plt.title('(b) Response to step in reference')
    tspan = numpy.linspace(0, t_end, points)
    for T in Ts:
        [t, y] = T.step(0, tspan)
        plt.plot(t, y)
    plt.plot(tspan, 0 * numpy.ones(points), ls='--')
    plt.plot(tspan, 1 * numpy.ones(points), ls='--')
    plt.axis(axlim)
    plt.xlabel('Time')
    plt.ylabel('$y(t)$')