예제 #1
0
    def test_squeeze_exceptions(self, fcn):
        if fcn == ct.frd:
            sys = fcn(ct.rss(2, 1, 1), [1e-2, 1e-1, 1, 1e1, 1e2])
        else:
            sys = fcn(ct.rss(2, 1, 1))

        with pytest.raises(ValueError, match="unknown squeeze value"):
            sys.frequency_response([1], squeeze=1)
        with pytest.raises(ValueError, match="unknown squeeze value"):
            sys([1j], squeeze='siso')
        with pytest.raises(ValueError, match="unknown squeeze value"):
            evalfr(sys, [1j], squeeze='siso')

        with pytest.raises(ValueError, match="must be 1D"):
            sys.frequency_response([[0.1, 1], [1, 10]])
        with pytest.raises(ValueError, match="must be 1D"):
            sys([[0.1j, 1j], [1j, 10j]])
        with pytest.raises(ValueError, match="must be 1D"):
            evalfr(sys, [[0.1j, 1j], [1j, 10j]])

        with pytest.warns(DeprecationWarning, match="LTI `inputs`"):
            ninputs = sys.inputs
        assert ninputs == sys.ninputs

        with pytest.warns(DeprecationWarning, match="LTI `outputs`"):
            noutputs = sys.outputs
        assert noutputs == sys.noutputs

        if isinstance(sys, ct.StateSpace):
            with pytest.warns(DeprecationWarning, match="StateSpace `states`"):
                nstates = sys.states
            assert nstates == sys.nstates
예제 #2
0
    def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape,
                     omega_type):
        """Test correct behavior of frequencey response squeeze parameter."""
        # Create the system to be tested
        if fcn == ct.frd:
            sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2])
        elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check():
            pytest.skip("Conversion of MIMO systems to transfer functions "
                        "requires slycot.")
        else:
            sys = fcn(ct.rss(nstate, nout, ninp))

        if omega_type == "numpy":
            omega = np.asarray(omega)
            isscalar = omega.ndim == 0
            # keep the ndarray type even for scalars
            s = np.asarray(omega * 1j)
        else:
            isscalar = not hasattr(omega, '__len__')
            if isscalar:
                s = omega * 1J
            else:
                s = [w * 1J for w in omega]

        # Call the transfer function directly and make sure shape is correct
        assert sys(s, squeeze=squeeze).shape == shape

        # Make sure that evalfr also works as expected
        assert ct.evalfr(sys, s, squeeze=squeeze).shape == shape

        # Check frequency response
        mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze)
        if isscalar and squeeze is not True:
            # sys.frequency_response() expects a list as an argument
            # Add the shape of the input to the expected shape
            assert mag.shape == shape + (1, )
            assert phase.shape == shape + (1, )
        else:
            assert mag.shape == shape
            assert phase.shape == shape

        # Make sure the default shape lines up with squeeze=None case
        if squeeze is None:
            assert sys(s).shape == shape

        # Changing config.default to False should return 3D frequency response
        ct.config.set_defaults('control', squeeze_frequency_response=False)
        mag, phase, _ = sys.frequency_response(omega)
        if isscalar:
            assert mag.shape == (sys.noutputs, sys.ninputs, 1)
            assert phase.shape == (sys.noutputs, sys.ninputs, 1)
            assert sys(s).shape == (sys.noutputs, sys.ninputs)
            assert ct.evalfr(sys, s).shape == (sys.noutputs, sys.ninputs)
        else:
            assert mag.shape == (sys.noutputs, sys.ninputs, len(omega))
            assert phase.shape == (sys.noutputs, sys.ninputs, len(omega))
            assert sys(s).shape == \
                (sys.noutputs, sys.ninputs, len(omega))
            assert ct.evalfr(sys, s).shape == \
                (sys.noutputs, sys.ninputs, len(omega))
예제 #3
0
    def testAcker(self, fixedseed):
        for states in range(1, self.maxStates):
            for i in range(self.maxTries):
                # start with a random SS system and transform to TF then
                # back to SS, check that the matrices are the same.
                sys = rss(states, 1, 1)
                if (self.debug):
                    print(sys)

                # Make sure the system is not degenerate
                Cmat = ctrb(sys.A, sys.B)
                if np.linalg.matrix_rank(Cmat) != states:
                    if (self.debug):
                        print("  skipping (not reachable or ill conditioned)")
                        continue

                # Place the poles at random locations
                des = rss(states, 1, 1)
                poles = pole(des)

                # Now place the poles using acker
                K = acker(sys.A, sys.B, poles)
                new = ss(sys.A - sys.B * K, sys.B, sys.C, sys.D)
                placed = pole(new)

                # Debugging code
                # diff = np.sort(poles) - np.sort(placed)
                # if not all(diff < 0.001):
                #     print("Found a problem:")
                #     print(sys)
                #     print("desired = ", poles)

                np.testing.assert_array_almost_equal(np.sort(poles),
                                                     np.sort(placed),
                                                     decimal=4)
예제 #4
0
def test_nyquist_exceptions():
    # MIMO not implemented
    sys = ct.rss(2, 2, 2)
    with pytest.raises(ct.exception.ControlMIMONotImplemented,
                       match="only supports SISO"):
        ct.nyquist_plot(sys)

    # Legacy keywords for arrow size
    sys = ct.rss(2, 1, 1)
    with pytest.warns(FutureWarning, match="use `arrow_size` instead"):
        ct.nyquist_plot(sys, arrow_width=8, arrow_length=6)

    # Unknown arrow keyword
    with pytest.raises(ValueError, match="unsupported arrow location"):
        ct.nyquist_plot(sys, arrows='uniform')

    # Bad value for indent direction
    sys = ct.tf([1], [1, 0, 1])
    with pytest.raises(ValueError, match="unknown value for indent"):
        ct.nyquist_plot(sys, indent_direction='up')

    # Discrete time system sampled above Nyquist frequency
    sys = ct.drss(2, 1, 1)
    sys.dt = 0.01
    with pytest.warns(UserWarning, match="above Nyquist"):
        ct.nyquist_plot(sys, np.logspace(-2, 3))
예제 #5
0
def test_interconnect_docstring():
    """Test the examples from the interconnect() docstring"""

    # MIMO interconnection (note: use [C, P] instead of [P, C] for state order)
    P = ct.LinearIOSystem(
           ct.rss(2, 2, 2, strictly_proper=True), name='P')
    C = ct.LinearIOSystem(ct.rss(2, 2, 2), name='C')
    T = ct.interconnect(
        [C, P],
        connections = [
          ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'],
          ['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']],
        inplist = ['C.u[0]', 'C.u[1]'],
        outlist = ['P.y[0]', 'P.y[1]'],
    )
    T_ss = ct.feedback(P * C, ct.ss([], [], [], np.eye(2)))
    np.testing.assert_almost_equal(T.A, T_ss.A)
    np.testing.assert_almost_equal(T.B, T_ss.B)
    np.testing.assert_almost_equal(T.C, T_ss.C)
    np.testing.assert_almost_equal(T.D, T_ss.D)

    # Implicit interconnection (note: use [C, P, sumblk] for proper state order)
    P = ct.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y')
    C = ct.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u')
    sumblk = ct.summing_junction(inputs=['r', '-y'], output='e')
    T = ct.interconnect([C, P, sumblk], inplist='r', outlist='y')
    T_ss = ct.feedback(P * C, 1)
    np.testing.assert_almost_equal(T.A, T_ss.A)
    np.testing.assert_almost_equal(T.B, T_ss.B)
    np.testing.assert_almost_equal(T.C, T_ss.C)
    np.testing.assert_almost_equal(T.D, T_ss.D)
예제 #6
0
def test_response_copy():
    # Generate some initial data to use
    sys_siso = ct.rss(4, 1, 1)
    response_siso = ct.step_response(sys_siso)
    siso_ntimes = response_siso.time.size

    sys_mimo = ct.rss(4, 2, 1)
    response_mimo = ct.step_response(sys_mimo)
    mimo_ntimes = response_mimo.time.size

    # Transpose
    response_mimo_transpose = response_mimo(transpose=True)
    assert response_mimo.outputs.shape == (2, 1, mimo_ntimes)
    assert response_mimo_transpose.outputs.shape == (mimo_ntimes, 2, 1)
    assert response_mimo.states.shape == (4, 1, mimo_ntimes)
    assert response_mimo_transpose.states.shape == (mimo_ntimes, 4, 1)

    # Squeeze
    response_siso_as_mimo = response_siso(squeeze=False)
    assert response_siso_as_mimo.outputs.shape == (1, 1, siso_ntimes)
    assert response_siso_as_mimo.states.shape == (4, 1, siso_ntimes)
    assert response_siso_as_mimo._legacy_states.shape == (4, siso_ntimes)

    response_mimo_squeezed = response_mimo(squeeze=True)
    assert response_mimo_squeezed.outputs.shape == (2, mimo_ntimes)
    assert response_mimo_squeezed.states.shape == (4, mimo_ntimes)
    assert response_mimo_squeezed._legacy_states.shape == (4, 1, mimo_ntimes)

    # Squeeze and transpose
    response_mimo_sqtr = response_mimo(squeeze=True, transpose=True)
    assert response_mimo_sqtr.outputs.shape == (mimo_ntimes, 2)
    assert response_mimo_sqtr.states.shape == (mimo_ntimes, 4)
    assert response_mimo_sqtr._legacy_states.shape == (mimo_ntimes, 4, 1)

    # Return_x
    t, y = response_mimo
    t, y = response_mimo()
    t, y, x = response_mimo(return_x=True)
    with pytest.raises(ValueError, match="too many"):
        t, y = response_mimo(return_x=True)
    with pytest.raises(ValueError, match="not enough"):
        t, y, x = response_mimo

    # Labels
    assert response_mimo.output_labels is None
    assert response_mimo.state_labels is None
    assert response_mimo.input_labels is None
    response = response_mimo(
        output_labels=['y1', 'y2'], input_labels='u',
        state_labels=["x[%d]" % i for i in range(4)])
    assert response.output_labels == ['y1', 'y2']
    assert response.state_labels == ['x[0]', 'x[1]', 'x[2]', 'x[3]']
    assert response.input_labels == ['u']

    # Unknown keyword
    with pytest.raises(ValueError, match="Unknown parameter(s)*"):
        response_bad_kw = response_mimo(input=0)
예제 #7
0
def test_duplicate_sysname():
    # Start with an unnamed system
    sys = ct.rss(4, 1, 1)

    # No warnings should be generated if we reuse an an unnamed system
    with pytest.warns(None) as record:
        res = sys * sys
    assert not any([type(msg) == UserWarning for msg in record])

    # Generate a warning if the system is named
    sys = ct.rss(4, 1, 1, name='sys')
    with pytest.warns(UserWarning, match="duplicate object found"):
        res = sys * sys
예제 #8
0
    def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape):
        # Create the system to be tested
        if fcn == ct.frd:
            sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2])
        elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check():
            pytest.skip("Conversion of MIMO systems to transfer functions "
                        "requires slycot.")
        else:
            sys = fcn(ct.rss(nstate, nout, ninp))

        # Convert the frequency list to an array for easy of use
        isscalar = not hasattr(omega, '__len__')
        omega = np.array(omega)

        # Call the transfer function directly and make sure shape is correct
        assert sys(omega * 1j, squeeze=squeeze).shape == shape

        # Make sure that evalfr also works as expected
        assert ct.evalfr(sys, omega * 1j, squeeze=squeeze).shape == shape

        # Check frequency response
        mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze)
        if isscalar and squeeze is not True:
            # sys.frequency_response() expects a list as an argument
            # Add the shape of the input to the expected shape
            assert mag.shape == shape + (1, )
            assert phase.shape == shape + (1, )
        else:
            assert mag.shape == shape
            assert phase.shape == shape

        # Make sure the default shape lines up with squeeze=None case
        if squeeze is None:
            assert sys(omega * 1j).shape == shape

        # Changing config.default to False should return 3D frequency response
        ct.config.set_defaults('control', squeeze_frequency_response=False)
        mag, phase, _ = sys.frequency_response(omega)
        if isscalar:
            assert mag.shape == (sys.noutputs, sys.ninputs, 1)
            assert phase.shape == (sys.noutputs, sys.ninputs, 1)
            assert sys(omega * 1j).shape == (sys.noutputs, sys.ninputs)
            assert ct.evalfr(sys,
                             omega * 1j).shape == (sys.noutputs, sys.ninputs)
        else:
            assert mag.shape == (sys.noutputs, sys.ninputs, len(omega))
            assert phase.shape == (sys.noutputs, sys.ninputs, len(omega))
            assert sys(omega * 1j).shape == \
                (sys.noutputs, sys.ninputs, len(omega))
            assert ct.evalfr(sys, omega * 1j).shape == \
                (sys.noutputs, sys.ninputs, len(omega))
예제 #9
0
def test_init_namedif():
    # Set up the initial system
    sys = ct.rss(2, 1, 1)

    # Rename the system, inputs, and outouts
    sys_new = sys.copy()
    ct.StateSpace.__init__(sys_new, sys, inputs='u', outputs='y', name='new')
    assert sys_new.name == 'new'
    assert sys_new.input_labels == ['u']
    assert sys_new.output_labels == ['y']

    # Call constructor without re-initialization
    sys_keep = sys.copy()
    ct.StateSpace.__init__(sys_keep, sys, init_namedio=False)
    assert sys_keep.name == sys_keep.name
    assert sys_keep.input_labels == sys_keep.input_labels
    assert sys_keep.output_labels == sys_keep.output_labels

    # Make sure that passing an unrecognized keyword generates an error
    with pytest.raises(TypeError, match="unrecognized keyword"):
        ct.StateSpace.__init__(sys_keep,
                               sys,
                               inputs='u',
                               outputs='y',
                               init_namedio=False)
예제 #10
0
    def test_response_transpose(self, nstate, nout, ninp, squeeze, ysh_in,
                                ysh_no, xsh_in):
        sys = ct.rss(nstate, nout, ninp)
        T = np.linspace(0, 1, 8)

        # Step response - input indexed
        t, y, x = ct.step_response(sys,
                                   T,
                                   transpose=True,
                                   return_x=True,
                                   squeeze=squeeze)
        assert t.shape == (T.size, )
        assert y.shape == ysh_in
        assert x.shape == xsh_in

        # Initial response - no input indexing
        t, y, x = ct.initial_response(sys,
                                      T,
                                      1,
                                      transpose=True,
                                      return_x=True,
                                      squeeze=squeeze)
        assert t.shape == (T.size, )
        assert y.shape == ysh_no
        assert x.shape == (T.size, sys.nstates)
예제 #11
0
    def tsys(self):
        """Create some systems for testing"""
        class Tsys:
            pass
        T = Tsys()
        # Single input, single output continuous and discrete time systems
        sys = rss(3, 1, 1)
        T.siso_ss1 = StateSpace(sys.A, sys.B, sys.C, sys.D, None)
        T.siso_ss1c = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.0)
        T.siso_ss1d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.1)
        T.siso_ss2d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.2)
        T.siso_ss3d = StateSpace(sys.A, sys.B, sys.C, sys.D, True)

        # Two input, two output continuous time system
        A = [[-3., 4., 2.], [-1., -3., 0.], [2., 5., 3.]]
        B = [[1., 4.], [-3., -3.], [-2., 1.]]
        C = [[4., 2., -3.], [1., 4., 3.]]
        D = [[-2., 4.], [0., 1.]]
        T.mimo_ss1 = StateSpace(A, B, C, D, None)
        T.mimo_ss1c = StateSpace(A, B, C, D, 0)

        # Two input, two output discrete time system
        T.mimo_ss1d = StateSpace(A, B, C, D, 0.1)

        # Same system, but with a different sampling time
        T.mimo_ss2d = StateSpace(A, B, C, D, 0.2)

        # Single input, single output continuus and discrete transfer function
        T.siso_tf1 = TransferFunction([1, 1], [1, 2, 1], None)
        T.siso_tf1c = TransferFunction([1, 1], [1, 2, 1], 0)
        T.siso_tf1d = TransferFunction([1, 1], [1, 2, 1], 0.1)
        T.siso_tf2d = TransferFunction([1, 1], [1, 2, 1], 0.2)
        T.siso_tf3d = TransferFunction([1, 1], [1, 2, 1], True)

        return T
예제 #12
0
    def test_size_mismatch(self):
        sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10))

        # Different number of inputs
        sys2 = FRD(ct.rss(3, 1, 2), np.logspace(-1, 1, 10))
        self.assertRaises(ValueError, FRD.__add__, sys1, sys2)

        # Different number of outputs
        sys2 = FRD(ct.rss(3, 2, 1), np.logspace(-1, 1, 10))
        self.assertRaises(ValueError, FRD.__add__, sys1, sys2)

        # Inputs and outputs don't match
        self.assertRaises(ValueError, FRD.__mul__, sys2, sys1)

        # Feedback mismatch
        self.assertRaises(ValueError, FRD.feedback, sys2, sys1)
예제 #13
0
def test_lqe_discrete():
    """Test overloading of lqe operator for discrete time systems"""
    csys = ct.rss(2, 1, 1)
    dsys = ct.drss(2, 1, 1)
    Q = np.eye(1)
    R = np.eye(1)

    # Calling with a system versus explicit A, B should be the sam
    K_csys, S_csys, E_csys = ct.lqe(csys, Q, R)
    K_expl, S_expl, E_expl = ct.lqe(csys.A, csys.B, csys.C, Q, R)
    np.testing.assert_almost_equal(K_csys, K_expl)
    np.testing.assert_almost_equal(S_csys, S_expl)
    np.testing.assert_almost_equal(E_csys, E_expl)

    # Calling lqe() with a discrete time system should call dlqe()
    K_lqe, S_lqe, E_lqe = ct.lqe(dsys, Q, R)
    K_dlqe, S_dlqe, E_dlqe = ct.dlqe(dsys, Q, R)
    np.testing.assert_almost_equal(K_lqe, K_dlqe)
    np.testing.assert_almost_equal(S_lqe, S_dlqe)
    np.testing.assert_almost_equal(E_lqe, E_dlqe)

    # Calling lqe() with no timebase should call lqe()
    asys = ct.ss(csys.A, csys.B, csys.C, csys.D, dt=None)
    K_asys, S_asys, E_asys = ct.lqe(asys, Q, R)
    K_expl, S_expl, E_expl = ct.lqe(csys.A, csys.B, csys.C, Q, R)
    np.testing.assert_almost_equal(K_asys, K_expl)
    np.testing.assert_almost_equal(S_asys, S_expl)
    np.testing.assert_almost_equal(E_asys, E_expl)

    # Calling dlqe() with a continuous time system should raise an error
    with pytest.raises(ControlArgument, match="called with a continuous"):
        K, S, E = ct.dlqe(csys, Q, R)
예제 #14
0
def test_interconnect_exceptions():
    # First make sure the docstring example works
    P = ct.tf2io(ct.tf(1, [1, 0]), input='u', output='y')
    C = ct.tf2io(ct.tf(10, [1, 1]), input='e', output='u')
    sumblk = ct.summing_junction(inputs=['r', '-y'], output='e')
    T = ct.interconnect((P, C, sumblk), input='r', output='y')
    assert (T.ninputs, T.noutputs, T.nstates) == (1, 1, 2)

    # Unrecognized arguments
    # LinearIOSystem
    with pytest.raises(TypeError, match="unknown parameter"):
        P = ct.LinearIOSystem(ct.rss(2, 1, 1), output_name='y')

    # Interconnect
    with pytest.raises(TypeError, match="unknown parameter"):
        T = ct.interconnect((P, C, sumblk), input_name='r', output='y')

    # Interconnected system
    with pytest.raises(TypeError, match="unknown parameter"):
        T = ct.InterconnectedSystem((P, C, sumblk), input_name='r', output='y')

    # NonlinearIOSytem
    with pytest.raises(TypeError, match="unknown parameter"):
        nlios =  ct.NonlinearIOSystem(
            None, lambda t, x, u, params: u*u, input_count=1, output_count=1)

    # Summing junction
    with pytest.raises(TypeError, match="input specification is required"):
        sumblk = ct.summing_junction()

    with pytest.raises(TypeError, match="unknown parameter"):
        sumblk = ct.summing_junction(input_count=2, output_count=2)
예제 #15
0
    def test_size_mismatch(self):
        sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10))

        # Different number of inputs
        sys2 = FRD(ct.rss(3, 1, 2), np.logspace(-1, 1, 10))
        self.assertRaises(ValueError, FRD.__add__, sys1, sys2)

        # Different number of outputs
        sys2 = FRD(ct.rss(3, 2, 1), np.logspace(-1, 1, 10))
        self.assertRaises(ValueError, FRD.__add__, sys1, sys2)

        # Inputs and outputs don't match
        self.assertRaises(ValueError, FRD.__mul__, sys2, sys1)

        # Feedback mismatch
        self.assertRaises(ValueError, FRD.feedback, sys2, sys1)
예제 #16
0
def test_named_ss():
    # Create a system to play with
    sys = ct.rss(2, 2, 2)
    assert sys.input_labels == ['u[0]', 'u[1]']
    assert sys.output_labels == ['y[0]', 'y[1]']
    assert sys.state_labels == ['x[0]', 'x[1]']

    # Get the state matrices for later use
    A, B, C, D = sys.A, sys.B, sys.C, sys.D

    # Set up a named state space systems with default names
    ct.namedio.NamedIOSystem._idCounter = 0
    sys = ct.ss(A, B, C, D)
    assert sys.name == 'sys[0]'
    assert sys.input_labels == ['u[0]', 'u[1]']
    assert sys.output_labels == ['y[0]', 'y[1]']
    assert sys.state_labels == ['x[0]', 'x[1]']
    assert repr(sys) == \
        "<LinearIOSystem:sys[0]:['u[0]', 'u[1]']->['y[0]', 'y[1]']>"

    # Pass the names as arguments
    sys = ct.ss(A,
                B,
                C,
                D,
                name='system',
                inputs=['u1', 'u2'],
                outputs=['y1', 'y2'],
                states=['x1', 'x2'])
    assert sys.name == 'system'
    assert ct.namedio.NamedIOSystem._idCounter == 1
    assert sys.input_labels == ['u1', 'u2']
    assert sys.output_labels == ['y1', 'y2']
    assert sys.state_labels == ['x1', 'x2']
    assert repr(sys) == \
        "<LinearIOSystem:system:['u1', 'u2']->['y1', 'y2']>"

    # Do the same with rss
    sys = ct.rss(['x1', 'x2', 'x3'], ['y1', 'y2'], 'u1', name='random')
    assert sys.name == 'random'
    assert ct.namedio.NamedIOSystem._idCounter == 1
    assert sys.input_labels == ['u1']
    assert sys.output_labels == ['y1', 'y2']
    assert sys.state_labels == ['x1', 'x2', 'x3']
    assert repr(sys) == \
        "<LinearIOSystem:random:['u1']->['y1', 'y2']>"
예제 #17
0
def test_nyquist_exceptions():
    # MIMO not implemented
    sys = ct.rss(2, 2, 2)
    with pytest.raises(ct.exception.ControlMIMONotImplemented,
                       match="only supports SISO"):
        ct.nyquist_plot(sys)

    # Legacy keywords for arrow size
    sys = ct.rss(2, 1, 1)
    with pytest.warns(FutureWarning, match="use `arrow_size` instead"):
        ct.nyquist_plot(sys, arrow_width=8, arrow_length=6)

    # Discrete time system sampled above Nyquist frequency
    sys = ct.drss(2, 1, 1)
    sys.dt = 0.01
    with pytest.warns(UserWarning, match="above Nyquist"):
        ct.nyquist_plot(sys, np.logspace(-2, 3))
예제 #18
0
def test_string_inputoutput():
    # regression test for gh-692
    P1 = ct.rss(2, 1, 1)
    P1_iosys = ct.LinearIOSystem(P1, inputs='u1', outputs='y1')
    P2 = ct.rss(2, 1, 1)
    P2_iosys = ct.LinearIOSystem(P2, inputs='y1', outputs='y2')

    P_s1 = ct.interconnect([P1_iosys, P2_iosys], inputs='u1', outputs=['y2'])
    assert P_s1.input_index == {'u1' : 0}

    P_s2 = ct.interconnect([P1_iosys, P2_iosys], input='u1', outputs=['y2'])
    assert P_s2.input_index == {'u1' : 0}

    P_s1 = ct.interconnect([P1_iosys, P2_iosys], inputs=['u1'], outputs='y2')
    assert P_s1.output_index == {'y2' : 0}

    P_s2 = ct.interconnect([P1_iosys, P2_iosys], inputs=['u1'], output='y2')
    assert P_s2.output_index == {'y2' : 0}
예제 #19
0
    def test_printing(self):
        """Print SISO"""
        sys = ss2tf(rss(4, 1, 1))
        assert isinstance(str(sys), str)
        assert isinstance(sys._repr_latex_(), str)

        # SISO, discrete time
        sys = sample_system(sys, 1)
        assert isinstance(str(sys), str)
        assert isinstance(sys._repr_latex_(), str)
예제 #20
0
def test_frequency_response():
    # Create an SISO frequence response
    sys = ct.rss(2, 2, 2)
    omega = np.logspace(-2, 2, 20)
    resp = ct.frequency_response(sys, omega)
    eval = sys(omega * 1j)

    # Make sure we get the right answers in various ways
    np.testing.assert_equal(resp.magnitude, np.abs(eval))
    np.testing.assert_equal(resp.phase, np.angle(eval))
    np.testing.assert_equal(resp.omega, omega)

    # Make sure that we can change the properties of the response
    sys = ct.rss(2, 1, 1)
    resp_default = ct.frequency_response(sys, omega)
    mag_default, phase_default, omega_default = resp_default
    assert mag_default.ndim == 1
    assert phase_default.ndim == 1
    assert omega_default.ndim == 1
    assert mag_default.shape[0] == omega_default.shape[0]
    assert phase_default.shape[0] == omega_default.shape[0]

    resp_nosqueeze = ct.frequency_response(sys, omega, squeeze=False)
    mag_nosqueeze, phase_nosqueeze, omega_nosqueeze = resp_nosqueeze
    assert mag_nosqueeze.ndim == 3
    assert phase_nosqueeze.ndim == 3
    assert omega_nosqueeze.ndim == 1
    assert mag_nosqueeze.shape[2] == omega_nosqueeze.shape[0]
    assert phase_nosqueeze.shape[2] == omega_nosqueeze.shape[0]

    # Try changing the response
    resp_def_nosq = resp_default(squeeze=False)
    mag_def_nosq, phase_def_nosq, omega_def_nosq = resp_def_nosq
    assert mag_def_nosq.shape == mag_nosqueeze.shape
    assert phase_def_nosq.shape == phase_nosqueeze.shape
    assert omega_def_nosq.shape == omega_nosqueeze.shape

    resp_nosq_sq = resp_nosqueeze(squeeze=True)
    mag_nosq_sq, phase_nosq_sq, omega_nosq_sq = resp_nosq_sq
    assert mag_nosq_sq.shape == mag_default.shape
    assert phase_nosq_sq.shape == phase_default.shape
    assert omega_nosq_sq.shape == omega_default.shape
예제 #21
0
    def test_lqr_call_format(self, cdlqr):
        # Create a random state space system for testing
        sys = rss(2, 3, 2)
        sys.dt = None  # treat as either continuous or discrete time

        # Weighting matrices
        Q = np.eye(sys.nstates)
        R = np.eye(sys.ninputs)
        N = np.zeros((sys.nstates, sys.ninputs))

        # Standard calling format
        Kref, Sref, Eref = cdlqr(sys.A, sys.B, Q, R)

        # Call with system instead of matricees
        K, S, E = cdlqr(sys, Q, R)
        np.testing.assert_array_almost_equal(Kref, K)
        np.testing.assert_array_almost_equal(Sref, S)
        np.testing.assert_array_almost_equal(Eref, E)

        # Pass a cross-weighting matrix
        K, S, E = cdlqr(sys, Q, R, N)
        np.testing.assert_array_almost_equal(Kref, K)
        np.testing.assert_array_almost_equal(Sref, S)
        np.testing.assert_array_almost_equal(Eref, E)

        # Inconsistent system dimensions
        with pytest.raises(ct.ControlDimension, match="Incompatible dimen"):
            K, S, E = cdlqr(sys.A, sys.C, Q, R)

        # Incorrect covariance matrix dimensions
        with pytest.raises(ct.ControlDimension, match="Q must be a square"):
            K, S, E = cdlqr(sys.A, sys.B, sys.C, R, Q)

        # Too few input arguments
        with pytest.raises(ct.ControlArgument, match="not enough input"):
            K, S, E = cdlqr(sys.A, sys.B)

        # First argument is the wrong type (use SISO for non-slycot tests)
        sys_tf = tf(rss(3, 1, 1))
        sys_tf.dt = None  # treat as either continuous or discrete time
        with pytest.raises(ct.ControlArgument, match="LTI system must be"):
            K, S, E = cdlqr(sys_tf, Q, R)
예제 #22
0
    def test_size_mismatch(self):
        """Test size mismacht"""
        sys1 = ss2tf(rss(2, 2, 2))

        # Different number of inputs
        sys2 = ss2tf(rss(3, 1, 2))
        with pytest.raises(ValueError):
            TransferFunction.__add__(sys1, sys2)

        # Different number of outputs
        sys2 = ss2tf(rss(3, 2, 1))
        with pytest.raises(ValueError):
            TransferFunction.__add__(sys1, sys2)

        # Inputs and outputs don't match
        with pytest.raises(ValueError):
            TransferFunction.__mul__(sys2, sys1)

        # Feedback mismatch (MIMO not implemented)
        with pytest.raises(NotImplementedError):
            TransferFunction.feedback(sys2, sys1)
예제 #23
0
    def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape):
        # Set defaults to match release 0.8.4
        ct.config.use_legacy_defaults('0.8.4')
        ct.config.use_numpy_matrix(False)

        # Generate system, time, and input vectors
        sys = ct.rss(nstate, nout, ninp, strictly_proper=True)
        tvec = np.linspace(0, 1, 8)
        uvec =np.ones((sys.ninputs, 1)) @ np.reshape(np.sin(tvec), (1, 8))

        _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze)
        assert yvec.shape == shape
예제 #24
0
def test_lqe_call_format(cdlqe):
    # Create a random state space system for testing
    sys = rss(4, 3, 2)
    sys.dt = None  # treat as either continuous or discrete time

    # Covariance matrices
    Q = np.eye(sys.ninputs)
    R = np.eye(sys.noutputs)
    N = np.zeros((sys.ninputs, sys.noutputs))

    # Standard calling format
    Lref, Pref, Eref = cdlqe(sys.A, sys.B, sys.C, Q, R)

    # Call with system instead of matricees
    L, P, E = cdlqe(sys, Q, R)
    np.testing.assert_almost_equal(Lref, L)
    np.testing.assert_almost_equal(Pref, P)
    np.testing.assert_almost_equal(Eref, E)

    # Make sure we get an error if we specify N
    with pytest.raises(ct.ControlNotImplemented):
        L, P, E = cdlqe(sys, Q, R, N)

    # Inconsistent system dimensions
    with pytest.raises(ct.ControlDimension, match="Incompatible"):
        L, P, E = cdlqe(sys.A, sys.C, sys.B, Q, R)

    # Incorrect covariance matrix dimensions
    with pytest.raises(ct.ControlDimension, match="Incompatible"):
        L, P, E = cdlqe(sys.A, sys.B, sys.C, R, Q)

    # Too few input arguments
    with pytest.raises(ct.ControlArgument, match="not enough input"):
        L, P, E = cdlqe(sys.A, sys.C)

    # First argument is the wrong type (use SISO for non-slycot tests)
    sys_tf = tf(rss(3, 1, 1))
    sys_tf.dt = None  # treat as either continuous or discrete time
    with pytest.raises(ct.ControlArgument, match="LTI system must be"):
        L, P, E = cdlqe(sys_tf, Q, R)
예제 #25
0
def test_mpc_iosystem_continuous():
    # Create a random state space system
    sys = ct.rss(2, 1, 1)
    T, _ = ct.step_response(sys)

    # provide penalties on the system signals
    Q = np.eye(sys.nstates)
    R = np.eye(sys.ninputs)
    cost = opt.quadratic_cost(sys, Q, R)

    # Continuous time MPC controller not implemented
    with pytest.raises(NotImplementedError):
        ctrl = opt.create_mpc_iosystem(sys, T, cost)
예제 #26
0
    def test_statefbk_errors(self):
        sys = ct.rss(4, 4, 2, strictly_proper=True)
        K, _, _ = ct.lqr(sys, np.eye(sys.nstates), np.eye(sys.ninputs))

        with pytest.raises(ControlArgument, match="must be I/O system"):
            sys_tf = ct.tf([1], [1, 1])
            ctrl, clsys = ct.create_statefbk_iosystem(sys_tf, K)

        with pytest.raises(ControlArgument, match="output size must match"):
            est = ct.rss(3, 3, 2)
            ctrl, clsys = ct.create_statefbk_iosystem(sys, K, estimator=est)

        with pytest.raises(ControlArgument, match="must be the full state"):
            sys_nf = ct.rss(4, 3, 2, strictly_proper=True)
            ctrl, clsys = ct.create_statefbk_iosystem(sys_nf, K)

        with pytest.raises(ControlArgument, match="gain must be an array"):
            ctrl, clsys = ct.create_statefbk_iosystem(sys, "bad argument")

        with pytest.raises(ControlArgument, match="unknown type"):
            ctrl, clsys = ct.create_statefbk_iosystem(sys, K, type=1)

        # Errors involving integral action
        C_int = np.eye(2, 4)
        K_int, _, _ = ct.lqr(sys,
                             np.eye(sys.nstates + C_int.shape[0]),
                             np.eye(sys.ninputs),
                             integral_action=C_int)

        with pytest.raises(ControlArgument, match="must pass an array"):
            ctrl, clsys = ct.create_statefbk_iosystem(
                sys, K_int, integral_action="bad argument")

        with pytest.raises(ControlArgument, match="must be an array of size"):
            ctrl, clsys = ct.create_statefbk_iosystem(sys,
                                                      K,
                                                      integral_action=C_int)
예제 #27
0
    def test_forced_response_legacy(self):
        # Define a system for testing
        sys = ct.rss(2, 1, 1)
        T = np.linspace(0, 10, 10)
        U = np.sin(T)
        """Make sure that legacy version of forced_response works"""
        ct.config.use_legacy_defaults("0.8.4")
        # forced_response returns x by default
        t, y = ct.step_response(sys, T)
        t, y, x = ct.forced_response(sys, T, U)

        ct.config.use_legacy_defaults("0.9.0")
        # forced_response returns input/output by default
        t, y = ct.step_response(sys, T)
        t, y = ct.forced_response(sys, T, U)
        t, y, x = ct.forced_response(sys, T, U, return_x=True)
예제 #28
0
def test_convert_to_statespace():
    # Set up the initial system
    sys = ct.tf(ct.rss(2, 1, 1))

    # Make sure we can rename system name, inputs, outputs
    sys_new = ct.ss(sys, inputs='u', outputs='y', name='new')
    assert sys_new.name == 'new'
    assert sys_new.input_labels == ['u']
    assert sys_new.output_labels == ['y']

    # Try specifying the state names (via low level test)
    with pytest.warns(UserWarning, match="non-unique state space realization"):
        sys_new = ct.ss(sys, inputs='u', outputs='y', states=['x1', 'x2'])
        assert sys_new.input_labels == ['u']
        assert sys_new.output_labels == ['y']
        assert sys_new.state_labels == ['x1', 'x2']
예제 #29
0
    def testMinrealBrute(self):

        # depending on the seed and minreal performance, a number of
        # reductions is produced. If random gen or minreal change, this
        # will be likely to fail
        nreductions = 0

        for n, m, p in permutations(range(1, 6), 3):
            s = rss(n, p, m)
            sr = s.minreal()
            if s.states > sr.states:
                nreductions += 1
            else:
                # Check to make sure that poles and zeros match

                # For poles, just look at eigenvalues of A
                np.testing.assert_array_almost_equal(np.sort(eigvals(s.A)),
                                                     np.sort(eigvals(sr.A)))

                # For zeros, need to extract SISO systems
                for i in range(m):
                    for j in range(p):
                        # Extract SISO dynamixs from input i to output j
                        s1 = ss(s.A, s.B[:, i], s.C[j, :], s.D[j, i])
                        s2 = ss(sr.A, sr.B[:, i], sr.C[j, :], sr.D[j, i])

                        # Check that the zeros match
                        # Note: sorting doesn't work => have to do the hard way
                        z1 = zero(s1)
                        z2 = zero(s2)

                        # Start by making sure we have the same # of zeros
                        assert len(z1) == len(z2)

                        # Make sure all zeros in s1 are in s2
                        for z in z1:
                            # Find the closest zero TODO: find proper bounds
                            assert min(abs(z2 - z)) <= 1e-7

                        # Make sure all zeros in s2 are in s1
                        for z in z2:
                            # Find the closest zero
                            assert min(abs(z1 - z)) <= 1e-7

        # Make sure that the number of systems reduced is as expected
        # (Need to update this number if you change the seed at top of file)
        assert nreductions == 2
예제 #30
0
def test_linestyle_checks():
    sys = ct.rss(2, 1, 1)

    # Things that should work
    ct.nyquist_plot(sys, primary_style=['-', '-'], mirror_style=['-', '-'])
    ct.nyquist_plot(sys, mirror_style=None)

    with pytest.raises(ValueError, match="invalid 'primary_style'"):
        ct.nyquist_plot(sys, primary_style=False)

    with pytest.raises(ValueError, match="invalid 'mirror_style'"):
        ct.nyquist_plot(sys, mirror_style=0.2)

    # If only one line style is given use, the default value for the other
    # TODO: for now, just make sure the signature works; no correct check yet
    with pytest.warns(PendingDeprecationWarning, match="single string"):
        ct.nyquist_plot(sys, primary_style=':', mirror_style='-.')
예제 #31
0
def test_trdata_labels():
    # Create an I/O system with labels
    sys = ct.rss(4, 3, 2)
    iosys = ct.LinearIOSystem(sys)

    T = np.linspace(1, 10, 10)
    U = [np.sin(T), np.cos(T)]

    # Create a response
    response = ct.input_output_response(iosys, T, U)

    # Make sure the labels got created
    np.testing.assert_equal(
        response.output_labels, ["y[%d]" % i for i in range(sys.noutputs)])
    np.testing.assert_equal(
        response.state_labels, ["x[%d]" % i for i in range(sys.nstates)])
    np.testing.assert_equal(
        response.input_labels, ["u[%d]" % i for i in range(sys.ninputs)])
예제 #32
0
    def test_operator_conversion(self):
        sys_tf = ct.tf([1], [1, 2, 1])
        frd_tf = FRD(sys_tf, np.logspace(-1, 1, 10))
        frd_2 = FRD(2 * np.ones(10), np.logspace(-1, 1, 10))

        # Make sure that we can add, multiply, and feedback constants
        sys_add = frd_tf + 2
        chk_add = frd_tf + frd_2
        np.testing.assert_array_almost_equal(sys_add.omega, chk_add.omega)
        np.testing.assert_array_almost_equal(sys_add.fresp, chk_add.fresp)

        sys_radd = 2 + frd_tf
        chk_radd = frd_2 + frd_tf
        np.testing.assert_array_almost_equal(sys_radd.omega, chk_radd.omega)
        np.testing.assert_array_almost_equal(sys_radd.fresp, chk_radd.fresp)

        sys_sub = frd_tf - 2
        chk_sub = frd_tf - frd_2
        np.testing.assert_array_almost_equal(sys_sub.omega, chk_sub.omega)
        np.testing.assert_array_almost_equal(sys_sub.fresp, chk_sub.fresp)

        sys_rsub = 2 - frd_tf
        chk_rsub = frd_2 - frd_tf
        np.testing.assert_array_almost_equal(sys_rsub.omega, chk_rsub.omega)
        np.testing.assert_array_almost_equal(sys_rsub.fresp, chk_rsub.fresp)

        sys_mul = frd_tf * 2
        chk_mul = frd_tf * frd_2
        np.testing.assert_array_almost_equal(sys_mul.omega, chk_mul.omega)
        np.testing.assert_array_almost_equal(sys_mul.fresp, chk_mul.fresp)

        sys_rmul = 2 * frd_tf
        chk_rmul = frd_2 * frd_tf
        np.testing.assert_array_almost_equal(sys_rmul.omega, chk_rmul.omega)
        np.testing.assert_array_almost_equal(sys_rmul.fresp, chk_rmul.fresp)

        sys_rdiv = 2 / frd_tf
        chk_rdiv = frd_2 / frd_tf
        np.testing.assert_array_almost_equal(sys_rdiv.omega, chk_rdiv.omega)
        np.testing.assert_array_almost_equal(sys_rdiv.fresp, chk_rdiv.fresp)

        sys_pow = frd_tf**2
        chk_pow = FRD(sys_tf**2, np.logspace(-1, 1, 10))
        np.testing.assert_array_almost_equal(sys_pow.omega, chk_pow.omega)
        np.testing.assert_array_almost_equal(sys_pow.fresp, chk_pow.fresp)

        sys_pow = frd_tf**-2
        chk_pow = FRD(sys_tf**-2, np.logspace(-1, 1, 10))
        np.testing.assert_array_almost_equal(sys_pow.omega, chk_pow.omega)
        np.testing.assert_array_almost_equal(sys_pow.fresp, chk_pow.fresp)

        # Assertion error if we try to raise to a non-integer power
        self.assertRaises(ValueError, FRD.__pow__, frd_tf, 0.5)

        # Selected testing on transfer function conversion
        sys_add = frd_2 + sys_tf
        chk_add = frd_2 + frd_tf
        np.testing.assert_array_almost_equal(sys_add.omega, chk_add.omega)
        np.testing.assert_array_almost_equal(sys_add.fresp, chk_add.fresp)

        # Input/output mismatch size mismatch in  rmul
        sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10))
        self.assertRaises(ValueError, FRD.__rmul__, frd_2, sys1)

        # Make sure conversion of something random generates exception
        self.assertRaises(TypeError,  FRD.__add__, frd_tf, 'string')