예제 #1
0
def test_stability_margins(tsys):
    sys, refout, refoutall = tsys
    """Test stability_margins() function"""
    out = stability_margins(sys)
    assert_allclose(out, refout, atol=1.5e-2)
    out = stability_margins(sys, returnall=True)
    compare_allmargins(out, refoutall, atol=1.5e-2)
예제 #2
0
def test_mag_phase_omega():
    """Test for bug reported in gh-58"""
    sys = TransferFunction(15, [1, 6, 11, 6])
    out = stability_margins(sys)
    omega = np.logspace(-2, 2, 1000)
    mag, phase, omega = sys.frequency_response(omega)
    out2 = stability_margins((mag, phase * 180 / np.pi, omega))
    ind = [0, 1, 3, 4]  # indices of gm, pm, wg, wp -- ignore sm
    marg1 = np.array(out)[ind]
    marg2 = np.array(out2)[ind]
    assert_allclose(marg1, marg2, atol=1.5e-3)
예제 #3
0
def test_stability_margins_discrete(cnum, cden, dt, ref, rtol,
                                    poly_is_inaccurate):
    """Test stability_margins with discrete TF input"""
    tf = TransferFunction(cnum, cden).sample(dt)
    if poly_is_inaccurate:
        with pytest.warns(UserWarning, match="numerical inaccuracy in 'poly'"):
            out = stability_margins(tf)
        # cover the explicit frd branch and make sure it yields the same
        # results as the fallback mechanism
        out_frd = stability_margins(tf, method='frd')
        assert_allclose(out, out_frd)
    else:
        out = stability_margins(tf)
    assert_allclose(out, ref, rtol=rtol)
예제 #4
0
def test_stability_margins_3input(tsys):
    sys, refout, refoutall = tsys
    """Test stability_margins() function with mag, phase, omega input"""
    omega = np.logspace(-2, 2, 2000)
    mag, phase, omega_ = sys.frequency_response(omega)
    out = stability_margins((mag, phase * 180 / np.pi, omega_))
    assert_allclose(out, refout, atol=1.5e-3)
예제 #5
0
def test_frd():
    """Test FrequencyResonseData margins"""
    f = np.array([
        0.005, 0.010, 0.020, 0.030, 0.040, 0.050, 0.060, 0.070, 0.080, 0.090,
        0.100, 0.200, 0.300, 0.400, 0.500, 0.750, 1.000, 1.250, 1.500, 1.750,
        2.000, 2.250, 2.500, 2.750, 3.000, 3.250, 3.500, 3.750, 4.000, 4.250,
        4.500, 4.750, 5.000, 6.000, 7.000, 8.000, 9.000, 10.000
    ])
    gain = np.array([
        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.2, 0.3,
        0.5, 0.5, -0.4, -2.3, -4.8, -7.3, -9.6, -11.7, -13.6, -15.3, -16.9,
        -18.3, -19.6, -20.8, -22.0, -23.1, -24.1, -25.0, -25.9, -29.1, -31.9,
        -34.2, -36.2, -38.1
    ])
    phase = np.array([
        0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -19, -29, -40, -51, -81,
        -114, -144, -168, -187, -202, -214, -224, -233, -240, -247, -253, -259,
        -264, -269, -273, -277, -280, -292, -301, -307, -313, -317
    ])
    # calculate response as complex number
    resp = 10**(gain / 20) * np.exp(1j * phase / (180. / np.pi))
    # frequency response data
    fresp = FrequencyResponseData(resp, f * 2 * np.pi, smooth=True)
    s = TransferFunction([1, 0], [1])
    G = 1. / (s**2)
    K = 1.
    C = K * (1 + 1.9 * s)
    TFopen = fresp * C * G
    gm, pm, sm, wg, wp, ws = stability_margins(TFopen)
    assert_allclose([pm], [44.55], atol=.01)
예제 #6
0
def test_zmore_stability_margins(tsys_zmore):
    """Test stability_margins for more tricky systems with returnall"""
    res = stability_margins(tsys_zmore['sys'] * tsys_zmore['K'],
                            returnall=True)
    compare_allmargins(res,
                       tsys_zmore['result'],
                       atol=tsys_zmore['atol'],
                       rtol=tsys_zmore['rtol'])
예제 #7
0
def margin(*args):
    """Calculate gain and phase margins and associated crossover frequencies
    
    Function ``margin`` takes either 1 or 3 parameters.
    
    Parameters
    ----------
    sys : StateSpace or TransferFunction 
        Linear SISO system
    mag, phase, w : array_like
        Input magnitude, phase (in deg.), and frequencies (rad/sec) from
        bode frequency response data

    Returns
    -------
    gm, pm, Wcg, Wcp : float
        Gain margin gm, phase margin pm (in deg), gain crossover frequency 
        (corresponding to phase margin) and phase crossover frequency
        (corresponding to gain margin), in rad/sec of SISO open-loop.
        If more than one crossover frequency is detected, returns the lowest
        corresponding margin.

    Examples
    --------
    >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
    >>> gm, pm, wg, wp = margin(sys)
    margin: no magnitude crossings found
    
    .. todo:: 
        better ecample system!
        
        #>>> gm, pm, wg, wp = margin(mag, phase, w)
    """
    if len(args) == 1:
        sys = args[0]
        margin = margins.stability_margins(sys)
    elif len(args) == 3:
        margin = margins.stability_margins(args)
    else: 
        raise ValueError("Margin needs 1 or 3 arguments; received %i." 
            % len(args))
            
    return margin[0], margin[1], margin[4], margin[3]
예제 #8
0
def margin(*args):
    """Calculate gain and phase margins and associated crossover frequencies
    
    Function ``margin`` takes either 1 or 3 parameters.
    
    Parameters
    ----------
    sys : StateSpace or TransferFunction 
        Linear SISO system
    mag, phase, w : array_like
        Input magnitude, phase (in deg.), and frequencies (rad/sec) from
        bode frequency response data

    Returns
    -------
    gm, pm, Wcg, Wcp : float
        Gain margin gm, phase margin pm (in deg), gain crossover frequency 
        (corresponding to phase margin) and phase crossover frequency
        (corresponding to gain margin), in rad/sec of SISO open-loop.
        If more than one crossover frequency is detected, returns the lowest
        corresponding margin.

    Examples
    --------
    >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
    >>> gm, pm, wg, wp = margin(sys)
    margin: no magnitude crossings found
    
    .. todo:: 
        better ecample system!
        
        #>>> gm, pm, wg, wp = margin(mag, phase, w)
    """
    if len(args) == 1:
        sys = args[0]
        margin = margins.stability_margins(sys)
    elif len(args) == 3:
        margin = margins.stability_margins(args)
    else: 
        raise ValueError("Margin needs 1 or 3 arguments; received %i." 
            % len(args))
            
    return margin[0], margin[1], margin[4], margin[3]
예제 #9
0
def test_frd_indexing():
    """Test FRD edge cases

    Make sure frd objects with non benign data do not raise exceptions when
    the stability criteria evaluate at the first or last frequency point
    bug reported in gh-407
    """
    # frequency points just a little under 1. and over 2.
    w = np.linspace(.99, 2.01, 11)

    # Note: stability_margins will convert the frd with smooth=True

    # gain margins
    # p crosses -180 at w[0]=1. and w[-1]=2.
    m = 0.6
    p = -180 * (2 * w - 1)
    d = m * np.exp(1J * np.pi / 180 * p)
    frd_gm = FrequencyResponseData(d, w)
    gm, _, _, wg, _, _ = stability_margins(frd_gm, returnall=True)
    assert_allclose(gm, [1 / m, 1 / m], atol=0.01)
    assert_allclose(wg, [1., 2.], atol=0.01)

    # phase margins
    # m crosses 1 at w[0]=1. and w[-1]=2.
    m = -(2 * w - 3)**4 + 2
    p = -90.
    d = m * np.exp(1J * np.pi / 180 * p)
    frd_pm = FrequencyResponseData(d, w)
    _, pm, _, _, wp, _ = stability_margins(frd_pm, returnall=True)
    assert_allclose(pm, [90., 90.], atol=0.01)
    assert_allclose(wp, [1., 2.], atol=0.01)

    # stability margins
    # minimum abs(d+1)=1-m at w[1]=1. and w[-2]=2., in nyquist plot
    w = np.arange(.9, 2.1, 0.1)
    m = 0.6
    p = -180 * (2 * w - 1)
    d = m * np.exp(1J * np.pi / 180 * p)
    frd_sm = FrequencyResponseData(d, w)
    _, _, sm, _, _, ws = stability_margins(frd_sm, returnall=True)
    assert_allclose(sm, [1 - m, 1 - m], atol=0.01)
    assert_allclose(ws, [1., 2.], atol=0.01)
예제 #10
0
def test_stability_margins_omega(tsys):
    sys, refout, refoutall = tsys
    """Test stability_margins() with interpolated frequencies"""
    omega = np.logspace(-2, 2, 2000)
    out = stability_margins(FrequencyResponseData(sys, omega))
    assert_allclose(out, refout, atol=1.5e-3)
예제 #11
0
def test_stability_margins_discrete(cnum, cden, dt, ref, rtol):
    """Test stability_margins with discrete TF input"""
    tf = TransferFunction(cnum, cden).sample(dt)
    out = stability_margins(tf)
    assert_allclose(out, ref, rtol=rtol)