66
77import argparse
88import json
9- import shutil
109import sys
1110from pathlib import Path
1211
13- # Import shim so both commands work:
12+ # Import shim so these styles can work depending on where the runbook is used:
13+ # python -m cli ...
1414# python -m model_predictive_control.cli ...
1515# python model_predictive_control/cli.py ...
1616if __package__ in (None , "" ):
1919 if str (PARENT ) not in sys .path :
2020 sys .path .insert (0 , str (PARENT ))
2121 from model_predictive_control .app import MPCApp # type: ignore
22- from model_predictive_control .core import run_spec # type: ignore
2322 from model_predictive_control .utils import optional_import , print_kv , resolve_project_paths # type: ignore
2423else :
2524 from .app import MPCApp
26- from .core import run_spec
2725 from .utils import optional_import , print_kv , resolve_project_paths
2826
2927
3230 "title" : "Double integrator constrained MPC" ,
3331 "analysis_type" : "lti_mpc_sim" ,
3432 "plant" : "linear_state_space" ,
33+ "solver" : {"backend" : "native_slsqp" , "maxiter" : 250 , "ftol" : 1e-8 },
3534 "dt" : 0.1 ,
3635 "model" : {
3736 "A" : [[1.0 , 0.1 ], [0.0 , 1.0 ]],
6059 "x_max" : [10.0 , 5.0 ]
6160 }
6261 },
62+ "double_integrator_mpc_casadi.json" : {
63+ "title" : "Double integrator constrained MPC - CasADi Opti backend" ,
64+ "analysis_type" : "lti_mpc_sim" ,
65+ "plant" : "linear_state_space" ,
66+ "solver" : {
67+ "backend" : "casadi_opti" ,
68+ "max_iter" : 150 ,
69+ "tol" : 1e-8 ,
70+ "acceptable_tol" : 1e-6 ,
71+ "print_level" : 0 ,
72+ "print_time" : False ,
73+ "expand" : False
74+ },
75+ "dt" : 0.1 ,
76+ "model" : {
77+ "A" : [[1.0 , 0.1 ], [0.0 , 1.0 ]],
78+ "B" : [[0.005 ], [0.1 ]],
79+ "d" : [0.0 , 0.0 ]
80+ },
81+ "state_names" : ["position" , "velocity" ],
82+ "input_names" : ["acceleration_command" ],
83+ "x0" : [6.0 , 0.0 ],
84+ "x_ref" : [0.0 , 0.0 ],
85+ "u_ref" : [0.0 ],
86+ "horizon" : 14 ,
87+ "steps" : 45 ,
88+ "weights" : {
89+ "Q" : [8.0 , 0.8 ],
90+ "R" : [0.04 ],
91+ "P" : [20.0 , 3.0 ],
92+ "Rd" : [0.15 ]
93+ },
94+ "constraints" : {
95+ "u_min" : [- 2.0 ],
96+ "u_max" : [2.0 ],
97+ "du_min" : [- 0.45 ],
98+ "du_max" : [0.45 ],
99+ "x_min" : [- 10.0 , - 5.0 ],
100+ "x_max" : [10.0 , 5.0 ]
101+ }
102+ },
63103 "automotive_engine_cooling_mpc.json" : {
64104 "title" : "Automotive engine cooling LTV MPC demo" ,
65105 "analysis_type" : "ltv_mpc_sim" ,
66106 "plant" : "thermal_cooling_4state_demo" ,
107+ "solver" : {"backend" : "native_slsqp" , "maxiter" : 250 , "ftol" : 1e-8 },
67108 "dt" : 1.0 ,
68109 "horizon" : 12 ,
69110 "steps" : 90 ,
86127 "x_min" : [70.0 , 60.0 , 70.0 , 50.0 ],
87128 "x_max" : [128.0 , 112.0 , 122.0 , 108.0 ]
88129 }
130+ },
131+ "automotive_engine_cooling_mpc_casadi.json" : {
132+ "title" : "Automotive engine cooling LTV MPC demo - CasADi Opti backend" ,
133+ "analysis_type" : "ltv_mpc_sim" ,
134+ "plant" : "thermal_cooling_4state_demo" ,
135+ "solver" : {
136+ "backend" : "casadi_opti" ,
137+ "max_iter" : 150 ,
138+ "tol" : 1e-8 ,
139+ "acceptable_tol" : 1e-6 ,
140+ "print_level" : 0 ,
141+ "print_time" : False ,
142+ "expand" : False
143+ },
144+ "dt" : 1.0 ,
145+ "horizon" : 8 ,
146+ "steps" : 45 ,
147+ "ambient_temp_c" : 38.0 ,
148+ "x0" : [118.0 , 101.0 , 110.0 , 90.0 ],
149+ "x_ref" : [105.0 , 92.0 , 100.0 , 82.0 ],
150+ "state_names" : ["wall_temp_c" , "coolant_out_c" , "block_temp_c" , "radiator_out_c" ],
151+ "input_names" : ["pump_command" , "fan_command" ],
152+ "weights" : {
153+ "Q" : [5.0 , 8.0 , 2.0 , 1.0 ],
154+ "R" : [0.05 , 0.08 ],
155+ "P" : [10.0 , 14.0 , 4.0 , 2.0 ],
156+ "Rd" : [0.35 , 0.45 ]
157+ },
158+ "constraints" : {
159+ "u_min" : [0.0 , 0.0 ],
160+ "u_max" : [1.0 , 1.0 ],
161+ "du_min" : [- 0.12 , - 0.12 ],
162+ "du_max" : [0.12 , 0.12 ],
163+ "x_min" : [70.0 , 60.0 , 70.0 , 50.0 ],
164+ "x_max" : [128.0 , 112.0 , 122.0 , 108.0 ]
165+ }
89166 }
90167}
91168
@@ -110,7 +187,8 @@ def build_parser() -> argparse.ArgumentParser:
110187 sub .add_parser ("list-examples" , help = "List example JSON files" )
111188 sub .add_parser ("check-libs" , help = "Check optional MPC-related package imports" )
112189 sub .add_parser ("tree" , help = "Print resolved package input and output folders" )
113- sub .add_parser ("self-test" , help = "Create examples and run the double-integrator demo" )
190+ sub .add_parser ("self-test" , help = "Create examples and run the native double-integrator demo" )
191+ sub .add_parser ("self-test-casadi" , help = "Create examples and run the CasADi double-integrator demo" )
114192
115193 return parser
116194
@@ -170,6 +248,8 @@ def cmd_run(args: argparse.Namespace) -> int:
170248 print ("MPC run complete." )
171249 print_kv ([
172250 ("title" , result .get ("title" )),
251+ ("plant" , result .get ("plant" )),
252+ ("solver_backend" , result .get ("solver_backend" )),
173253 ("steps" , result .get ("steps" )),
174254 ("horizon" , result .get ("horizon" )),
175255 ("all_success" , result .get ("all_optimizations_successful" )),
@@ -188,6 +268,13 @@ def cmd_self_test() -> int:
188268 return cmd_run (args )
189269
190270
271+ def cmd_self_test_casadi () -> int :
272+ cmd_init_examples (force = False )
273+ paths = resolve_project_paths ()
274+ args = argparse .Namespace (input = str (paths .in_dir / "double_integrator_mpc_casadi.json" ), out = None , stem = "self_test_double_integrator_casadi" , no_plots = False , show = False )
275+ return cmd_run (args )
276+
277+
191278def main (argv : list [str ] | None = None ) -> int :
192279 parser = build_parser ()
193280 args = parser .parse_args (argv )
@@ -207,6 +294,8 @@ def main(argv: list[str] | None = None) -> int:
207294 return cmd_run (args )
208295 if args .command == "self-test" :
209296 return cmd_self_test ()
297+ if args .command == "self-test-casadi" :
298+ return cmd_self_test_casadi ()
210299
211300 parser .error (f"Unknown command: { args .command } " )
212301 return 2
0 commit comments