Пример #1
0
def test_equality_constraints():
    """Test out the ability to handle equality constraints"""
    # Create the system (double integrator, continuous time)
    sys = ct.ss2io(ct.ss(np.zeros((2, 2)), np.eye(2), np.eye(2), 0))

    # 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)

    # Set up terminal constraints as a nonlinear constraint
    def final_point_eval(x, u):
        return x

    final_point = [(sp.optimize.NonlinearConstraint, final_point_eval, [0, 0],
                    [0, 0])]

    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, u2, x2 = res.time, res.inputs, res.states
    np.testing.assert_almost_equal(x2[:, -1], 0, decimal=4)
    np.testing.assert_almost_equal(u1, u2)
    np.testing.assert_almost_equal(x1, x2)

    # Try passing and unknown constraint type
    final_point = [(None, final_point_eval, [0, 0], [0, 0])]
    with pytest.raises(TypeError, match="unknown constraint type"):
        optctrl = opt.OptimalControlProblem(sys,
                                            time,
                                            cost,
                                            terminal_constraints=final_point)
        res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True)
Пример #2
0
def test_constraint_specification(constraint_list):
    sys = ct.ss2io(ct.ss([[1, 1], [0, 1]], [[1], [0.5]], np.eye(2), 0, 1))

    """Test out different forms of constraints on a simple problem"""
    # Parse out the constraint
    constraints = []
    for constraint_setup in constraint_list:
        if constraint_setup[0] in \
           (sp.optimize.LinearConstraint, sp.optimize.NonlinearConstraint):
            # No processing required
            constraints.append(constraint_setup)
        else:
            # Call the function in the first argument to set up the constraint
            constraints.append(constraint_setup[0](sys, *constraint_setup[1:]))

    # Quadratic state and input penalty
    Q = [[1, 0], [0, 1]]
    R = [[1]]
    cost = opt.quadratic_cost(sys, Q, R)

    # Create a model predictive controller system
    time = np.arange(0, 5, 1)
    optctrl = opt.OptimalControlProblem(sys, time, cost, constraints)

    # Compute optimal control and compare against MPT3 solution
    x0 = [4, 0]
    res = optctrl.compute_trajectory(x0, squeeze=True)
    t, u_openloop = res.time, res.inputs
    np.testing.assert_almost_equal(
        u_openloop, [-1, -1, 0.1393, 0.3361, -5.204e-16], decimal=3)
Пример #3
0
def test_discrete_lqr():
    # oscillator model defined in 2D
    # Source: https://www.mpt3.org/UI/RegulationProblem
    A = [[0.5403, -0.8415], [0.8415, 0.5403]]
    B = [[-0.4597], [0.8415]]
    C = [[1, 0]]
    D = [[0]]

    # Linear discrete-time model with sample time 1
    sys = ct.ss2io(ct.ss(A, B, C, D, 1))

    # Include weights on states/inputs
    Q = np.eye(2)
    R = 1
    K, S, E = ct.dlqr(A, B, Q, R)

    # Compute the integral and terminal cost
    integral_cost = opt.quadratic_cost(sys, Q, R)
    terminal_cost = opt.quadratic_cost(sys, S, None)

    # Solve the LQR problem
    lqr_sys = ct.ss2io(ct.ss(A - B @ K, B, C, D, 1))

    # Generate a simulation of the LQR controller
    time = np.arange(0, 5, 1)
    x0 = np.array([1, 1])
    _, _, lqr_x = ct.input_output_response(lqr_sys, time, 0, x0, return_x=True)

    # Use LQR input as initial guess to avoid convergence/precision issues
    lqr_u = np.array(-K @ lqr_x[0:time.size])  # convert from matrix

    # Formulate the optimal control problem and compute optimal trajectory
    optctrl = opt.OptimalControlProblem(sys,
                                        time,
                                        integral_cost,
                                        terminal_cost=terminal_cost,
                                        initial_guess=lqr_u)
    res1 = optctrl.compute_trajectory(x0, return_states=True)

    # Compare to make sure results are the same
    np.testing.assert_almost_equal(res1.inputs, lqr_u[0])
    np.testing.assert_almost_equal(res1.states, lqr_x)

    # Add state and input constraints
    trajectory_constraints = [
        (sp.optimize.LinearConstraint, np.eye(3), [-5, -5, -.5], [5, 5, 0.5]),
    ]

    # Re-solve
    res2 = opt.solve_ocp(sys,
                         time,
                         x0,
                         integral_cost,
                         trajectory_constraints,
                         terminal_cost=terminal_cost,
                         initial_guess=lqr_u)

    # Make sure we got a different solution
    assert np.any(np.abs(res1.inputs - res2.inputs) > 0.1)
def test_discrete_lqr():
    # oscillator model defined in 2D
    # Source: https://www.mpt3.org/UI/RegulationProblem
    A = [[0.5403, -0.8415], [0.8415, 0.5403]]
    B = [[-0.4597], [0.8415]]
    C = [[1, 0]]
    D = [[0]]

    # Linear discrete-time model with sample time 1
    sys = ct.ss2io(ct.ss(A, B, C, D, 1))

    # Include weights on states/inputs
    Q = np.eye(2)
    R = 1
    K, S, E = ct.lqr(A, B, Q, R)  # note: *continuous* time LQR

    # Compute the integral and terminal cost
    integral_cost = opt.quadratic_cost(sys, Q, R)
    terminal_cost = opt.quadratic_cost(sys, S, None)

    # Formulate finite horizon MPC problem
    time = np.arange(0, 5, 1)
    x0 = np.array([1, 1])
    optctrl = opt.OptimalControlProblem(sys,
                                        time,
                                        integral_cost,
                                        terminal_cost=terminal_cost)
    res1 = optctrl.compute_trajectory(x0, return_states=True)

    with pytest.xfail("discrete LQR not implemented"):
        # Result should match LQR
        K, S, E = ct.dlqr(A, B, Q, R)
        lqr_sys = ct.ss2io(ct.ss(A - B @ K, B, C, D, 1))
        _, _, lqr_x = ct.input_output_response(lqr_sys,
                                               time,
                                               0,
                                               x0,
                                               return_x=True)
        np.testing.assert_almost_equal(res1.states, lqr_x)

    # Add state and input constraints
    trajectory_constraints = [
        (sp.optimize.LinearConstraint, np.eye(3), [-10, -10, -1], [10, 10, 1]),
    ]

    # Re-solve
    res2 = opt.solve_ocp(sys,
                         time,
                         x0,
                         integral_cost,
                         constraints,
                         terminal_cost=terminal_cost)

    # Make sure we got a different solution
    assert np.any(np.abs(res1.inputs - res2.inputs) > 0.1)
Пример #5
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