Exemplo n.º 1
0
    def test_isdtime(self, objfun, arg, dt, ref, strictref):
        """Test isdtime and isctime functions to follow convention"""
        obj = objfun(*arg, dt=dt)

        assert isdtime(obj) == ref
        assert isdtime(obj, strict=True) == strictref

        if dt is not None:
            ref = not ref
            strictref = not strictref
        assert isctime(obj) == ref
        assert isctime(obj, strict=True) == strictref
Exemplo n.º 2
0
 def test_class_constants_s(self):
     """Make sure that the 's' variable is defined properly"""
     s = TransferFunction.s
     G = (s + 1)/(s**2 + 2*s + 1)
     np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
     np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
     assert isctime(G, strict=True)
Exemplo n.º 3
0
    def test_class_constants(self):
        # Make sure that the 's' variable is defined properly
        s = TransferFunction.s
        G = (s + 1) / (s**2 + 2 * s + 1)
        np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
        np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
        self.assertTrue(isctime(G, strict=True))

        # Make sure that the 'z' variable is defined properly
        z = TransferFunction.z
        G = (z + 1) / (z**2 + 2 * z + 1)
        np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
        np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
        self.assertTrue(isdtime(G, strict=True))
Exemplo n.º 4
0
    def test_class_constants(self):
        # Make sure that the 's' variable is defined properly
        s = TransferFunction.s
        G = (s + 1)/(s**2 + 2*s + 1)
        np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
        np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
        self.assertTrue(isctime(G, strict=True))

        # Make sure that the 'z' variable is defined properly
        z = TransferFunction.z
        G = (z + 1)/(z**2 + 2*z + 1)
        np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
        np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
        self.assertTrue(isdtime(G, strict=True))
Exemplo n.º 5
0
def sample_system(sysc, Ts, method='matched'):
    # TODO: add docstring

    # Make sure we have a continuous time system
    if not isctime(sysc):
        raise ValueError("First argument must be continuous time system")

    # TODO: impelement MIMO version
    if (sysc.inputs != 1 or sysc.outputs != 1):
        raise NotImplementedError("MIMO implementation not available")

    # If we are passed a state space system, convert to transfer function first
    if isinstance(sysc, StateSpace):
        warn("sample_system: converting to transfer function")
        sysc = _convertToTransferFunction(sysc)

    # Decide what to do based on the methods available
    if method == 'matched':
        sysd = _c2dmatched(sysc, Ts)

    elif method == 'tustin':
        sys = [sysc.num[0][0], sysc.den[0][0]]
        scipySysD = cont2discrete(sys, Ts, method='bilinear')
        sysd = TransferFunction(scipySysD[0][0], scipySysD[1], dt)

    elif method == 'zoh':
        sys = [sysc.num[0][0], sysc.den[0][0]]
        scipySysD = cont2discrete(sys, Ts, method='zoh')
        sysd = TransferFunction(scipySysD[0][0],scipySysD[1], dt)

    elif method == 'foh' or method == 'impulse':
        raise ValueError("Method not developed yet")

    else:
        raise ValueError("Invalid discretization method: %s" % method)

    # TODO: Convert back into the input form
    # Set sampling time
    return sysd
Exemplo n.º 6
0
def sample_system(sysc, Ts, method='matched'):
    # TODO: add docstring

    # Make sure we have a continuous time system
    if not isctime(sysc):
        raise ValueError("First argument must be continuous time system")

    # TODO: impelement MIMO version
    if (sysc.inputs != 1 or sysc.outputs != 1):
        raise NotImplementedError("MIMO implementation not available")

    # If we are passed a state space system, convert to transfer function first
    if isinstance(sysc, StateSpace):
        warn("sample_system: converting to transfer function")
        sysc = _convertToTransferFunction(sysc)

    # Decide what to do based on the methods available
    if method == 'matched':
        sysd = _c2dmatched(sysc, Ts)

    elif method == 'tustin':
        sys = [sysc.num[0][0], sysc.den[0][0]]
        scipySysD = cont2discrete(sys, Ts, method='bilinear')
        sysd = TransferFunction(scipySysD[0][0], scipySysD[1], Ts)

    elif method == 'zoh':
        sys = [sysc.num[0][0], sysc.den[0][0]]
        scipySysD = cont2discrete(sys, Ts, method='zoh')
        sysd = TransferFunction(scipySysD[0][0], scipySysD[1], Ts)

    elif method == 'foh' or method == 'impulse':
        raise ValueError("Method not developed yet")

    else:
        raise ValueError("Invalid discretization method: %s" % method)

    # TODO: Convert back into the input form
    # Set sampling time
    return sysd
Exemplo n.º 7
0
def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
                           Kp0=0, Ki0=0, Kd0=0, tau=0.01,
                           C_ff=0, derivative_in_feedback_path=False,
                           plot=True):
    """Manual PID controller design based on root locus using Sisotool

    Uses `Sisotool` to investigate the effect of adding or subtracting an
    amount `deltaK` to the proportional, integral, or derivative (PID) gains of
    a controller. One of the PID gains, `Kp`, `Ki`, or `Kd`, respectively, can
    be modified at a time. `Sisotool` plots the step response, frequency
    response, and root locus.

    When first run, `deltaK` is set to 0; click on a branch of the root locus
    plot to try a different value. Each click updates plots and prints
    the corresponding `deltaK`. To tune all three PID gains, repeatedly call
    `rootlocus_pid_designer`, and select a different `gain` each time (`'P'`,
    `'I'`, or `'D'`). Make sure to add the resulting `deltaK` to your chosen
    initial gain on the next iteration.

    Example: to examine the effect of varying `Kp` starting from an intial
    value of 10, use the arguments `gain='P', Kp0=10`. Suppose a `deltaK`
    value of 5 gives satisfactory performance. Then on the next iteration,
    to tune the derivative gain, use the arguments `gain='D', Kp0=15`.

    By default, all three PID terms are in the forward path C_f in the diagram
    shown below, that is,

    C_f = Kp + Ki/s + Kd*s/(tau*s + 1).

    If `plant` is a discrete-time system, then the proportional, integral, and
    derivative terms are given instead by Kp, Ki*dt/2*(z+1)/(z-1), and
    Kd/dt*(z-1)/z, respectively.

        ------> C_ff ------    d
        |                 |    |
    r   |     e           V    V  u         y
    ------->O---> C_f --->O--->O---> plant --->
            ^-            ^-                |
            |             |                 |
            |             ----- C_b <-------|
            ---------------------------------

    It is also possible to move the derivative term into the feedback path
    `C_b` using `derivative_in_feedback_path=True`. This may be desired to
    avoid that the plant is subject to an impulse function when the reference
    `r` is a step input. `C_b` is otherwise set to zero.

    If `plant` is a 2-input system, the disturbance `d` is fed directly into
    its second input rather than being added to `u`.

    Remark: It may be helpful to zoom in using the magnifying glass on the
    plot. Just ake sure to deactivate magnification mode when you are done by
    clicking the magnifying glass. Otherwise you will not be able to be able to choose
    a gain on the root locus plot.

    Parameters
    ----------
    plant : :class:`LTI` (:class:`TransferFunction` or :class:`StateSpace` system)
        The dynamical system to be controlled
    gain : string (optional)
        Which gain to vary by `deltaK`. Must be one of `'P'`, `'I'`, or `'D'`
        (proportional, integral, or derative)
    sign : int (optional)
        The sign of deltaK gain perturbation
    input : string (optional)
        The input used for the step response; must be `'r'` (reference) or
        `'d'` (disturbance) (see figure above)
    Kp0, Ki0, Kd0 : float (optional)
        Initial values for proportional, integral, and derivative gains,
        respectively
    tau : float (optional)
        The time constant associated with the pole in the continuous-time
        derivative term. This is required to make the derivative transfer
        function proper.
    C_ff : float or :class:`LTI` system (optional)
        Feedforward controller. If :class:`LTI`, must have timebase that is
        compatible with plant.
    derivative_in_feedback_path : bool (optional)
        Whether to place the derivative term in feedback transfer function
        `C_b` instead of the forward transfer function `C_f`.
    plot : bool (optional)
        Whether to create Sisotool interactive plot.

    Returns
    ----------
    closedloop : class:`StateSpace` system
        The closed-loop system using initial gains.
    """

    plant = _convert_to_statespace(plant)
    if plant.ninputs == 1:
        plant = ss2io(plant, inputs='u', outputs='y')
    elif plant.ninputs == 2:
        plant = ss2io(plant, inputs=['u', 'd'], outputs='y')
    else:
        raise ValueError("plant must have one or two inputs")
    C_ff = ss2io(_convert_to_statespace(C_ff),   inputs='r', outputs='uff')
    dt = common_timebase(plant, C_ff)

    # create systems used for interconnections
    e_summer = summing_junction(['r', '-y'], 'e')
    if plant.ninputs == 2:
        u_summer = summing_junction(['ufb', 'uff'], 'u')
    else:
        u_summer = summing_junction(['ufb', 'uff', 'd'], 'u')

    if isctime(plant):
        prop  = tf(1, 1)
        integ = tf(1, [1, 0])
        deriv = tf([1, 0], [tau, 1])
    else: # discrete-time
        prop  = tf(1, 1, dt)
        integ = tf([dt/2, dt/2], [1, -1], dt)
        deriv = tf([1, -1], [dt, 0], dt)

    # add signal names by turning into iosystems
    prop  = tf2io(prop,        inputs='e', outputs='prop_e')
    integ = tf2io(integ,       inputs='e', outputs='int_e')
    if derivative_in_feedback_path:
        deriv = tf2io(-deriv,  inputs='y', outputs='deriv')
    else:
        deriv = tf2io(deriv,   inputs='e', outputs='deriv')

    # create gain blocks
    Kpgain = tf2io(tf(Kp0, 1),            inputs='prop_e',  outputs='ufb')
    Kigain = tf2io(tf(Ki0, 1),            inputs='int_e',   outputs='ufb')
    Kdgain = tf2io(tf(Kd0, 1),            inputs='deriv',  outputs='ufb')

    # for the gain that is varied, replace gain block with a special block
    # that has an 'input' and an 'output' that creates loop transfer function
    if gain in ('P', 'p'):
        Kpgain = ss2io(ss([],[],[],[[0, 1], [-sign, Kp0]]),
            inputs=['input', 'prop_e'], outputs=['output', 'ufb'])
    elif gain in ('I', 'i'):
        Kigain = ss2io(ss([],[],[],[[0, 1], [-sign, Ki0]]),
            inputs=['input', 'int_e'],  outputs=['output', 'ufb'])
    elif gain in ('D', 'd'):
        Kdgain = ss2io(ss([],[],[],[[0, 1], [-sign, Kd0]]),
            inputs=['input', 'deriv'], outputs=['output', 'ufb'])
    else:
        raise ValueError(gain + ' gain not recognized.')

    # the second input and output are used by sisotool to plot step response
    loop = interconnect((plant, Kpgain, Kigain, Kdgain, prop, integ, deriv,
                            C_ff, e_summer, u_summer),
                            inplist=['input', input_signal],
                            outlist=['output', 'y'], check_unused=False)
    if plot:
        sisotool(loop, kvect=(0.,))
    cl = loop[1, 1] # closed loop transfer function with initial gains
    return StateSpace(cl.A, cl.B, cl.C, cl.D, cl.dt)
Exemplo n.º 8
0
def impulse_response(sys, T=None, X0=0., input=0, output=0,
                    transpose=False, **keywords):
    #pylint: disable=W0622
    """Impulse response of a linear system
    
    If the system has multiple inputs or outputs (MIMO), one input and one 
    output have to be selected for the simulation. The parameters `input` 
    and `output` do this. All other inputs are set to 0, all other outputs 
    are ignored.
    
    For information on the **shape** of parameters `T`, `X0` and 
    return values `T`, `yout` see: :ref:`time-series-convention`

    Parameters
    ----------
    sys: StateSpace, TransferFunction
        LTI system to simulate

    T: array-like object, optional
        Time vector (argument is autocomputed if not given)

    X0: array-like object or number, optional
        Initial condition (default = 0)

        Numbers are converted to constant arrays with the correct shape.

    input: int
        Index of the input that will be used in this simulation.

    output: int
        Index of the output that will be used in this simulation.

    transpose: bool
        If True, transpose all input and output arrays (for backward
        compatibility with MATLAB and scipy.signal.lsim)

    **keywords:
        Additional keyword arguments control the solution algorithm for the 
        differential equations. These arguments are passed on to the function
        :func:`lsim`, which in turn passes them on to
        :func:`scipy.integrate.odeint`. See the documentation for
        :func:`scipy.integrate.odeint` for information about these
        arguments.


    Returns
    -------
    T: array
        Time values of the output
    yout: array
        Response of the system
    
    See Also
    --------
    ForcedReponse, initial_response, step_response

    Examples
    --------
    >>> T, yout = impulse_response(sys, T, X0) 
    """
    sys = _convertToStateSpace(sys) 
    sys = _mimo2siso(sys, input, output, warn_conversion=True)
    
    # System has direct feedthrough, can't simulate impulse response numerically
    if np.any(sys.D != 0) and isctime(sys):
        warnings.warn('System has direct feedthrough: ``D != 0``. The infinite '
                      'impulse at ``t=0`` does not appear in the output. \n'
                      'Results may be meaningless!')
    
    # create X0 if not given, test if X0 has correct shape.
    # Must be done here because it is used for computations here.
    n_states = sys.A.shape[0]
    X0 = _check_convert_array(X0, [(n_states,), (n_states,1)],
                              'Parameter ``X0``: \n', squeeze=True)

    # Compute new X0 that contains the impulse
    # We can't put the impulse into U because there is no numerical
    # representation for it (infinitesimally short, infinitely high).
    # See also: http://www.mathworks.com/support/tech-notes/1900/1901.html
    B = np.asarray(sys.B).squeeze()
    new_X0 = B + X0

    # Compute T and U, no checks necessary, they will be checked in lsim
    if T is None:
        T = _default_response_times(sys.A, 100)
    U = np.zeros_like(T)

    T, yout, _xout  = forced_response(sys, T, U, new_X0, \
                          transpose=transpose, **keywords)
    return T, yout
Exemplo n.º 9
0
def step_response(sys, T=None, X0=0., input=0, output=0, \
                     transpose = False, **keywords):
    #pylint: disable=W0622
    """Step response of a linear system
    
    If the system has multiple inputs or outputs (MIMO), one input and one 
    output have to be selected for the simulation. The parameters `input` 
    and `output` do this. All other inputs are set to 0, all other outputs 
    are ignored.
    
    For information on the **shape** of parameters `T`, `X0` and 
    return values `T`, `yout` see: :ref:`time-series-convention`

    Parameters
    ----------
    sys: StateSpace, or TransferFunction
        LTI system to simulate

    T: array-like object, optional
        Time vector (argument is autocomputed if not given)

    X0: array-like or number, optional
        Initial condition (default = 0)

        Numbers are converted to constant arrays with the correct shape.

    input: int
        Index of the input that will be used in this simulation.

    output: int
        Index of the output that will be used in this simulation.

    transpose: bool
        If True, transpose all input and output arrays (for backward
        compatibility with MATLAB and scipy.signal.lsim)
        
    **keywords:
        Additional keyword arguments control the solution algorithm for the 
        differential equations. These arguments are passed on to the function
        :func:`lsim`, which in turn passes them on to
        :func:`scipy.integrate.odeint`. See the documentation for
        :func:`scipy.integrate.odeint` for information about these
        arguments.

    Returns
    -------
    T: array
        Time values of the output

    yout: array
        Response of the system
    
    See Also
    --------
    forced_response, initial_response, impulse_response

    Examples
    --------
    >>> T, yout = step_response(sys, T, X0)
    """
    sys = _convertToStateSpace(sys)
    sys = _mimo2siso(sys, input, output, warn_conversion=True)
    if T is None:
        if isctime(sys):
            T = _default_response_times(sys.A, 100)
        else:
            # For discrete time, use integers
            tvec = _default_response_times(sys.A, 100)
            T = range(int(np.ceil(max(tvec))))

    U = np.ones_like(T)

    T, yout, _xout = forced_response(sys, T, U, X0, 
                                    transpose=transpose, **keywords)

    return T, yout
Exemplo n.º 10
0
def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords):
    """Simulate the output of a linear system.
    
    As a convenience for parameters `U`, `X0`:
    Numbers (scalars) are converted to constant arrays with the correct shape.
    The correct shape is inferred from arguments `sys` and `T`. 
    
    For information on the **shape** of parameters `U`, `T`, `X0` and 
    return values `T`, `yout`, `xout` see: :ref:`time-series-convention`
    
    Parameters
    ----------
    sys: Lti (StateSpace, or TransferFunction)
        LTI system to simulate
        
    T: array-like 
        Time steps at which the input is defined, numbers must be (strictly 
        monotonic) increasing. 
        
    U: array-like or number, optional
        Input array giving input at each time `T` (default = 0).
        
        If `U` is ``None`` or ``0``, a special algorithm is used. This special 
        algorithm is faster than the general algorithm, which is used otherwise.
        
    X0: array-like or number, optional
        Initial condition (default = 0). 

    transpose: bool
        If True, transpose all input and output arrays (for backward
        compatibility with MATLAB and scipy.signal.lsim)
        
    **keywords:
        Additional keyword arguments control the solution algorithm for the 
        differential equations. These arguments are passed on to the function
        :func:`scipy.integrate.odeint`. See the documentation for
        :func:`scipy.integrate.odeint` for information about these
        arguments.

    Returns
    -------
    T: array
        Time values of the output. 
    yout: array
        Response of the system. 
    xout: array
        Time evolution of the state vector. 
    
    See Also
    --------
    step_response, initial_response, impulse_response
    
    Examples
    --------
    >>> T, yout, xout = forced_response(sys, T, u, X0)
    """
    if not isinstance(sys, Lti):
        raise TypeError('Parameter ``sys``: must be a ``Lti`` object. '
                        '(For example ``StateSpace`` or ``TransferFunction``)')
    sys = _convertToStateSpace(sys) 
    A, B, C, D = np.asarray(sys.A), np.asarray(sys.B), np.asarray(sys.C), \
                 np.asarray(sys.D)
#    d_type = A.dtype
    n_states = A.shape[0]
    n_inputs = B.shape[1]

    # Set and/or check time vector in discrete time case
    if isdtime(sys, strict=True):
        if T == None:
            if U == None:
                raise ValueError('Parameters ``T`` and ``U`` can\'t both be'
                                 'zero for discrete-time simulation')
            # Set T to integers with same length as U
            T = range(len(U))
        else:
            # Make sure the input vector and time vector have same length
            # TODO: allow interpolation of the input vector
            if len(U) != len(T):
                ValueError('Pamameter ``T`` must have same length as'
                           'input vector ``U``')

    # Test if T has shape (n,) or (1, n);
    # T must be array-like and values must be increasing.
    # The length of T determines the length of the input vector.
    if T is None:
        raise ValueError('Parameter ``T``: must be array-like, and contain '
                         '(strictly monotonic) increasing numbers.')
    T = _check_convert_array(T, [('any',), (1,'any')], 
                             'Parameter ``T``: ', squeeze=True, 
                             transpose = transpose)
    if not all(T[1:] - T[:-1] > 0):
        raise ValueError('Parameter ``T``: time values must be '
                         '(strictly monotonic) increasing numbers.')
    n_steps = len(T)            # number of simulation steps
    
    #create X0 if not given, test if X0 has correct shape
    X0 = _check_convert_array(X0, [(n_states,), (n_states,1)], 
                              'Parameter ``X0``: ', squeeze=True)

    # Separate out the discrete and continuous time cases
    if isctime(sys):
        # Solve the differential equation, copied from scipy.signal.ltisys.
        dot, squeeze, = np.dot, np.squeeze #Faster and shorter code

        # Faster algorithm if U is zero
        if U is None or (isinstance(U, (int, float)) and U == 0):
            # Function that computes the time derivative of the linear system
            def f_dot(x, _t):
                return dot(A,x)
        
            xout = sp.integrate.odeint(f_dot, X0, T, **keywords)
            yout = dot(C, xout.T)

        # General algorithm that interpolates U in between output points
        else:
            # Test if U has correct shape and type
            legal_shapes = [(n_steps,), (1,n_steps)] if n_inputs == 1 else \
                           [(n_inputs, n_steps)]
            U = _check_convert_array(U, legal_shapes,
                                     'Parameter ``U``: ', squeeze=False,
                                     transpose=transpose)
            # convert 1D array to D2 array with only one row
            if len(U.shape) == 1:
                U = U.reshape(1,-1)                      #pylint: disable=E1103

            # Create a callable that uses linear interpolation to
            # calculate the input at any time.
            compute_u = \
                sp.interpolate.interp1d(T, U, kind='linear', copy=False,
                                        axis=-1, bounds_error=False,
                                        fill_value=0)
        
            # Function that computes the time derivative of the linear system
            def f_dot(x, t):
                return dot(A,x) + squeeze(dot(B,compute_u([t])))
        
            xout = sp.integrate.odeint(f_dot, X0, T, **keywords)
            yout = dot(C, xout.T) + dot(D, U)

        yout = squeeze(yout)
        xout = xout.T

    else:
        # Discrete time simulation using signal processing toolbox
        dsys = (A, B, C, D, sys.dt)
        tout, yout, xout = sp.signal.dlsim(dsys, U, T, X0)

    # See if we need to transpose the data back into MATLAB form
    if (transpose):
        T = np.transpose(T)
        yout = np.transpose(yout)
        xout = np.transpose(xout)

    return T, yout, xout
Exemplo n.º 11
0
def step_response(sys,
                  T=None,
                  X0=0.,
                  input=0,
                  output=None,
                  transpose=False,
                  **keywords):
    #pylint: disable=W0622
    """Step response of a linear system
    
    If the system has multiple inputs or outputs (MIMO), one input has
    to be selected for the simulation. Optionally, one output may be
    selected. The parameters `input` and `output` do this. All other
    inputs are set to 0, all other outputs are ignored.
    
    For information on the **shape** of parameters `T`, `X0` and 
    return values `T`, `yout` see: :ref:`time-series-convention`

    Parameters
    ----------
    sys: StateSpace, or TransferFunction
        LTI system to simulate

    T: array-like object, optional
        Time vector (argument is autocomputed if not given)

    X0: array-like or number, optional
        Initial condition (default = 0)

        Numbers are converted to constant arrays with the correct shape.

    input: int
        Index of the input that will be used in this simulation.

    output: int
        Index of the output that will be used in this simulation. Set to None
        to not trim outputs

    transpose: bool
        If True, transpose all input and output arrays (for backward
        compatibility with MATLAB and scipy.signal.lsim)
        
    **keywords:
        Additional keyword arguments control the solution algorithm for the 
        differential equations. These arguments are passed on to the function
        :func:`lsim`, which in turn passes them on to
        :func:`scipy.integrate.odeint`. See the documentation for
        :func:`scipy.integrate.odeint` for information about these
        arguments.

    Returns
    -------
    T: array
        Time values of the output

    yout: array
        Response of the system
    
    See Also
    --------
    forced_response, initial_response, impulse_response

    Examples
    --------
    >>> T, yout = step_response(sys, T, X0)
    """
    sys = _convertToStateSpace(sys)
    if output == None:
        sys = _mimo2simo(sys, input, warn_conversion=True)
    else:
        sys = _mimo2siso(sys, input, output, warn_conversion=True)
    if T is None:
        if isctime(sys):
            T = _default_response_times(sys.A, 100)
        else:
            # For discrete time, use integers
            tvec = _default_response_times(sys.A, 100)
            T = range(int(np.ceil(max(tvec))))

    U = np.ones_like(T)

    T, yout, _xout = forced_response(sys,
                                     T,
                                     U,
                                     X0,
                                     transpose=transpose,
                                     **keywords)

    return T, yout
Exemplo n.º 12
0
    def testisctime(self):
        # Constant
        self.assertEqual(isctime(1), True);
        self.assertEqual(isctime(1, strict=True), False);

        # State Space
        self.assertEqual(isctime(self.siso_ss1), True);
        self.assertEqual(isctime(self.siso_ss1, strict=True), False);
        self.assertEqual(isctime(self.siso_ss1c), True);
        self.assertEqual(isctime(self.siso_ss1c, strict=True), True);
        self.assertEqual(isctime(self.siso_ss1d), False);
        self.assertEqual(isctime(self.siso_ss1d, strict=True), False);
        self.assertEqual(isctime(self.siso_ss3d, strict=True), False);

        # Transfer Function
        self.assertEqual(isctime(self.siso_tf1), True);
        self.assertEqual(isctime(self.siso_tf1, strict=True), False);
        self.assertEqual(isctime(self.siso_tf1c), True);
        self.assertEqual(isctime(self.siso_tf1c, strict=True), True);
        self.assertEqual(isctime(self.siso_tf1d), False);
        self.assertEqual(isctime(self.siso_tf1d, strict=True), False);
        self.assertEqual(isctime(self.siso_tf3d, strict=True), False);
Exemplo n.º 13
0
def modred(sys, ELIM, method='matchdc'):
    """
    Model reduction of `sys` by eliminating the states in `ELIM` using a given 
    method.

    Parameters
    ----------
    sys: StateSpace
        Original system to reduce
    ELIM: array
        Vector of states to eliminate
    method: string
        Method of removing states in `ELIM`: either ``'truncate'`` or 
        ``'matchdc'``.

    Returns
    -------
    rsys: StateSpace
        A reduced order model 

    Raises
    ------
    ValueError
        * if `method` is not either ``'matchdc'`` or ``'truncate'``
        * if eigenvalues of `sys.A` are not all in left half plane 
          (`sys` must be stable) 

    Examples
    --------
    >>> rsys = modred(sys, ELIM, method='truncate')
    """

    #Check for ss system object, need a utility for this?

    #TODO: Check for continous or discrete, only continuous supported right now
    # if isCont():
    #    dico = 'C'
    # elif isDisc():
    #    dico = 'D'
    # else:
    if (isctime(sys)):
        dico = 'C'
    else:
        raise NotImplementedError("Function not implemented in discrete time")

    #Check system is stable
    D, V = np.linalg.eig(sys.A)
    for e in D:
        if e.real >= 0:
            raise ValueError("Oops, the system is unstable!")
    ELIM = np.sort(ELIM)
    NELIM = []
    # Create list of elements not to eliminate (NELIM)
    for i in range(0, len(sys.A)):
        if i not in ELIM:
            NELIM.append(i)
    # A1 is a matrix of all columns of sys.A not to eliminate
    A1 = sys.A[:, NELIM[0]]
    for i in NELIM[1:]:
        A1 = np.hstack((A1, sys.A[:, i]))
    A11 = A1[NELIM, :]
    A21 = A1[ELIM, :]
    # A2 is a matrix of all columns of sys.A to eliminate
    A2 = sys.A[:, ELIM[0]]
    for i in ELIM[1:]:
        A2 = np.hstack((A2, sys.A[:, i]))
    A12 = A2[NELIM, :]
    A22 = A2[ELIM, :]

    C1 = sys.C[:, NELIM]
    C2 = sys.C[:, ELIM]
    B1 = sys.B[NELIM, :]
    B2 = sys.B[ELIM, :]

    A22I = np.linalg.inv(A22)

    if method == 'matchdc':
        # if matchdc, residualize
        Ar = A11 - A12 * A22.I * A21
        Br = B1 - A12 * A22.I * B2
        Cr = C1 - C2 * A22.I * A21
        Dr = sys.D - C2 * A22.I * B2
    elif method == 'truncate':
        # if truncate, simply discard state x2
        Ar = A11
        Br = B1
        Cr = C1
        Dr = sys.D
    else:
        raise ValueError("Oops, method is not supported!")

    rsys = StateSpace(Ar, Br, Cr, Dr)
    return rsys
Exemplo n.º 14
0
    def testisctime(self):
        # Constant
        self.assertEqual(isctime(1), True)
        self.assertEqual(isctime(1, strict=True), False)

        # State Space
        self.assertEqual(isctime(self.siso_ss1), True)
        self.assertEqual(isctime(self.siso_ss1, strict=True), False)
        self.assertEqual(isctime(self.siso_ss1c), True)
        self.assertEqual(isctime(self.siso_ss1c, strict=True), True)
        self.assertEqual(isctime(self.siso_ss1d), False)
        self.assertEqual(isctime(self.siso_ss1d, strict=True), False)
        self.assertEqual(isctime(self.siso_ss3d, strict=True), False)

        # Transfer Function
        self.assertEqual(isctime(self.siso_tf1), True)
        self.assertEqual(isctime(self.siso_tf1, strict=True), False)
        self.assertEqual(isctime(self.siso_tf1c), True)
        self.assertEqual(isctime(self.siso_tf1c, strict=True), True)
        self.assertEqual(isctime(self.siso_tf1d), False)
        self.assertEqual(isctime(self.siso_tf1d, strict=True), False)
        self.assertEqual(isctime(self.siso_tf3d, strict=True), False)
Exemplo n.º 15
0
def impulse_response(sys,
                     T=None,
                     X0=0.,
                     input=0,
                     output=None,
                     transpose=False,
                     **keywords):
    #pylint: disable=W0622
    """Impulse response of a linear system
    
    If the system has multiple inputs or outputs (MIMO), one input and one 
    output have to be selected for the simulation. The parameters `input` 
    and `output` do this. All other inputs are set to 0, all other outputs 
    are ignored.
    
    For information on the **shape** of parameters `T`, `X0` and 
    return values `T`, `yout` see: :ref:`time-series-convention`

    Parameters
    ----------
    sys: StateSpace, TransferFunction
        LTI system to simulate

    T: array-like object, optional
        Time vector (argument is autocomputed if not given)

    X0: array-like object or number, optional
        Initial condition (default = 0)

        Numbers are converted to constant arrays with the correct shape.

    input: int
        Index of the input that will be used in this simulation.

    output: int
        Index of the output that will be used in this simulation. Set to None
        to not trim outputs

    transpose: bool
        If True, transpose all input and output arrays (for backward
        compatibility with MATLAB and scipy.signal.lsim)

    **keywords:
        Additional keyword arguments control the solution algorithm for the 
        differential equations. These arguments are passed on to the function
        :func:`lsim`, which in turn passes them on to
        :func:`scipy.integrate.odeint`. See the documentation for
        :func:`scipy.integrate.odeint` for information about these
        arguments.


    Returns
    -------
    T: array
        Time values of the output
    yout: array
        Response of the system
    
    See Also
    --------
    ForcedReponse, initial_response, step_response

    Examples
    --------
    >>> T, yout = impulse_response(sys, T, X0) 
    """
    sys = _convertToStateSpace(sys)
    if output == None:
        sys = _mimo2simo(sys, input, warn_conversion=True)
    else:
        sys = _mimo2siso(sys, input, output, warn_conversion=True)

    # System has direct feedthrough, can't simulate impulse response numerically
    if np.any(sys.D != 0) and isctime(sys):
        warnings.warn(
            'System has direct feedthrough: ``D != 0``. The infinite '
            'impulse at ``t=0`` does not appear in the output. \n'
            'Results may be meaningless!')

    # create X0 if not given, test if X0 has correct shape.
    # Must be done here because it is used for computations here.
    n_states = sys.A.shape[0]
    X0 = _check_convert_array(X0, [(n_states, ), (n_states, 1)],
                              'Parameter ``X0``: \n',
                              squeeze=True)

    # Compute new X0 that contains the impulse
    # We can't put the impulse into U because there is no numerical
    # representation for it (infinitesimally short, infinitely high).
    # See also: http://www.mathworks.com/support/tech-notes/1900/1901.html
    B = np.asarray(sys.B).squeeze()
    new_X0 = B + X0

    # Compute T and U, no checks necessary, they will be checked in lsim
    if T is None:
        T = _default_response_times(sys.A, 100)
    U = np.zeros_like(T)

    T, yout, _xout  = forced_response(sys, T, U, new_X0, \
                          transpose=transpose, **keywords)
    return T, yout
Exemplo n.º 16
0
def modred(sys, ELIM, method='matchdc'):
    """
    Model reduction of `sys` by eliminating the states in `ELIM` using a given 
    method.

    Parameters
    ----------
    sys: StateSpace
        Original system to reduce
    ELIM: array
        Vector of states to eliminate
    method: string
        Method of removing states in `ELIM`: either ``'truncate'`` or 
        ``'matchdc'``.

    Returns
    -------
    rsys: StateSpace
        A reduced order model 

    Raises
    ------
    ValueError
        * if `method` is not either ``'matchdc'`` or ``'truncate'``
        * if eigenvalues of `sys.A` are not all in left half plane 
          (`sys` must be stable) 

    Examples
    --------
    >>> rsys = modred(sys, ELIM, method='truncate')
    """

    #Check for ss system object, need a utility for this?

    #TODO: Check for continous or discrete, only continuous supported right now
        # if isCont():
        #    dico = 'C'
        # elif isDisc():
        #    dico = 'D'
        # else:
    if (isctime(sys)):
        dico = 'C'
    else:
        raise NotImplementedError("Function not implemented in discrete time")


    #Check system is stable
    D,V = np.linalg.eig(sys.A)
    for e in D:
        if e.real >= 0:
            raise ValueError("Oops, the system is unstable!")
    ELIM = np.sort(ELIM)
    NELIM = []
    # Create list of elements not to eliminate (NELIM)
    for i in range(0,len(sys.A)):
        if i not in ELIM:
            NELIM.append(i)
    # A1 is a matrix of all columns of sys.A not to eliminate
    A1 = sys.A[:,NELIM[0]]
    for i in NELIM[1:]:
        A1 = np.hstack((A1, sys.A[:,i]))
    A11 = A1[NELIM,:]  
    A21 = A1[ELIM,:]  
    # A2 is a matrix of all columns of sys.A to eliminate
    A2 = sys.A[:,ELIM[0]]
    for i in ELIM[1:]:
        A2 = np.hstack((A2, sys.A[:,i]))
    A12 = A2[NELIM,:]
    A22 = A2[ELIM,:]

    C1 = sys.C[:,NELIM]
    C2 = sys.C[:,ELIM]
    B1 = sys.B[NELIM,:]
    B2 = sys.B[ELIM,:]

    A22I = np.linalg.inv(A22)

    if method=='matchdc':
        # if matchdc, residualize
        Ar = A11 - A12*A22.I*A21
        Br = B1 - A12*A22.I*B2
        Cr = C1 - C2*A22.I*A21
        Dr = sys.D - C2*A22.I*B2
    elif method=='truncate':
        # if truncate, simply discard state x2
        Ar = A11 
        Br = B1
        Cr = C1
        Dr = sys.D 
    else:
        raise ValueError("Oops, method is not supported!")

    rsys = StateSpace(Ar,Br,Cr,Dr)
    return rsys
Exemplo n.º 17
0
def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords):
    """Simulate the output of a linear system.
    
    As a convenience for parameters `U`, `X0`:
    Numbers (scalars) are converted to constant arrays with the correct shape.
    The correct shape is inferred from arguments `sys` and `T`. 
    
    For information on the **shape** of parameters `U`, `T`, `X0` and 
    return values `T`, `yout`, `xout` see: :ref:`time-series-convention`
    
    Parameters
    ----------
    sys: Lti (StateSpace, or TransferFunction)
        LTI system to simulate
        
    T: array-like 
        Time steps at which the input is defined, numbers must be (strictly 
        monotonic) increasing. 
        
    U: array-like or number, optional
        Input array giving input at each time `T` (default = 0).
        
        If `U` is ``None`` or ``0``, a special algorithm is used. This special 
        algorithm is faster than the general algorithm, which is used otherwise.
        
    X0: array-like or number, optional
        Initial condition (default = 0). 

    transpose: bool
        If True, transpose all input and output arrays (for backward
        compatibility with MATLAB and scipy.signal.lsim)
        
    **keywords:
        Additional keyword arguments control the solution algorithm for the 
        differential equations. These arguments are passed on to the function
        :func:`scipy.integrate.odeint`. See the documentation for
        :func:`scipy.integrate.odeint` for information about these
        arguments.

    Returns
    -------
    T: array
        Time values of the output. 
    yout: array
        Response of the system. 
    xout: array
        Time evolution of the state vector. 
    
    See Also
    --------
    step_response, initial_response, impulse_response
    
    Examples
    --------
    >>> T, yout, xout = forced_response(sys, T, u, X0)
    """
    if not isinstance(sys, Lti):
        raise TypeError('Parameter ``sys``: must be a ``Lti`` object. '
                        '(For example ``StateSpace`` or ``TransferFunction``)')
    sys = _convertToStateSpace(sys)
    A, B, C, D = np.asarray(sys.A), np.asarray(sys.B), np.asarray(sys.C), \
                 np.asarray(sys.D)
    #    d_type = A.dtype
    n_states = A.shape[0]
    n_inputs = B.shape[1]

    # Set and/or check time vector in discrete time case
    if isdtime(sys, strict=True):
        if T == None:
            if U == None:
                raise ValueError('Parameters ``T`` and ``U`` can\'t both be'
                                 'zero for discrete-time simulation')
            # Set T to integers with same length as U
            T = range(len(U))
        else:
            # Make sure the input vector and time vector have same length
            # TODO: allow interpolation of the input vector
            if len(U) != len(T):
                ValueError('Pamameter ``T`` must have same length as'
                           'input vector ``U``')

    # Test if T has shape (n,) or (1, n);
    # T must be array-like and values must be increasing.
    # The length of T determines the length of the input vector.
    if T is None:
        raise ValueError('Parameter ``T``: must be array-like, and contain '
                         '(strictly monotonic) increasing numbers.')
    T = _check_convert_array(T, [('any', ), (1, 'any')],
                             'Parameter ``T``: ',
                             squeeze=True,
                             transpose=transpose)
    if not all(T[1:] - T[:-1] > 0):
        raise ValueError('Parameter ``T``: time values must be '
                         '(strictly monotonic) increasing numbers.')
    n_steps = len(T)  # number of simulation steps

    #create X0 if not given, test if X0 has correct shape
    X0 = _check_convert_array(X0, [(n_states, ), (n_states, 1)],
                              'Parameter ``X0``: ',
                              squeeze=True)

    # Separate out the discrete and continuous time cases
    if isctime(sys):
        # Solve the differential equation, copied from scipy.signal.ltisys.
        dot, squeeze, = np.dot, np.squeeze  #Faster and shorter code

        # Faster algorithm if U is zero
        if U is None or (isinstance(U, (int, float)) and U == 0):
            # Function that computes the time derivative of the linear system
            def f_dot(x, _t):
                return dot(A, x)

            xout = sp.integrate.odeint(f_dot, X0, T, **keywords)
            yout = dot(C, xout.T)

        # General algorithm that interpolates U in between output points
        else:
            # Test if U has correct shape and type
            legal_shapes = [(n_steps,), (1,n_steps)] if n_inputs == 1 else \
                           [(n_inputs, n_steps)]
            U = _check_convert_array(U,
                                     legal_shapes,
                                     'Parameter ``U``: ',
                                     squeeze=False,
                                     transpose=transpose)
            # convert 1D array to D2 array with only one row
            if len(U.shape) == 1:
                U = U.reshape(1, -1)  #pylint: disable=E1103

            # Create a callable that uses linear interpolation to
            # calculate the input at any time.
            compute_u = \
                sp.interpolate.interp1d(T, U, kind='linear', copy=False,
                                        axis=-1, bounds_error=False,
                                        fill_value=0)

            # Function that computes the time derivative of the linear system
            def f_dot(x, t):
                return dot(A, x) + squeeze(dot(B, compute_u([t])))

            xout = sp.integrate.odeint(f_dot, X0, T, **keywords)
            yout = dot(C, xout.T) + dot(D, U)

        yout = squeeze(yout)
        xout = xout.T

    else:
        # Discrete time simulation using signal processing toolbox
        dsys = (A, B, C, D, sys.dt)
        tout, yout, xout = sp.signal.dlsim(dsys, U, T, X0)

    # See if we need to transpose the data back into MATLAB form
    if (transpose):
        T = np.transpose(T)
        yout = np.transpose(yout)
        xout = np.transpose(xout)

    return T, yout, xout