예제 #1
0
    def testConvertToTransferFunction(self):
        """Test for correct state space to transfer function conversion."""

        A = [[1., -2.], [-3., 4.]]
        B = [[6., 5.], [4., 3.]]
        C = [[1., -2.], [3., -4.], [5., -6.]]
        D = [[1., 0.], [0., 1.], [1., 0.]]
        sys = StateSpace(A, B, C, D)

        tfsys = _convertToTransferFunction(sys)

        num = [[np.array([1., -7., 10.]),
                np.array([-1., 10.])],
               [np.array([2., -8.]),
                np.array([1., -2., -8.])],
               [np.array([1., 1., -30.]),
                np.array([7., -22.])]]
        den = [[np.array([1., -5., -2.]) for j in range(sys.inputs)]
               for i in range(sys.outputs)]

        for i in range(sys.outputs):
            for j in range(sys.inputs):
                np.testing.assert_array_almost_equal(tfsys.num[i][j],
                                                     num[i][j])
                np.testing.assert_array_almost_equal(tfsys.den[i][j],
                                                     den[i][j])
예제 #2
0
    def setUp(self):
        """Set up some systems for testing out MATLAB functions"""
        A = np.matrix("1. -2.; 3. -4.")
        B = np.matrix("5.; 7.")
        C = np.matrix("6. 8.")
        D = np.matrix("9.")
        self.siso_ss1 = StateSpace(A, B, C, D)

        # Create some transfer functions
        self.siso_tf1 = TransferFunction([1], [1, 2, 1])
        self.siso_tf2 = _convertToTransferFunction(self.siso_ss1)

        # Create MIMO system, contains ``siso_ss1`` twice
        A = np.matrix("1. -2. 0.  0.;"
                      "3. -4. 0.  0.;"
                      "0.  0. 1. -2.;"
                      "0.  0. 3. -4. ")
        B = np.matrix("5. 0.;"
                      "7. 0.;"
                      "0. 5.;"
                      "0. 7. ")
        C = np.matrix("6. 8. 0. 0.;"
                      "0. 0. 6. 8. ")
        D = np.matrix("9. 0.;"
                      "0. 9. ")
        self.mimo_ss1 = StateSpace(A, B, C, D)
예제 #3
0
def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
    """Calculate gain, phase and stability margins and associated
    crossover frequencies.
    
    Usage
    -----
    gm, pm, sm, wg, wp, ws = stability_margins(sysdata, deg=True)
    
    Parameters
    ----------
    sysdata: linsys or (mag, phase, omega) sequence 
        sys : linsys
            Linear SISO system
        mag, phase, omega : sequence of array_like
            Input magnitude, phase, and frequencies (rad/sec) sequence from 
            bode frequency response data 
    deg=True: boolean  
        If true, all input and output phases in degrees, else in radians
    returnall=False: boolean
        If true, return all margins found. Note that for frequency data or
        FRD systems, only one margin is found and returned. 
    epsw=1e-12: float
        frequencies below this value are considered static gain, and not
        returned as margin.
       
    Returns
    -------
    gm, pm, sm, wg, wp, ws: float or array_like
        Gain margin gm, phase margin pm, stability margin sm, and 
        associated crossover
        frequencies wg, wp, and ws of SISO open-loop. If more than
        one crossover frequency is detected, returns the lowest corresponding
        margin. 
        When requesting all margins, the return values are array_like, 
        and all margins are returns for linear systems not equal to FRD
        """

    try:
        if isinstance(sysdata, frdata.FRD):
            sys = frdata.FRD(sysdata, smooth=True)
        elif isinstance(sysdata, xferfcn.TransferFunction):
            sys = sysdata
        elif getattr(sysdata, '__iter__', False) and len(sysdata) == 3:
            mag, phase, omega = sysdata
            sys = frdata.FRD(mag * np.exp((1j / 360.) * phase),
                             omega,
                             smooth=True)
        else:
            sys = xferfcn._convertToTransferFunction(sysdata)
    except Exception, e:
        print(e)
        raise ValueError("Margin sysdata must be either a linear system or "
                         "a 3-sequence of mag, phase, omega.")
예제 #4
0
 def testMIMO(self):
     """Test conversion of a single input, two-output state-space
     system against the same TF"""
     s = TransferFunction([1, 0], [1])
     b0 = 0.2
     b1 = 0.1
     b2 = 0.5
     a0 = 2.3
     a1 = 6.3
     a2 = 3.6
     a3 = 1.0
     h = (b0 + b1 * s + b2 * s ** 2) / (a0 + a1 * s + a2 * s ** 2 + a3 * s ** 3)
     H = TransferFunction([[h.num[0][0]], [(h * s).num[0][0]]], [[h.den[0][0]], [h.den[0][0]]])
     sys = _convertToStateSpace(H)
     H2 = _convertToTransferFunction(sys)
     np.testing.assert_array_almost_equal(H.num[0][0], H2.num[0][0])
     np.testing.assert_array_almost_equal(H.den[0][0], H2.den[0][0])
     np.testing.assert_array_almost_equal(H.num[1][0], H2.num[1][0])
     np.testing.assert_array_almost_equal(H.den[1][0], H2.den[1][0])
예제 #5
0
def phase_crossover_frequencies(sys):
    """
    Compute frequencies and gains at intersections with real axis
    in Nyquist plot.

    Call as:
        omega, gain = phase_crossover_frequencies()

    Returns
    -------
    omega: 1d array of (non-negative) frequencies where Nyquist plot
    intersects the real axis

    gain: 1d array of corresponding gains
        
    Examples
    --------
    >>> tf = TransferFunction([1], [1, 2, 3, 4])
    >>> PhaseCrossoverFrequenies(tf)
    (array([ 1.73205081,  0.        ]), array([-0.5 ,  0.25]))
    """

    # Convert to a transfer function
    tf = xferfcn._convertToTransferFunction(sys)

    # if not siso, fall back to (0,0) element
    #! TODO: should add a check and warning here
    num = tf.num[0][0]
    den = tf.den[0][0]

    # Compute frequencies that we cross over the real axis
    numj = (1.j)**np.arange(len(num)-1,-1,-1)*num
    denj = (-1.j)**np.arange(len(den)-1,-1,-1)*den
    allfreq = np.roots(np.imag(np.polymul(numj,denj)))
    realfreq = np.real(allfreq[np.isreal(allfreq)])
    realposfreq = realfreq[realfreq >= 0.]

    # using real() to avoid rounding errors and results like 1+0j
    # it would be nice to have a vectorized version of self.evalfr here
    gain = np.real(np.asarray([tf.evalfr(f)[0][0] for f in realposfreq]))

    return realposfreq, gain
예제 #6
0
def phase_crossover_frequencies(sys):
    """
    Compute frequencies and gains at intersections with real axis
    in Nyquist plot.

    Call as:
        omega, gain = phase_crossover_frequencies()

    Returns
    -------
    omega: 1d array of (non-negative) frequencies where Nyquist plot
    intersects the real axis

    gain: 1d array of corresponding gains
        
    Examples
    --------
    >>> tf = TransferFunction([1], [1, 2, 3, 4])
    >>> PhaseCrossoverFrequenies(tf)
    (array([ 1.73205081,  0.        ]), array([-0.5 ,  0.25]))
    """

    # Convert to a transfer function
    tf = xferfcn._convertToTransferFunction(sys)

    # if not siso, fall back to (0,0) element
    #! TODO: should add a check and warning here
    num = tf.num[0][0]
    den = tf.den[0][0]

    # Compute frequencies that we cross over the real axis
    numj = (1.j)**np.arange(len(num) - 1, -1, -1) * num
    denj = (-1.j)**np.arange(len(den) - 1, -1, -1) * den
    allfreq = np.roots(np.imag(np.polymul(numj, denj)))
    realfreq = np.real(allfreq[np.isreal(allfreq)])
    realposfreq = realfreq[realfreq >= 0.]

    # using real() to avoid rounding errors and results like 1+0j
    # it would be nice to have a vectorized version of self.evalfr here
    gain = np.real(np.asarray([tf.evalfr(f)[0][0] for f in realposfreq]))

    return realposfreq, gain
예제 #7
0
 def testMIMO(self):
     """Test conversion of a single input, two-output state-space
     system against the same TF"""
     s = TransferFunction([1, 0], [1])
     b0 = 0.2
     b1 = 0.1
     b2 = 0.5
     a0 = 2.3
     a1 = 6.3
     a2 = 3.6
     a3 = 1.0
     h = (b0 + b1 * s + b2 * s**2) / (a0 + a1 * s + a2 * s**2 + a3 * s**3)
     H = TransferFunction([[h.num[0][0]], [(h * s).num[0][0]]],
                          [[h.den[0][0]], [h.den[0][0]]])
     sys = _convertToStateSpace(H)
     H2 = _convertToTransferFunction(sys)
     np.testing.assert_array_almost_equal(H.num[0][0], H2.num[0][0])
     np.testing.assert_array_almost_equal(H.den[0][0], H2.den[0][0])
     np.testing.assert_array_almost_equal(H.num[1][0], H2.num[1][0])
     np.testing.assert_array_almost_equal(H.den[1][0], H2.den[1][0])
예제 #8
0
    def testConvertToTransferFunction(self):
        """Test for correct state space to transfer function conversion."""

        A = [[1., -2.], [-3., 4.]]
        B = [[6., 5.], [4., 3.]]
        C = [[1., -2.], [3., -4.], [5., -6.]]
        D = [[1., 0.], [0., 1.], [1., 0.]]
        sys = StateSpace(A, B, C, D)

        tfsys = _convertToTransferFunction(sys)

        num = [[np.array([1., -7., 10.]), np.array([-1., 10.])],
               [np.array([2., -8.]), np.array([1., -2., -8.])],
               [np.array([1., 1., -30.]), np.array([7., -22.])]]
        den = [[np.array([1., -5., -2.]) for j in range(sys.inputs)]
            for i in range(sys.outputs)]

        for i in range(sys.outputs):
            for j in range(sys.inputs):
                np.testing.assert_array_almost_equal(tfsys.num[i][j], num[i][j])
                np.testing.assert_array_almost_equal(tfsys.den[i][j], den[i][j])
예제 #9
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
예제 #10
0
def tfdata(sys, **kw):
    '''
    Return transfer function data objects for a system

    Parameters
    ----------
    sys: Lti (StateSpace, or TransferFunction)
        LTI system whose data will be returned

    Keywords
    --------
    inputs = int; outputs = int
        For MIMO transfer function, return num, den for given inputs, outputs

    Returns
    -------
    (num, den): numerator and denominator arrays
        Transfer function coefficients (SISO only)
    '''
    tf = _convertToTransferFunction(sys, **kw)

    return (tf.num, tf.den)
예제 #11
0
def tfdata(sys, **kw):
    '''
    Return transfer function data objects for a system
    
    Parameters
    ----------
    sys: Lti (StateSpace, or TransferFunction)
        LTI system whose data will be returned

    Keywords
    --------
    inputs = int; outputs = int
        For MIMO transfer function, return num, den for given inputs, outputs

    Returns
    -------
    (num, den): numerator and denominator arrays
        Transfer function coefficients (SISO only)
    '''
    tf = _convertToTransferFunction(sys, **kw)
    
    return (tf.num, tf.den)
예제 #12
0
def _systopoly1d(sys):
    """Extract numerator and denominator polynomails for a system"""
    # Allow inputs from the signal processing toolbox
    if (isinstance(sys, scipy.signal.lti)):
        nump = sys.num; denp = sys.den;

    else:
        # Convert to a transfer function, if needed
        sys = xferfcn._convertToTransferFunction(sys)

        # Make sure we have a SISO system
        if (sys.inputs > 1 or sys.outputs > 1):
            raise ControlMIMONotImplemented()

        # Start by extracting the numerator and denominator from system object
        nump = sys.num[0][0]; denp = sys.den[0][0];

    # Check to see if num, den are already polynomials; otherwise convert
    if (not isinstance(nump, poly1d)): nump = poly1d(nump)
    if (not isinstance(denp, poly1d)): denp = poly1d(denp)

    return (nump, denp)
예제 #13
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
예제 #14
0
def _systopoly1d(sys):
    """Extract numerator and denominator polynomails for a system"""
    # Allow inputs from the signal processing toolbox
    if (isinstance(sys, scipy.signal.lti)):
        nump = sys.num
        denp = sys.den

    else:
        # Convert to a transfer function, if needed
        sys = xferfcn._convertToTransferFunction(sys)

        # Make sure we have a SISO system
        if (sys.inputs > 1 or sys.outputs > 1):
            raise ControlMIMONotImplemented()

        # Start by extracting the numerator and denominator from system object
        nump = sys.num[0][0]
        denp = sys.den[0][0]

    # Check to see if num, den are already polynomials; otherwise convert
    if (not isinstance(nump, poly1d)): nump = poly1d(nump)
    if (not isinstance(denp, poly1d)): denp = poly1d(denp)

    return (nump, denp)
예제 #15
0
def ss2tf(*args):
    """
    Transform a state space system to a transfer function.
    
    The function accepts either 1 or 4 parameters:
    
    ``ss2tf(sys)``
        Convert a linear system into space system form. Always creates a 
        new system, even if sys is already a StateSpace object.
        
    ``ss2tf(A, B, C, D)``
        Create a state space system from the matrices of its state and
        output equations.
        
        For details see: :func:`ss` 
    
    Parameters
    ----------
    sys: StateSpace
        A linear system
    A: array_like or string
        System matrix
    B: array_like or string
        Control matrix
    C: array_like or string
        Output matrix
    D: array_like or string
        Feed forward matrix

    Returns
    -------
    out: TransferFunction 
        New linear system in transfer function form

    Raises
    ------
    ValueError
        if matrix sizes are not self-consistent, or if an invalid number of
        arguments is passed in
    TypeError
        if `sys` is not a StateSpace object

    See Also
    --------
    tf
    ss
    tf2ss

    Examples
    --------
    >>> A = [[1., -2], [3, -4]]
    >>> B = [[5.], [7]]
    >>> C = [[6., 8]]
    >>> D = [[9.]]
    >>> sys1 = ss2tf(A, B, C, D)
    
    >>> sys_ss = ss(A, B, C, D)
    >>> sys2 = ss2tf(sys_ss) 

    """

    if len(args) == 4 or len(args) == 5:
        # Assume we were given the A, B, C, D matrix and (optional) dt
        return _convertToTransferFunction(StateSpace(*args))

    elif len(args) == 1:
        sys = args[0]
        if isinstance(sys, StateSpace):
            return _convertToTransferFunction(sys)
        else:
            raise TypeError("ss2tf(sys): sys must be a StateSpace object.  It \
is %s." % type(sys))
    else:
        raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))
예제 #16
0
def feedback(sys1, sys2=1, sign=-1):
    """
    Feedback interconnection between two I/O systems.

    Parameters
    ----------
    sys1: scalar, StateSpace, or TransferFunction
        The primary plant.
    sys2: scalar, StateSpace, or TransferFunction
        The feedback plant (often a feedback controller).
    sign: scalar 
        The sign of feedback.  `sign` = -1 indicates negative feedback, and
        `sign` = 1 indicates positive feedback.  `sign` is an optional
        argument; it assumes a value of -1 if not specified.

    Returns
    -------
    out: StateSpace or TransferFunction

    Raises
    ------
    ValueError
        if `sys1` does not have as many inputs as `sys2` has outputs, or if
        `sys2` does not have as many inputs as `sys1` has outputs
    NotImplementedError
        if an attempt is made to perform a feedback on a MIMO TransferFunction
        object

    See Also
    --------
    series
    parallel

    Notes
    -----
    This function is a wrapper for the feedback function in the StateSpace and
    TransferFunction classes.  It calls TransferFunction.feedback if `sys1` is a
    TransferFunction object, and StateSpace.feedback if `sys1` is a StateSpace
    object.  If `sys1` is a scalar, then it is converted to `sys2`'s type, and
    the corresponding feedback function is used.  If `sys1` and `sys2` are both
    scalars, then TransferFunction.feedback is used.
  
    """

    # Check for correct input types.
    if not isinstance(sys1, (int, float, complex, tf.TransferFunction,
        ss.StateSpace)):
        raise TypeError("sys1 must be a TransferFunction or StateSpace object, \
or a scalar.")
    if not isinstance(sys2, (int, float, complex, tf.TransferFunction,
        ss.StateSpace)):
        raise TypeError("sys2 must be a TransferFunction or StateSpace object, \
or a scalar.")

    # If sys1 is a scalar, convert it to the appropriate LTI type so that we can
    # its feedback member function.
    if isinstance(sys1, (int, float, complex)):
        if isinstance(sys2, tf.TransferFunction):
            sys1 = tf._convertToTransferFunction(sys1)
        elif isinstance(sys2, ss.StateSpace):
            sys1 = ss._convertToStateSpace(sys1)
        else: # sys2 is a scalar.
            sys1 = tf._convertToTransferFunction(sys1)
            sys2 = tf._convertToTransferFunction(sys2)

    return sys1.feedback(sys2, sign)
예제 #17
0
def feedback(sys1, sys2=1, sign=-1):
    """
    Feedback interconnection between two I/O systems.

    Parameters
    ----------
    sys1: scalar, StateSpace, or TransferFunction
        The primary plant.
    sys2: scalar, StateSpace, or TransferFunction
        The feedback plant (often a feedback controller).
    sign: scalar 
        The sign of feedback.  `sign` = -1 indicates negative feedback, and
        `sign` = 1 indicates positive feedback.  `sign` is an optional
        argument; it assumes a value of -1 if not specified.

    Returns
    -------
    out: StateSpace or TransferFunction

    Raises
    ------
    ValueError
        if `sys1` does not have as many inputs as `sys2` has outputs, or if
        `sys2` does not have as many inputs as `sys1` has outputs
    NotImplementedError
        if an attempt is made to perform a feedback on a MIMO TransferFunction
        object

    See Also
    --------
    series
    parallel

    Notes
    -----
    This function is a wrapper for the feedback function in the StateSpace and
    TransferFunction classes.  It calls TransferFunction.feedback if `sys1` is a
    TransferFunction object, and StateSpace.feedback if `sys1` is a StateSpace
    object.  If `sys1` is a scalar, then it is converted to `sys2`'s type, and
    the corresponding feedback function is used.  If `sys1` and `sys2` are both
    scalars, then TransferFunction.feedback is used.
  
    """

    # Check for correct input types.
    if not isinstance(
            sys1, (int, float, complex, tf.TransferFunction, ss.StateSpace)):
        raise TypeError(
            "sys1 must be a TransferFunction or StateSpace object, \
or a scalar.")
    if not isinstance(
            sys2, (int, float, complex, tf.TransferFunction, ss.StateSpace)):
        raise TypeError(
            "sys2 must be a TransferFunction or StateSpace object, \
or a scalar.")

    # If sys1 is a scalar, convert it to the appropriate LTI type so that we can
    # its feedback member function.
    if isinstance(sys1, (int, float, complex)):
        if isinstance(sys2, tf.TransferFunction):
            sys1 = tf._convertToTransferFunction(sys1)
        elif isinstance(sys2, ss.StateSpace):
            sys1 = ss._convertToStateSpace(sys1)
        else:  # sys2 is a scalar.
            sys1 = tf._convertToTransferFunction(sys1)
            sys2 = tf._convertToTransferFunction(sys2)

    return sys1.feedback(sys2, sign)
예제 #18
0
def stability_margins(sysdata, deg=True, returnall=False, epsw=1e-12):
    """Calculate gain, phase and stability margins and associated
    crossover frequencies.
    
    Usage
    -----
    gm, pm, sm, wg, wp, ws = stability_margins(sysdata, deg=True)
    
    Parameters
    ----------
    sysdata: linsys or (mag, phase, omega) sequence 
        sys : linsys
            Linear SISO system
        mag, phase, omega : sequence of array_like
            Input magnitude, phase, and frequencies (rad/sec) sequence from 
            bode frequency response data 
    deg=True: boolean  
        If true, all input and output phases in degrees, else in radians
    returnall=False: boolean
        If true, return all margins found. Note that for frequency data or
        FRD systems, only one margin is found and returned. 
    epsw=1e-12: float
        frequencies below this value are considered static gain, and not
        returned as margin.
       
    Returns
    -------
    gm, pm, sm, wg, wp, ws: float or array_like
        Gain margin gm, phase margin pm, stability margin sm, and 
        associated crossover
        frequencies wg, wp, and ws of SISO open-loop. If more than
        one crossover frequency is detected, returns the lowest corresponding
        margin. 
        When requesting all margins, the return values are array_like, 
        and all margins are returns for linear systems not equal to FRD
        """

    try:
        if isinstance(sysdata, frdata.FRD):
            sys = frdata.FRD(sysdata, smooth=True) 
        elif isinstance(sysdata, xferfcn.TransferFunction):
            sys = sysdata
        elif getattr(sysdata, '__iter__', False) and len(sysdata) == 3:
            mag, phase, omega = sysdata
            sys = frdata.FRD(mag*np.exp((1j/360.)*phase), omega, smooth=True)
        else:
            sys = xferfcn._convertToTransferFunction(sysdata)
    except Exception as e:
        print (e)
        raise ValueError("Margin sysdata must be either a linear system or "
                         "a 3-sequence of mag, phase, omega.")

    # calculate gain of system
    if isinstance(sys, xferfcn.TransferFunction):
        
        # check for siso
        if not issiso(sys):
            raise ValueError("Can only do margins for SISO system")
        
        # real and imaginary part polynomials in omega:
        rnum, inum = _polyimsplit(sys.num[0][0])
        rden, iden = _polyimsplit(sys.den[0][0])

        # test imaginary part of tf == 0, for phase crossover/gain margins
        test_w_180 = np.polyadd(np.polymul(inum, rden), np.polymul(rnum, -iden))
        w_180 = np.roots(test_w_180)
        w_180 = np.real(w_180[(np.imag(w_180) == 0) * (w_180 > epsw)])
        w_180.sort()

        # test magnitude is 1 for gain crossover/phase margins
        test_wc = np.polysub(np.polyadd(_polysqr(rnum), _polysqr(inum)), 
                             np.polyadd(_polysqr(rden), _polysqr(iden)))
        wc = np.roots(test_wc)
        wc = np.real(wc[(np.imag(wc) == 0) * (wc > epsw)])
        wc.sort()

        # stability margin was a bitch to elaborate, relies on magnitude to
        # point -1, then take the derivative. Second derivative needs to be >0
        # to have a minimum
        test_wstabn = np.polyadd(_polysqr(rnum), _polysqr(inum))
        test_wstabd = np.polyadd(_polysqr(np.polyadd(rnum,rden)), 
                                 _polysqr(np.polyadd(inum,iden)))
        test_wstab = np.polysub(
            np.polymul(np.polyder(test_wstabn),test_wstabd), 
            np.polymul(np.polyder(test_wstabd),test_wstabn))

        # find the solutions
        wstab = np.roots(test_wstab)

        # and find the value of the 2nd derivative there, needs to be positive
        wstabplus = np.polyval(np.polyder(test_wstab), wstab)
        wstab = np.real(wstab[(np.imag(wstab) == 0) * (wstab > epsw) *
                              (np.abs(wstabplus) > 0.)])
        wstab.sort()

    else:
        # a bit coarse, have the interpolated frd evaluated again
        def mod(w):
            """to give the function to calculate |G(jw)| = 1"""
            return [np.abs(sys.evalfr(w[0])[0][0]) - 1]

        def arg(w):
            """function to calculate the phase angle at -180 deg"""
            return [np.angle(sys.evalfr(w[0])[0][0]) + np.pi]

        def dstab(w):
            """function to calculate the distance from -1 point"""
            return np.abs(sys.evalfr(w[0])[0][0] + 1.)

        # how to calculate the frequency at which |G(jw)| = 1
        wc = np.array([sp.optimize.fsolve(mod, sys.omega[0])])[0]
        w_180 = np.array([sp.optimize.fsolve(arg, sys.omega[0])])[0]
        wstab = np.real(
            np.array([sp.optimize.fmin(dstab, sys.omega[0], disp=0)])[0])

    # margins, as iterables, converted frdata and xferfcn calculations to
    # vector for this
    PM = np.angle(sys.evalfr(wc)[0][0], deg=True) + 180
    GM = 1/(np.abs(sys.evalfr(w_180)[0][0]))
    SM = np.abs(sys.evalfr(wstab)[0][0]+1)

    if returnall:
        return GM, PM, SM, wc, w_180, wstab
    else:
        return (
            (GM.shape[0] or None) and GM[0], 
            (PM.shape[0] or None) and PM[0], 
            (SM.shape[0] or None) and SM[0], 
            (wc.shape[0] or None) and wc[0],
            (w_180.shape[0] or None) and w_180[0],
            (wstab.shape[0] or None) and wstab[0])
예제 #19
0
def ss2tf(*args):
    """
    Transform a state space system to a transfer function.
    
    The function accepts either 1 or 4 parameters:
    
    ``ss2tf(sys)``
        Convert a linear system into space system form. Always creates a 
        new system, even if sys is already a StateSpace object.
        
    ``ss2tf(A, B, C, D)``
        Create a state space system from the matrices of its state and
        output equations.
        
        For details see: :func:`ss` 
    
    Parameters
    ----------
    sys: StateSpace
        A linear system
    A: array_like or string
        System matrix
    B: array_like or string
        Control matrix
    C: array_like or string
        Output matrix
    D: array_like or string
        Feed forward matrix

    Returns
    -------
    out: TransferFunction 
        New linear system in transfer function form

    Raises
    ------
    ValueError
        if matrix sizes are not self-consistent, or if an invalid number of
        arguments is passed in
    TypeError
        if `sys` is not a StateSpace object

    See Also
    --------
    tf
    ss
    tf2ss

    Examples
    --------
    >>> A = [[1., -2], [3, -4]]
    >>> B = [[5.], [7]]
    >>> C = [[6., 8]]
    >>> D = [[9.]]
    >>> sys1 = ss2tf(A, B, C, D)
    
    >>> sys_ss = ss(A, B, C, D)
    >>> sys2 = ss2tf(sys_ss) 

    """

    if len(args) == 4 or len(args) == 5:
        # Assume we were given the A, B, C, D matrix and (optional) dt
        return _convertToTransferFunction(StateSpace(*args))

    elif len(args) == 1:
        sys = args[0]
        if isinstance(sys, StateSpace):
            return _convertToTransferFunction(sys)
        else:
            raise TypeError("ss2tf(sys): sys must be a StateSpace object.  It \
is %s." % type(sys))
    else:
        raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))