@@ -39,7 +39,8 @@ def test_finite_horizon_simple():
3939
4040 # Retrieve the full open-loop predictions
4141 res = opt .solve_ocp (
42- sys , time , x0 , cost , constraints , squeeze = True )
42+ sys , time , x0 , cost , constraints , squeeze = True ,
43+ terminal_cost = cost ) # include to match MPT3 formulation
4344 t , u_openloop = res .time , res .inputs
4445 np .testing .assert_almost_equal (
4546 u_openloop , [- 1 , - 1 , 0.1393 , 0.3361 , - 5.204e-16 ], decimal = 4 )
@@ -57,9 +58,7 @@ def test_finite_horizon_simple():
5758#
5859# The next unit test is intended to confirm that a finite horizon
5960# optimal control problem with terminal cost set to LQR "cost to go"
60- # gives the same answer as LQR. Unfortunately, it requires a discrete
61- # time LQR function which is not yet availbale => for now this just
62- # tests the interface a bit.
61+ # gives the same answer as LQR.
6362#
6463@slycotonly
6564def test_discrete_lqr ():
@@ -76,35 +75,43 @@ def test_discrete_lqr():
7675 # Include weights on states/inputs
7776 Q = np .eye (2 )
7877 R = 1
79- K , S , E = ct .lqr (A , B , Q , R ) # note: *continuous* time LQR
78+ K , S , E = ct .dlqr (A , B , Q , R )
8079
8180 # Compute the integral and terminal cost
8281 integral_cost = opt .quadratic_cost (sys , Q , R )
8382 terminal_cost = opt .quadratic_cost (sys , S , None )
8483
85- # Formulate finite horizon MPC problem
84+ # Solve the LQR problem
85+ lqr_sys = ct .ss2io (ct .ss (A - B @ K , B , C , D , 1 ))
86+
87+ # Generate a simulation of the LQR controller
8688 time = np .arange (0 , 5 , 1 )
8789 x0 = np .array ([1 , 1 ])
90+ _ , _ , lqr_x = ct .input_output_response (
91+ lqr_sys , time , 0 , x0 , return_x = True )
92+
93+ # Use LQR input as initial guess to avoid convergence/precision issues
94+ lqr_u = - K @ lqr_x [0 :time .size ]
95+
96+ # Formulate the optimal control problem and compute optimal trajectory
8897 optctrl = opt .OptimalControlProblem (
89- sys , time , integral_cost , terminal_cost = terminal_cost )
98+ sys , time , integral_cost , terminal_cost = terminal_cost ,
99+ initial_guess = lqr_u )
90100 res1 = optctrl .compute_trajectory (x0 , return_states = True )
91101
92- with pytest .xfail ("discrete LQR not implemented" ):
93- # Result should match LQR
94- K , S , E = ct .dlqr (A , B , Q , R )
95- lqr_sys = ct .ss2io (ct .ss (A - B @ K , B , C , D , 1 ))
96- _ , _ , lqr_x = ct .input_output_response (
97- lqr_sys , time , 0 , x0 , return_x = True )
98- np .testing .assert_almost_equal (res1 .states , lqr_x )
102+ # Compare to make sure results are the same
103+ np .testing .assert_almost_equal (res1 .inputs , lqr_u [0 ])
104+ np .testing .assert_almost_equal (res1 .states , lqr_x )
99105
100106 # Add state and input constraints
101107 trajectory_constraints = [
102- (sp .optimize .LinearConstraint , np .eye (3 ), [- 10 , - 10 , - 1 ], [10 , 10 , 1 ]),
108+ (sp .optimize .LinearConstraint , np .eye (3 ), [- 5 , - 5 , - .5 ], [5 , 5 , 0.5 ]),
103109 ]
104110
105111 # Re-solve
106112 res2 = opt .solve_ocp (
107- sys , time , x0 , integral_cost , constraints , terminal_cost = terminal_cost )
113+ sys , time , x0 , integral_cost , trajectory_constraints ,
114+ terminal_cost = terminal_cost , initial_guess = lqr_u )
108115
109116 # Make sure we got a different solution
110117 assert np .any (np .abs (res1 .inputs - res2 .inputs ) > 0.1 )
@@ -205,7 +212,9 @@ def test_constraint_specification(constraint_list):
205212
206213 # Create a model predictive controller system
207214 time = np .arange (0 , 5 , 1 )
208- optctrl = opt .OptimalControlProblem (sys , time , cost , constraints )
215+ optctrl = opt .OptimalControlProblem (
216+ sys , time , cost , constraints ,
217+ terminal_cost = cost ) # include to match MPT3 formulation
209218
210219 # Compute optimal control and compare against MPT3 solution
211220 x0 = [4 , 0 ]
@@ -223,7 +232,7 @@ def test_constraint_specification(constraint_list):
223232 ([[1 , 0 ], [0 , 1 ]], np .eye (2 ), np .eye (2 ), 0 , 1 ),
224233 id = "discrete, dt=1" ),
225234 pytest .param (
226- (np .zeros ((2 ,2 )), np .eye (2 ), np .eye (2 ), 0 ),
235+ (np .zeros ((2 , 2 )), np .eye (2 ), np .eye (2 ), 0 ),
227236 id = "continuous" ),
228237])
229238def test_terminal_constraints (sys_args ):
@@ -274,8 +283,11 @@ def test_terminal_constraints(sys_args):
274283
275284 # Re-run using a basis function and see if we get the same answer
276285 res = opt .solve_ocp (sys , time , x0 , cost , terminal_constraints = final_point ,
277- basis = flat .BezierFamily (4 , Tf ))
278- np .testing .assert_almost_equal (res .inputs , u1 , decimal = 2 )
286+ basis = flat .BezierFamily (8 , Tf ))
287+
288+ # Final point doesn't affect cost => don't need to test
289+ np .testing .assert_almost_equal (
290+ res .inputs [:, :- 1 ], u1 [:, :- 1 ], decimal = 2 )
279291
280292 # Impose some cost on the state, which should change the path
281293 Q = np .eye (2 )
0 commit comments