Skip to content

Commit 40f576d

Browse files
authored
Merge pull request #61 from pablomarcel/develop
implemented a native route and a casadi route in the model predictive…
2 parents 90c7343 + 9fd86ea commit 40f576d

8 files changed

Lines changed: 744 additions & 60 deletions

model_predictive_control/RUNS.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,77 @@ python -m cli init-examples
2323
Overwrite examples if needed:
2424

2525
```bash
26-
python -m cli init-examples --force
26+
python -m cli init-examples \
27+
--force
2728
```
2829

29-
## Run the constrained double-integrator MPC demo
30+
## List example input files
3031

3132
```bash
32-
python -m cli run model_predictive_control/in/double_integrator_mpc.json
33+
python -m cli list-examples
3334
```
3435

35-
Equivalent short form when running from inside the package folder:
36+
## Native SciPy/SLSQP route: double integrator
3637

3738
```bash
38-
python cli.py run in/double_integrator_mpc.json
39+
python -m cli run in/double_integrator_mpc.json
3940
```
4041

41-
## Run the automotive engine-cooling LTV MPC demo
42+
## Native SciPy/SLSQP route: automotive engine cooling demo
4243

4344
```bash
44-
python -m cli run model_predictive_control/in/automotive_engine_cooling_mpc.json
45+
python -m cli run in/automotive_engine_cooling_mpc.json
4546
```
4647

47-
Equivalent short form when running from inside the package folder:
48+
## CasADi Opti/IPOPT route: double integrator
4849

4950
```bash
50-
python cli.py run in/automotive_engine_cooling_mpc.json
51+
python -m cli run in/double_integrator_mpc_casadi.json
52+
```
53+
54+
## CasADi Opti/IPOPT route: automotive engine cooling demo
55+
56+
```bash
57+
python -m cli run in/automotive_engine_cooling_mpc_casadi.json
58+
```
59+
60+
## Send outputs to the package out folder with an explicit stem
61+
62+
```bash
63+
python -m cli run in/double_integrator_mpc_casadi.json \
64+
--out out \
65+
--stem double_integrator_casadi
66+
```
67+
68+
```bash
69+
python -m cli run in/automotive_engine_cooling_mpc_casadi.json \
70+
--out out \
71+
--stem cooling_casadi
72+
```
73+
74+
## Run without plots
75+
76+
```bash
77+
python -m cli run in/double_integrator_mpc_casadi.json \
78+
--no-plots
5179
```
5280

5381
## Send outputs to a custom folder
5482

5583
```bash
56-
python -m cli run model_predictive_control/in/automotive_engine_cooling_mpc.json \
57-
--out model_predictive_control/out \
84+
python -m cli run in/automotive_engine_cooling_mpc.json \
85+
--out out/mpc/out \
5886
--stem cooling_demo
5987
```
6088

61-
## Run a quick package self-test
89+
## Self-test the native route
6290

6391
```bash
6492
python -m cli self-test
6593
```
94+
95+
## Self-test the CasADi route
96+
97+
```bash
98+
python -m cli self-test-casadi
99+
```

model_predictive_control/apis.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,26 @@
99

1010
try:
1111
from .app import MPCApp, MPCRunResult
12-
from .core import LinearMPCProblem, parse_problem, run_spec, simulate_mpc, solve_open_loop
12+
from .core import (
13+
LinearMPCProblem,
14+
parse_problem,
15+
parse_solver_config,
16+
run_spec,
17+
simulate_mpc,
18+
solve_open_loop,
19+
solve_open_loop_casadi,
20+
)
1321
except ImportError: # pragma: no cover
1422
from app import MPCApp, MPCRunResult # type: ignore
15-
from core import LinearMPCProblem, parse_problem, run_spec, simulate_mpc, solve_open_loop # type: ignore
23+
from core import ( # type: ignore
24+
LinearMPCProblem,
25+
parse_problem,
26+
parse_solver_config,
27+
run_spec,
28+
simulate_mpc,
29+
solve_open_loop,
30+
solve_open_loop_casadi,
31+
)
1632

1733

1834
def run_mpc_file(input_path: str | Path, out_dir: str | Path | None = None, *, plots: bool = True, show: bool = False) -> MPCRunResult:
@@ -32,9 +48,11 @@ def run_mpc_spec(spec: dict[str, Any], out_dir: str | Path | None = None, *, plo
3248
"MPCApp",
3349
"MPCRunResult",
3450
"parse_problem",
51+
"parse_solver_config",
3552
"run_mpc_file",
3653
"run_mpc_spec",
3754
"run_spec",
3855
"simulate_mpc",
3956
"solve_open_loop",
57+
"solve_open_loop_casadi",
4058
]

model_predictive_control/cli.py

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
import argparse
88
import json
9-
import shutil
109
import sys
1110
from 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 ...
1616
if __package__ in (None, ""):
@@ -19,11 +19,9 @@
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
2423
else:
2524
from .app import MPCApp
26-
from .core import run_spec
2725
from .utils import optional_import, print_kv, resolve_project_paths
2826

2927

@@ -32,6 +30,7 @@
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]],
@@ -60,10 +59,52 @@
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,
@@ -86,6 +127,42 @@
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+
191278
def 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

Comments
 (0)