Skip to content
This repository was archived by the owner on Jul 20, 2022. It is now read-only.

Commit a9bac5a

Browse files
FreyJoadeebshihadehComma Device
authored
longitudinal MPC: use reset() function instead of recreating the solver in (commaai#24091)
* scons: add acados_template as dependency for lat and long mpc * long MPC: use acados reset instead of recreating the solver * long MPC: print timings and reset commented * update acados x86_64 * update acados include folder * update acados Python interface * update acados reference commit to latest acados/master * update x86 libs * update comma two * update acados again with commit 8ea8827fafb1b23b4c7da1c4cf650de1cbd73584 * update comma two * update comma three * update x86 Co-authored-by: Adeeb Shihadeh <[email protected]> Co-authored-by: Comma Device <[email protected]>
1 parent b51deb9 commit a9bac5a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1532
-233
lines changed

pyextra/acados_template/acados_layout.json

+3
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,9 @@
668668
"hessian_approx": [
669669
"str"
670670
],
671+
"hpipm_mode": [
672+
"str"
673+
],
671674
"regularize_method": [
672675
"str"
673676
],

pyextra/acados_template/acados_ocp.py

+29-4
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,7 @@ def __init__(self):
21552155
self.__globalization_use_SOC = 0
21562156
self.__full_step_dual = 0
21572157
self.__eps_sufficient_descent = 1e-4
2158+
self.__hpipm_mode = 'BALANCE'
21582159

21592160

21602161
@property
@@ -2165,6 +2166,21 @@ def qp_solver(self):
21652166
"""
21662167
return self.__qp_solver
21672168

2169+
@property
2170+
def hpipm_mode(self):
2171+
"""
2172+
Mode of HPIPM to be used,
2173+
2174+
String in ('BALANCE', 'SPEED_ABS', 'SPEED', 'ROBUST').
2175+
2176+
Default: 'BALANCE'.
2177+
2178+
see https://cdn.syscop.de/publications/Frison2020a.pdf
2179+
and the HPIPM code:
2180+
https://github.com/giaf/hpipm/blob/master/ocp_qp/x_ocp_qp_ipm.c#L69
2181+
"""
2182+
return self.__hpipm_mode
2183+
21682184
@property
21692185
def hessian_approx(self):
21702186
"""Hessian approximation.
@@ -2541,6 +2557,15 @@ def collocation_type(self, collocation_type):
25412557
raise Exception('Invalid collocation_type value. Possible values are:\n\n' \
25422558
+ ',\n'.join(collocation_types) + '.\n\nYou have: ' + collocation_type + '.\n\nExiting.')
25432559

2560+
@hpipm_mode.setter
2561+
def hpipm_mode(self, hpipm_mode):
2562+
hpipm_modes = ('BALANCE', 'SPEED_ABS', 'SPEED', 'ROBUST')
2563+
if hpipm_mode in hpipm_modes:
2564+
self.__hpipm_mode = hpipm_mode
2565+
else:
2566+
raise Exception('Invalid hpipm_mode value. Possible values are:\n\n' \
2567+
+ ',\n'.join(hpipm_modes) + '.\n\nYou have: ' + hpipm_mode + '.\n\nExiting.')
2568+
25442569
@hessian_approx.setter
25452570
def hessian_approx(self, hessian_approx):
25462571
hessian_approxs = ('GAUSS_NEWTON', 'EXACT')
@@ -2890,10 +2915,10 @@ def __init__(self, acados_path=''):
28902915
self.solver_options = AcadosOcpOptions()
28912916
"""Solver Options, type :py:class:`acados_template.acados_ocp.AcadosOcpOptions`"""
28922917

2893-
self.acados_include_path = f'{acados_path}/include'
2894-
"""Path to acados include directory, type: string"""
2895-
self.acados_lib_path = f'{acados_path}/lib'
2896-
"""Path to where acados library is located, type: string"""
2918+
self.acados_include_path = os.path.join(acados_path, 'include').replace(os.sep, '/') # the replace part is important on Windows for CMake
2919+
"""Path to acados include directory (set automatically), type: `string`"""
2920+
self.acados_lib_path = os.path.join(acados_path, 'lib').replace(os.sep, '/') # the replace part is important on Windows for CMake
2921+
"""Path to where acados library is located, type: `string`"""
28972922

28982923
import numpy
28992924
self.cython_include_dirs = numpy.get_include()

pyextra/acados_template/acados_ocp_solver.py

+112-29
Large diffs are not rendered by default.

pyextra/acados_template/acados_ocp_solver_pyx.pyx

+24-7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ cdef class AcadosOcpSolverCython:
112112
return acados_solver.acados_solve(self.capsule)
113113

114114

115+
def reset(self):
116+
"""
117+
Sets current iterate to all zeros.
118+
"""
119+
return acados_solver.acados_reset(self.capsule)
120+
121+
115122
def set_new_time_steps(self, new_time_steps):
116123
"""
117124
Set new time steps.
@@ -450,12 +457,12 @@ cdef class AcadosOcpSolverCython:
450457
return out
451458

452459

453-
def get_residuals(self):
460+
def get_residuals(self, recompute=False):
454461
"""
455462
Returns an array of the form [res_stat, res_eq, res_ineq, res_comp].
456463
"""
457464
# compute residuals if RTI
458-
if self.nlp_solver_type == 'SQP_RTI':
465+
if self.nlp_solver_type == 'SQP_RTI' or recompute:
459466
acados_solver_common.ocp_nlp_eval_residuals(self.nlp_solver, self.nlp_in, self.nlp_out)
460467

461468
# create output array
@@ -504,7 +511,7 @@ cdef class AcadosOcpSolverCython:
504511
cost_fields = ['y_ref', 'yref']
505512
constraints_fields = ['lbx', 'ubx', 'lbu', 'ubu']
506513
out_fields = ['x', 'u', 'pi', 'lam', 't', 'z', 'sl', 'su']
507-
mem_fields = ['xdot_guess']
514+
mem_fields = ['xdot_guess', 'z_guess']
508515

509516
field = field_.encode('utf-8')
510517

@@ -635,11 +642,21 @@ cdef class AcadosOcpSolverCython:
635642
"""
636643
Set options of the solver.
637644
638-
:param field: string, e.g. 'print_level', 'rti_phase', 'initialize_t_slacks', 'step_length', 'alpha_min', 'alpha_reduction'
639-
:param value: of type int, float
645+
:param field: string, e.g. 'print_level', 'rti_phase', 'initialize_t_slacks', 'step_length', 'alpha_min', 'alpha_reduction', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC', 'qp_tol_stat', 'qp_tol_eq', 'qp_tol_ineq', 'qp_tol_comp', 'qp_tau_min', 'qp_mu0'
646+
647+
:param value: of type int, float, string
648+
649+
- qp_tol_stat: QP solver tolerance stationarity
650+
- qp_tol_eq: QP solver tolerance equalities
651+
- qp_tol_ineq: QP solver tolerance inequalities
652+
- qp_tol_comp: QP solver tolerance complementarity
653+
- qp_tau_min: for HPIPM QP solvers: minimum value of barrier parameter in HPIPM
654+
- qp_mu0: for HPIPM QP solvers: initial value for complementarity slackness
655+
- warm_start_first_qp: indicates if first QP in SQP is warm_started
640656
"""
641-
int_fields = ['print_level', 'rti_phase', 'initialize_t_slacks', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC']
642-
double_fields = ['step_length', 'tol_eq', 'tol_stat', 'tol_ineq', 'tol_comp', 'alpha_min', 'alpha_reduction', 'eps_sufficient_descent']
657+
int_fields = ['print_level', 'rti_phase', 'initialize_t_slacks', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC', 'warm_start_first_qp']
658+
double_fields = ['step_length', 'tol_eq', 'tol_stat', 'tol_ineq', 'tol_comp', 'alpha_min', 'alpha_reduction', 'eps_sufficient_descent',
659+
'qp_tol_stat', 'qp_tol_eq', 'qp_tol_ineq', 'qp_tol_comp', 'qp_tau_min', 'qp_mu0']
643660
string_fields = ['globalization']
644661

645662
# encode

pyextra/acados_template/acados_sim.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,9 @@ def __init__(self, acados_path=''):
294294
self.solver_options = AcadosSimOpts()
295295
"""Solver Options, type :py:class:`acados_template.acados_sim.AcadosSimOpts`"""
296296

297-
self.acados_include_path = f'{acados_path}/include'
298-
"""Path to acados include directors (set automatically), type: `string`"""
299-
self.acados_lib_path = f'{acados_path}/lib'
297+
self.acados_include_path = os.path.join(acados_path, 'include').replace(os.sep, '/') # the replace part is important on Windows for CMake
298+
"""Path to acados include directory (set automatically), type: `string`"""
299+
self.acados_lib_path = os.path.join(acados_path, 'lib').replace(os.sep, '/') # the replace part is important on Windows for CMake
300300
"""Path to where acados library is located (set automatically), type: `string`"""
301301

302302
self.code_export_directory = 'c_generated_code'

pyextra/acados_template/acados_sim_solver.py

+45-13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from .acados_model import acados_model_strip_casadi_symbolics
4848
from .utils import is_column, render_template, format_class_dict, np_array_to_list,\
4949
make_model_consistent, set_up_imported_gnsf_model, get_python_interface_path
50+
from .builders import CMakeBuilder
5051

5152

5253
def make_sim_dims_consistent(acados_sim):
@@ -111,7 +112,17 @@ def sim_formulation_json_dump(acados_sim, json_file='acados_sim.json'):
111112
json.dump(sim_json, f, default=np_array_to_list, indent=4, sort_keys=True)
112113

113114

114-
def sim_render_templates(json_file, model_name, code_export_dir):
115+
def sim_get_default_cmake_builder() -> CMakeBuilder:
116+
"""
117+
If :py:class:`~acados_template.acados_sim_solver.AcadosSimSolver` is used with `CMake` this function returns a good first setting.
118+
:return: default :py:class:`~acados_template.builders.CMakeBuilder`
119+
"""
120+
cmake_builder = CMakeBuilder()
121+
cmake_builder.options_on = ['BUILD_ACADOS_SIM_SOLVER_LIB']
122+
return cmake_builder
123+
124+
125+
def sim_render_templates(json_file, model_name, code_export_dir, cmake_options: CMakeBuilder = None):
115126
# setting up loader and environment
116127
json_path = os.path.join(os.getcwd(), json_file)
117128

@@ -129,9 +140,15 @@ def sim_render_templates(json_file, model_name, code_export_dir):
129140
out_file = f'acados_sim_solver_{model_name}.h'
130141
render_template(in_file, out_file, template_dir, json_path)
131142

132-
in_file = 'Makefile.in'
133-
out_file = f'Makefile'
134-
render_template(in_file, out_file, template_dir, json_path)
143+
# Builder
144+
if cmake_options is not None:
145+
in_file = 'CMakeLists.in.txt'
146+
out_file = 'CMakeLists.txt'
147+
render_template(in_file, out_file, template_dir, json_path)
148+
else:
149+
in_file = 'Makefile.in'
150+
out_file = 'Makefile'
151+
render_template(in_file, out_file, template_dir, json_path)
135152

136153
in_file = 'main_sim.in.c'
137154
out_file = f'main_sim_{model_name}.c'
@@ -161,15 +178,19 @@ def sim_generate_casadi_functions(acados_sim):
161178
elif integrator_type == 'GNSF':
162179
generate_c_code_gnsf(model, opts)
163180

181+
164182
class AcadosSimSolver:
165183
"""
166184
Class to interact with the acados integrator C object.
167185
168-
:param acados_sim: type :py:class:`acados_template.acados_ocp.AcadosOcp` (takes values to generate an instance :py:class:`acados_template.acados_sim.AcadosSim`) or :py:class:`acados_template.acados_sim.AcadosSim`
169-
:param json_file: Default: 'acados_sim.json'
170-
:param build: Default: True
186+
:param acados_sim: type :py:class:`~acados_template.acados_ocp.AcadosOcp` (takes values to generate an instance :py:class:`~acados_template.acados_sim.AcadosSim`) or :py:class:`~acados_template.acados_sim.AcadosSim`
187+
:param json_file: Default: 'acados_sim.json'
188+
:param build: Default: True
189+
:param cmake_builder: type :py:class:`~acados_template.utils.CMakeBuilder` generate a `CMakeLists.txt` and use
190+
the `CMake` pipeline instead of a `Makefile` (`CMake` seems to be the better option in conjunction with
191+
`MS Visual Studio`); default: `None`
171192
"""
172-
def __init__(self, acados_sim_, json_file='acados_sim.json', build=True):
193+
def __init__(self, acados_sim_, json_file='acados_sim.json', build=True, cmake_builder: CMakeBuilder = None):
173194

174195
self.solver_created = False
175196

@@ -203,12 +224,16 @@ def __init__(self, acados_sim_, json_file='acados_sim.json', build=True):
203224
code_export_dir = acados_sim.code_export_directory
204225
if build:
205226
# render templates
206-
sim_render_templates(json_file, model_name, code_export_dir)
227+
sim_render_templates(json_file, model_name, code_export_dir, cmake_builder)
207228

208-
## Compile solver
229+
# Compile solver
209230
cwd = os.getcwd()
231+
code_export_dir = os.path.abspath(code_export_dir)
210232
os.chdir(code_export_dir)
211-
os.system('make sim_shared_lib')
233+
if cmake_builder is not None:
234+
cmake_builder.exec(code_export_dir)
235+
else:
236+
os.system('make sim_shared_lib')
212237
os.chdir(cwd)
213238

214239
self.sim_struct = acados_sim
@@ -234,8 +259,15 @@ def __init__(self, acados_sim_, json_file='acados_sim.json', build=True):
234259
print('acados was compiled without OpenMP.')
235260

236261
# Ctypes
237-
shared_lib = f'{code_export_dir}/libacados_sim_solver_{model_name}.so'
238-
self.shared_lib = CDLL(shared_lib)
262+
lib_prefix = 'lib'
263+
lib_ext = '.so'
264+
if os.name == 'nt':
265+
lib_prefix = ''
266+
lib_ext = ''
267+
self.shared_lib_name = os.path.join(code_export_dir, f'{lib_prefix}acados_sim_solver_{model_name}{lib_ext}')
268+
print(f'self.shared_lib_name = "{self.shared_lib_name}"')
269+
270+
self.shared_lib = CDLL(self.shared_lib_name)
239271

240272

241273
# create capsule

pyextra/acados_template/builders.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# -*- coding: future_fstrings -*-
2+
#
3+
# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
4+
# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
5+
# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
6+
# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
7+
#
8+
# This file is part of acados.
9+
#
10+
# The 2-Clause BSD License
11+
#
12+
# Redistribution and use in source and binary forms, with or without
13+
# modification, are permitted provided that the following conditions are met:
14+
#
15+
# 1. Redistributions of source code must retain the above copyright notice,
16+
# this list of conditions and the following disclaimer.
17+
#
18+
# 2. Redistributions in binary form must reproduce the above copyright notice,
19+
# this list of conditions and the following disclaimer in the documentation
20+
# and/or other materials provided with the distribution.
21+
#
22+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
# POSSIBILITY OF SUCH DAMAGE.;
33+
#
34+
35+
import os
36+
import sys
37+
from subprocess import call
38+
39+
40+
class CMakeBuilder:
41+
"""
42+
Class to work with the `CMake` build system.
43+
"""
44+
def __init__(self):
45+
self._source_dir = None # private source directory, this is set to code_export_dir
46+
self.build_dir = 'build'
47+
self._build_dir = None # private build directory, usually rendered to abspath(build_dir)
48+
self.generator = None
49+
"""Defines the generator, options can be found via `cmake --help` under 'Generator'. Type: string. Linux default 'Unix Makefiles', Windows 'Visual Studio 15 2017 Win64'; default value: `None`."""
50+
# set something for Windows
51+
if os.name == 'nt':
52+
self.generator = 'Visual Studio 15 2017 Win64'
53+
self.build_targets = None
54+
"""A comma-separated list of the build targets, if `None` then all targets will be build; type: List of strings; default: `None`."""
55+
self.options_on = None
56+
"""List of strings as CMake options which are translated to '-D Opt[0]=ON -D Opt[1]=ON ...'; default: `None`."""
57+
58+
# Generate the command string for handling the cmake command.
59+
def get_cmd1_cmake(self):
60+
defines_str = ''
61+
if self.options_on is not None:
62+
defines_arr = [f' -D{opt}=ON' for opt in self.options_on]
63+
defines_str = ' '.join(defines_arr)
64+
generator_str = ''
65+
if self.generator is not None:
66+
generator_str = f' -G"{self.generator}"'
67+
return f'cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="{self._source_dir}"{defines_str}{generator_str} -Wdev -S"{self._source_dir}" -B"{self._build_dir}"'
68+
69+
# Generate the command string for handling the build.
70+
def get_cmd2_build(self):
71+
import multiprocessing
72+
cmd = f'cmake --build "{self._build_dir}" --config Release -j{multiprocessing.cpu_count()}'
73+
if self.build_targets is not None:
74+
cmd += f' -t {self.build_targets}'
75+
return cmd
76+
77+
# Generate the command string for handling the install command.
78+
def get_cmd3_install(self):
79+
return f'cmake --install "{self._build_dir}"'
80+
81+
def exec(self, code_export_directory):
82+
"""
83+
Execute the compilation using `CMake` with the given settings.
84+
:param code_export_directory: must be the absolute path to the directory where the code was exported to
85+
"""
86+
if(os.path.isabs(code_export_directory) is False):
87+
print(f'(W) the code export directory "{code_export_directory}" is not an absolute path!')
88+
self._source_dir = code_export_directory
89+
self._build_dir = os.path.abspath(self.build_dir)
90+
try:
91+
os.mkdir(self._build_dir)
92+
except FileExistsError as e:
93+
pass
94+
95+
try:
96+
os.chdir(self._build_dir)
97+
cmd_str = self.get_cmd1_cmake()
98+
print(f'call("{cmd_str})"')
99+
retcode = call(cmd_str, shell=True)
100+
if retcode != 0:
101+
raise RuntimeError(f'CMake command "{cmd_str}" was terminated by signal {retcode}')
102+
cmd_str = self.get_cmd2_build()
103+
print(f'call("{cmd_str}")')
104+
retcode = call(cmd_str, shell=True)
105+
if retcode != 0:
106+
raise RuntimeError(f'Build command "{cmd_str}" was terminated by signal {retcode}')
107+
cmd_str = self.get_cmd3_install()
108+
print(f'call("{cmd_str}")')
109+
retcode = call(cmd_str, shell=True)
110+
if retcode != 0:
111+
raise RuntimeError(f'Install command "{cmd_str}" was terminated by signal {retcode}')
112+
except OSError as e:
113+
print("Execution failed:", e, file=sys.stderr)
114+
except Exception as e:
115+
print("Execution failed:", e, file=sys.stderr)
116+
exit(1)

0 commit comments

Comments
 (0)