def test_optimal_logging(capsys): """Test logging functions (mainly for code coverage)""" sys = ct.ss2io(ct.ss(np.eye(2), np.eye(2), np.eye(2), 0, 1)) # Set up the optimal control problem cost = opt.quadratic_cost(sys, 1, 1) state_constraint = opt.state_range_constraint(sys, [-np.inf, 1], [10, 1]) input_constraint = opt.input_range_constraint(sys, [-100, -100], [100, 100]) time = np.arange(0, 3, 1) x0 = [-1, 1] # Solve it, with logging turned on (with warning due to mixed constraints) with pytest.warns(sp.optimize.optimize.OptimizeWarning, match="Equality and inequality .* same element"): res = opt.solve_ocp(sys, time, x0, cost, input_constraint, terminal_cost=cost, terminal_constraints=state_constraint, log=True) # Make sure the output has info available only with logging turned on captured = capsys.readouterr() assert captured.out.find("process time") != -1
def time_steering_bezier_basis(nbasis, ntimes): # Set up costs and constriants Q = np.diag([.1, 10, .1]) # keep lateral error low R = np.diag([1, 1]) # minimize applied inputs cost = opt.quadratic_cost(vehicle, Q, R, x0=xf, u0=uf) constraints = [opt.input_range_constraint(vehicle, [0, -0.1], [20, 0.1])] terminal = [opt.state_range_constraint(vehicle, xf, xf)] # Set up horizon horizon = np.linspace(0, Tf, ntimes, endpoint=True) # Set up the optimal control problem res = opt.solve_ocp( vehicle, horizon, x0, cost, constraints, terminal_constraints=terminal, initial_guess=bend_left, basis=flat.BezierFamily(nbasis, T=Tf), # solve_ivp_kwargs={'atol': 1e-4, 'rtol': 1e-2}, minimize_method='trust-constr', minimize_options={'finite_diff_rel_step': 0.01}, # minimize_method='SLSQP', minimize_options={'eps': 0.01}, return_states=True, print_summary=False) t, u, x = res.time, res.inputs, res.states # Make sure we found a valid solution assert res.success
def time_steering_terminal_constraint(integrator_name, minimizer_name): # Get the integrator and minimizer parameters to use integrator = integrator_table[integrator_name] minimizer = minimizer_table[minimizer_name] # Input cost and terminal constraints R = np.diag([1, 1]) # minimize applied inputs cost = opt.quadratic_cost(vehicle, np.zeros((3, 3)), R, u0=uf) constraints = [opt.input_range_constraint(vehicle, [8, -0.1], [12, 0.1])] terminal = [opt.state_range_constraint(vehicle, xf, xf)] res = opt.solve_ocp( vehicle, horizon, x0, cost, constraints, terminal_constraints=terminal, initial_guess=bend_left, log=False, solve_ivp_method=integrator[0], solve_ivp_kwargs=integrator[1], minimize_method=minimizer[0], minimize_options=minimizer[1], ) # Only count this as a benchmark if we converged assert res.success
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)
def test_flat_cost_constr(self): # Double integrator system sys = ct.ss([[0, 1], [0, 0]], [[0], [1]], [[1, 0]], 0) flat_sys = fs.LinearFlatSystem(sys) # Define the endpoints of the trajectory x0 = [1, 0] u0 = [0] xf = [0, 0] uf = [0] Tf = 10 T = np.linspace(0, Tf, 500) # Find trajectory between initial and final conditions traj = fs.point_to_point(flat_sys, Tf, x0, u0, xf, uf, basis=fs.PolyFamily(8)) x, u = traj.eval(T) np.testing.assert_array_almost_equal(x0, x[:, 0]) np.testing.assert_array_almost_equal(u0, u[:, 0]) np.testing.assert_array_almost_equal(xf, x[:, -1]) np.testing.assert_array_almost_equal(uf, u[:, -1]) # Solve with a cost function timepts = np.linspace(0, Tf, 10) cost_fcn = opt.quadratic_cost(flat_sys, np.diag([0, 0]), 1, x0=xf, u0=uf) traj_cost = fs.point_to_point( flat_sys, timepts, x0, u0, xf, uf, cost=cost_fcn, basis=fs.PolyFamily(8), # initial_guess='lstsq', # minimize_kwargs={'method': 'trust-constr'} ) # Verify that the trajectory computation is correct x_cost, u_cost = traj_cost.eval(T) np.testing.assert_array_almost_equal(x0, x_cost[:, 0]) np.testing.assert_array_almost_equal(u0, u_cost[:, 0]) np.testing.assert_array_almost_equal(xf, x_cost[:, -1]) np.testing.assert_array_almost_equal(uf, u_cost[:, -1]) # Make sure that we got a different answer than before assert np.any(np.abs(x - x_cost) > 0.1) # Re-solve with constraint on the y deviation lb, ub = [-2, -0.1], [2, 0] lb, ub = [-2, np.min(x_cost[1]) * 0.95], [2, 1] constraints = [opt.state_range_constraint(flat_sys, lb, ub)] # Make sure that the previous solution violated at least one constraint assert np.any(x_cost[0, :] < lb[0]) or np.any(x_cost[0, :] > ub[0]) \ or np.any(x_cost[1, :] < lb[1]) or np.any(x_cost[1, :] > ub[1]) traj_const = fs.point_to_point( flat_sys, timepts, x0, u0, xf, uf, cost=cost_fcn, constraints=constraints, basis=fs.PolyFamily(8), ) # Verify that the trajectory computation is correct x_const, u_const = traj_const.eval(T) np.testing.assert_array_almost_equal(x0, x_const[:, 0]) np.testing.assert_array_almost_equal(u0, u_const[:, 0]) np.testing.assert_array_almost_equal(xf, x_const[:, -1]) np.testing.assert_array_almost_equal(uf, u_const[:, -1]) # Make sure that the solution respects the bounds (with some slop) for i in range(x_const.shape[0]): assert np.all(x_const[i] >= lb[i] * 1.02) assert np.all(x_const[i] <= ub[i] * 1.02) # Solve the same problem with a nonlinear constraint type nl_constraints = [(sp.optimize.NonlinearConstraint, lambda x, u: x, lb, ub)] traj_nlconst = fs.point_to_point( flat_sys, timepts, x0, u0, xf, uf, cost=cost_fcn, constraints=nl_constraints, basis=fs.PolyFamily(8), ) x_nlconst, u_nlconst = traj_nlconst.eval(T) np.testing.assert_almost_equal(x_const, x_nlconst) np.testing.assert_almost_equal(u_const, u_nlconst)
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
plot_results(t2, y2, u2, figure=2, yf=xf[0:2]) # # Approach 3: terminal constraints # # We can also remove the cost function on the state and replace it # with a terminal *constraint* on the state. If a solution is found, # it guarantees we get to exactly the final state. # print("Approach 3: terminal constraints") # Input cost and terminal constraints R = np.diag([1, 1]) # minimize applied inputs cost3 = opt.quadratic_cost(vehicle, np.zeros((3, 3)), R, u0=uf) constraints = [opt.input_range_constraint(vehicle, [8, -0.1], [12, 0.1])] terminal = [opt.state_range_constraint(vehicle, xf, xf)] # Reset logging to its default values logging.basicConfig(level=logging.DEBUG, filename="./steering-terminal_constraint.log", filemode='w', force=True) # Compute the optimal control start_time = time.process_time() result3 = opt.solve_ocp( vehicle, horizon, x0, cost3, constraints,