示例#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
示例#2
0
def stepinfo(sys,
             display=False,
             T=None,
             SettlingTimeThreshold=0.05,
             RiseTimeLimits=(0.1, 0.9)):

    T_max = get_T_max([sys], T=None, N=200)

    if T is None:
        if ctl.isctime(sys):
            T = np.linspace(0, T_max, 200)
        else:
            # For discrete time, use integers
            T = np.arange(0, T_max, sys.dt)

    info = ctl.step_info(sys,
                         T,
                         SettlingTimeThreshold=0.05,
                         RiseTimeLimits=(0.1, 0.9))

    if display == True:
        for keys, values in info.items():
            print("{} :\t{:.5f}".format(keys, values))
        print()

    return info
 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)
示例#4
0
def step(tf_list=[], N=100, T=None, name=None):

    data = []
    T_max = get_T_max(tf_list, T=T, N=N)

    for index, tf in enumerate(tf_list):
        if ctl.isctime(tf):
            T = np.linspace(0, T_max, N)
            line_shape = "linear"
        else:
            T = np.arange(0, T_max, tf.dt)
            line_shape = "hv"

        t, y = ctl.step_response(tf, T=T)
        tf_name = "tf {}".format(index + 1)
        data.append({
            "x": np.ravel(t),
            "y": np.ravel(y),
            "name": tf_name,
            "mode": "lines",
            "showlegend": False,
            "line_shape": line_shape
        })

    layout = default_layout("time (s)", "response", name)
    fig = go.Figure(data, layout=layout)
    return fig
示例#5
0
    def test_connect(self):
        # Define a couple of (linear) systems to interconnection
        linsys1 = self.siso_linsys
        iosys1 = ios.LinearIOSystem(linsys1)
        linsys2 = self.siso_linsys
        iosys2 = ios.LinearIOSystem(linsys2)

        # Connect systems in different ways and compare to StateSpace
        linsys_series = linsys2 * linsys1
        iosys_series = ios.InterconnectedSystem(
            (iosys1, iosys2),   # systems
            ((1, 0),),          # interconnection (series)
            0,                  # input = first system
            1                   # output = second system
        )

        # Run a simulation and compare to linear response
        T, U = self.T, self.U
        X0 = np.concatenate((self.X0, self.X0))
        ios_t, ios_y, ios_x = ios.input_output_response(
            iosys_series, T, U, X0, return_x=True)
        lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0)
        np.testing.assert_array_almost_equal(lti_t, ios_t)
        np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)

        # Connect systems with different timebases
        linsys2c = self.siso_linsys
        linsys2c.dt = 0         # Reset the timebase
        iosys2c = ios.LinearIOSystem(linsys2c)
        iosys_series = ios.InterconnectedSystem(
            (iosys1, iosys2c),   # systems
            ((1, 0),),          # interconnection (series)
            0,                  # input = first system
            1                   # output = second system
        )
        self.assertTrue(ct.isctime(iosys_series, strict=True))
        ios_t, ios_y, ios_x = ios.input_output_response(
            iosys_series, T, U, X0, return_x=True)
        lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0)
        np.testing.assert_array_almost_equal(lti_t, ios_t)
        np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)

        # Feedback interconnection
        linsys_feedback = ct.feedback(linsys1, linsys2)
        iosys_feedback = ios.InterconnectedSystem(
            (iosys1, iosys2),   # systems
            ((1, 0),            # input of sys2 = output of sys1
             (0, (1, 0, -1))),  # input of sys1 = -output of sys2
            0,                  # input = first system
            0                   # output = first system
        )
        ios_t, ios_y, ios_x = ios.input_output_response(
            iosys_feedback, T, U, X0, return_x=True)
        lti_t, lti_y, lti_x = ct.forced_response(linsys_feedback, T, U, X0)
        np.testing.assert_array_almost_equal(lti_t, ios_t)
        np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)
示例#6
0
def damp(sys):

    is_continuous = ctl.isctime(sys)

    for pole in sys.pole():
        pole = pole.astype(
            complex
        )  # WTF: the python control "damp" function is buggy due to this missing cast !

        if is_continuous:
            pole_continuous = pole
        else:
            pole_continuous = np.log(pole) / sys.dt

        wn = np.abs(pole_continuous)
        m = -np.real(pole_continuous) / wn
        print("poles {:.3f} : wn={:.3f} rad/s, m= {:.3f}".format(pole, wn, m))
示例#7
0
def get_T_max(tf_list, T=None, N=100):
    """ Get Time vector """
    if T is None:
        T_max = 100000
        for tf in tf_list:

            if ctl.isctime(tf):
                ss = ctl.tf2ss(tf)
                T_temp = _default_response_times(ss.A, N)
                t_max_temp = T_temp[-1]
            else:
                t_max_temp = N * tf.dt

            if t_max_temp < T_max:
                T_max = t_max_temp
    else:
        T_max = T[-1]
    return T_max
示例#8
0
    def test_time_vector(self, tsystem, fun, squeeze, matarrayout):
        """Test time vector handling and correct output convention

        gh-239, gh-295
        """
        sys = tsystem.sys

        kw = {}
        if hasattr(tsystem, "t"):
            t = tsystem.t
            kw['T'] = t
            if fun == forced_response:
                kw['U'] = np.vstack([np.sin(t) for i in range(sys.ninputs)])
        elif fun == forced_response and isctime(sys, strict=True):
            pytest.skip("No continuous forced_response without time vector.")
        if hasattr(sys, "nstates"):
            kw['X0'] = np.arange(sys.nstates) + 1
        if sys.ninputs > 1 and fun in [step_response, impulse_response]:
            kw['input'] = 1
        if squeeze is not None:
            kw['squeeze'] = squeeze

        out = fun(sys, **kw)
        tout, yout = out[:2]

        assert tout.ndim == 1
        if hasattr(tsystem, 't'):
            # tout should always match t, which has shape (n, )
            np.testing.assert_allclose(tout, tsystem.t)
        elif fun == forced_response and sys.dt in [None, True]:
            np.testing.assert_allclose(np.diff(tout), 1.)

        if squeeze is False or not sys.issiso():
            assert yout.shape[0] == sys.noutputs
            assert yout.shape[-1] == tout.shape[0]
        else:
            assert yout.shape == tout.shape

        if sys.isdtime(strict=True) and sys.dt is not True and not \
                np.isclose(sys.dt, 0.5):
            kw['T'] = np.arange(0, 5, 0.5)  # incompatible timebase
            with pytest.raises(ValueError):
                fun(sys, **kw)
示例#9
0
    def __init__(self,
                 linsys,
                 inputs=None,
                 outputs=None,
                 states=None,
                 name=None):
        """Define a flat system from a SISO LTI system.

        Given a reachable, single-input/single-output, linear time-invariant
        system, create a differentially flat system representation.

        """
        # Make sure we can handle the system
        if (not control.isctime(linsys)):
            raise control.ControlNotImplemented(
                "requires continuous time, linear control system")
        elif (not control.issiso(linsys)):
            raise control.ControlNotImplemented(
                "only single input, single output systems are supported")

        # Initialize the object as a LinearIO system
        LinearIOSystem.__init__(self,
                                linsys,
                                inputs=inputs,
                                outputs=outputs,
                                states=states,
                                name=name)

        # Find the transformation to chain of integrators form
        # Note: store all array as ndarray, not matrix
        zsys, Tr = control.reachable_form(linsys)
        Tr = np.array(Tr[::-1, ::])  # flip rows

        # Extract the information that we need
        self.F = np.array(zsys.A[0, ::-1])  # input function coeffs
        self.T = Tr  # state space transformation
        self.Tinv = np.linalg.inv(Tr)  # compute inverse once

        # Compute the flat output variable z = C x
        Cfz = np.zeros(np.shape(linsys.C))
        Cfz[0, 0] = 1
        self.Cf = Cfz @ Tr
示例#10
0
    def __init__(self, sys):
        # Make sure we can handle the system
        if (not control.isctime(sys)):
            raise control.ControlNotImplemented(
                "requires continuous time, linear control system")
        elif (not control.issiso(sys)):
            raise control.ControlNotImplemented(
                "only single input, single output systems are supported")

        # Initialize the object and store system matrices
        FlatSystem.__init__(self, sys.states, sys.inputs)
        self.A = sys.A
        self.B = sys.B

        # Find the transformation to bring the system into reachable form
        zsys, Tr = control.reachable_form(sys)
        self.F = zsys.A[0,:]            # input function coeffs
        self.T = Tr                     # state space transformation
        self.Tinv = np.linalg.inv(Tr)   # computer inverse once
        
        # Compute the flat output variable z = C x
        Cfz = np.zeros(np.shape(sys.C)); Cfz[0, -1] = 1
        self.C = Cfz * Tr
    def __init__(self, sys):
        # Make sure we can handle the system
        if (not control.isctime(sys)):
            raise control.ControlNotImplemented(
                "requires continuous time, linear control system")
        elif (not control.issiso(sys)):
            raise control.ControlNotImplemented(
                "only single input, single output systems are supported")

        # Initialize the object and store system matrices
        FlatSystem.__init__(self, sys.states, sys.inputs)
        self.A = sys.A
        self.B = sys.B

        # Find the transformation to bring the system into reachable form
        zsys, Tr = control.reachable_form(sys)
        self.F = zsys.A[0, :]  # input function coeffs
        self.T = Tr  # state space transformation
        self.Tinv = np.linalg.inv(Tr)  # computer inverse once

        # Compute the flat output variable z = C x
        Cfz = np.zeros(np.shape(sys.C))
        Cfz[0, -1] = 1
        self.C = Cfz * Tr
示例#12
0
    def testisctime(self, tsys):
        # Constant
        assert isctime(1)
        assert not isctime(1, strict=True)

        # State Space
        assert isctime(tsys.siso_ss1)
        assert not isctime(tsys.siso_ss1, strict=True)
        assert isctime(tsys.siso_ss1c)
        assert isctime(tsys.siso_ss1c, strict=True)
        assert not isctime(tsys.siso_ss1d)
        assert not isctime(tsys.siso_ss1d, strict=True)
        assert not isctime(tsys.siso_ss3d, strict=True)

        # Transfer Function
        assert isctime(tsys.siso_tf1)
        assert not isctime(tsys.siso_tf1, strict=True)
        assert isctime(tsys.siso_tf1c)
        assert isctime(tsys.siso_tf1c, strict=True)
        assert not isctime(tsys.siso_tf1d)
        assert not isctime(tsys.siso_tf1d, strict=True)
        assert not isctime(tsys.siso_tf3d, strict=True)
示例#13
0
def test_terminal_constraints(sys_args):
    """Test out the ability to handle terminal constraints"""
    # Create the system
    sys = ct.ss2io(ct.ss(*sys_args))

    # Shortest path to a point is a line
    Q = np.zeros((2, 2))
    R = np.eye(2)
    cost = opt.quadratic_cost(sys, Q, R)

    # Set up the terminal constraint to be the origin
    final_point = [opt.state_range_constraint(sys, [0, 0], [0, 0])]

    # Create the optimal control problem
    time = np.arange(0, 3, 1)
    optctrl = opt.OptimalControlProblem(
        sys, time, cost, terminal_constraints=final_point)

    # Find a path to the origin
    x0 = np.array([4, 3])
    res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True)
    t, u1, x1 = res.time, res.inputs, res.states

    # Bug prior to SciPy 1.6 will result in incorrect results
    if NumpyVersion(sp.__version__) < '1.6.0':
        pytest.xfail("SciPy 1.6 or higher required")

    np.testing.assert_almost_equal(x1[:,-1], 0, decimal=4)

    # Make sure it is a straight line
    Tf = time[-1]
    if ct.isctime(sys):
        # Continuous time is not that accurate on the input, so just skip test
        pass
    else:
        # Final point doesn't affect cost => don't need to test
        np.testing.assert_almost_equal(
            u1[:, 0:-1],
            np.kron((-x0/Tf).reshape((2, 1)), np.ones(time.shape))[:, 0:-1])
    np.testing.assert_allclose(
        x1, np.kron(x0.reshape((2, 1)), time[::-1]/Tf), atol=0.1, rtol=0.01)

    # Re-run using initial guess = optional and make sure nothing changes
    res = optctrl.compute_trajectory(x0, initial_guess=u1)
    np.testing.assert_almost_equal(res.inputs, u1)

    # Re-run using a basis function and see if we get the same answer
    res = opt.solve_ocp(sys, time, x0, cost, terminal_constraints=final_point,
                       basis=flat.BezierFamily(4, Tf))
    np.testing.assert_almost_equal(res.inputs, u1, decimal=2)

    # Impose some cost on the state, which should change the path
    Q = np.eye(2)
    R = np.eye(2) * 0.1
    cost = opt.quadratic_cost(sys, Q, R)
    optctrl = opt.OptimalControlProblem(
        sys, time, cost, terminal_constraints=final_point)

    # Turn off warning messages, since we sometimes don't get convergence
    with warnings.catch_warnings():
        warnings.filterwarnings(
            "ignore", message="unable to solve", category=UserWarning)
        # Find a path to the origin
        res = optctrl.compute_trajectory(
            x0, squeeze=True, return_x=True, initial_guess=u1)
        t, u2, x2 = res.time, res.inputs, res.states

        # Not all configurations are able to converge (?)
        if res.success:
            np.testing.assert_almost_equal(x2[:,-1], 0)

            # Make sure that it is *not* a straight line path
            assert np.any(np.abs(x2 - x1) > 0.1)
            assert np.any(np.abs(u2) > 1)       # Make sure next test is useful

        # Add some bounds on the inputs
        constraints = [opt.input_range_constraint(sys, [-1, -1], [1, 1])]
        optctrl = opt.OptimalControlProblem(
            sys, time, cost, constraints, terminal_constraints=final_point)
        res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True)
        t, u3, x3 = res.time, res.inputs, res.states

        # Check the answers only if we converged
        if res.success:
            np.testing.assert_almost_equal(x3[:,-1], 0, decimal=4)

            # Make sure we got a new path and didn't violate the constraints
            assert np.any(np.abs(x3 - x1) > 0.1)
            np.testing.assert_array_less(np.abs(u3), 1 + 1e-6)

    # Make sure that infeasible problems are handled sensibly
    x0 = np.array([10, 3])
    with pytest.warns(UserWarning, match="unable to solve"):
        res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True)
        assert not res.success
示例#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)
示例#15
0
    def __init__(self,
                 linsys,
                 inputs=None,
                 outputs=None,
                 states=None,
                 name=None):
        """Define a flat system from a SISO LTI system.

        Given a reachable, single-input/single-output, linear time-invariant
        system, create a differentially flat system representation.

        Parameters
        ----------
        linsys : StateSpace
            LTI StateSpace system to be converted
        inputs : int, list of str or None, optional
            Description of the system inputs.  This can be given as an integer
            count or as a list of strings that name the individual signals.
            If an integer count is specified, the names of the signal will be
            of the form `s[i]` (where `s` is one of `u`, `y`, or `x`).  If
            this parameter is not given or given as `None`, the relevant
            quantity will be determined when possible based on other
            information provided to functions using the system.
        outputs : int, list of str or None, optional
            Description of the system outputs.  Same format as `inputs`.
        states : int, list of str, or None, optional
            Description of the system states.  Same format as `inputs`.
        dt : None, True or float, optional
            System timebase.  None (default) indicates continuous
            time, True indicates discrete time with undefined sampling
            time, positive number is discrete time with specified
            sampling time.
        params : dict, optional
            Parameter values for the systems.  Passed to the evaluation
            functions for the system as default values, overriding internal
            defaults.
        name : string, optional
            System name (used for specifying signals)

        Returns
        -------
        iosys : LinearFlatSystem
            Linear system represented as an flat input/output system

        """
        # Make sure we can handle the system
        if (not control.isctime(linsys)):
            raise control.ControlNotImplemented(
                "requires continuous time, linear control system")
        elif (not control.issiso(linsys)):
            raise control.ControlNotImplemented(
                "only single input, single output systems are supported")

        # Initialize the object as a LinearIO system
        LinearIOSystem.__init__(self,
                                linsys,
                                inputs=inputs,
                                outputs=outputs,
                                states=states,
                                name=name)

        # Find the transformation to chain of integrators form
        # Note: store all array as ndarray, not matrix
        zsys, Tr = control.reachable_form(linsys)
        Tr = np.array(Tr[::-1, ::])  # flip rows

        # Extract the information that we need
        self.F = np.array(zsys.A[0, ::-1])  # input function coeffs
        self.T = Tr  # state space transformation
        self.Tinv = np.linalg.inv(Tr)  # compute inverse once

        # Compute the flat output variable z = C x
        Cfz = np.zeros(np.shape(linsys.C))
        Cfz[0, 0] = 1
        self.Cf = np.dot(Cfz, Tr)