alpaqa 0.0.1
Nonconvex constrained optimization
casadi_problem.py
Go to the documentation of this file.
1from typing import Tuple, Union
2import casadi as cs
3import alpaqa as pa
4import os
5from os.path import join, basename
6import shelve
7import uuid
8import pickle
9import base64
10import glob
11import subprocess
12import tempfile
13import platform
14
15
17 f: cs.Function,
18 g: cs.Function,
19 second_order: bool = False,
20 name: str = "alpaqa_problem",
21) -> Tuple[cs.CodeGenerator, int, int, int]:
22 """Convert the objective and constraint functions into a CasADi code
23 generator.
24
25 :param f: Objective function.
26 :param g: Constraint function.
27 :param second_order: Whether to generate functions for evaluating Hessians.
28 :param name: Optional string description of the problem (used for filename).
29
30 :return: * Code generator that generates the functions and derivatives
31 used by the solvers.
32 * Dimensions of the decision variables (primal dimension).
33 * Number of nonlinear constraints (dual dimension).
34 * Number of parameters.
35 """
36
37 assert f.n_in() in [1, 2]
38 assert f.n_in() == g.n_in()
39 assert f.size1_in(0) == g.size1_in(0)
40 if f.n_in() == 2:
41 assert f.size1_in(1) == g.size1_in(1)
42 assert f.n_out() == 1
43 assert g.n_out() == 1
44 n = f.size1_in(0)
45 m = g.size1_out(0)
46 p = f.size1_in(1) if f.n_in() == 2 else 0
47 xp = (f.sx_in(0), f.sx_in(1)) if f.n_in() == 2 else (f.sx_in(0), )
48 xp_names = (f.name_in(0),
49 f.name_in(1)) if f.n_in() == 2 else (f.name_in(0), )
50 x = xp[0]
51 y = cs.SX.sym("y", m)
52 v = cs.SX.sym("v", n)
53
54 L = f(*xp) + cs.dot(y, g(*xp)) if m > 0 else f(*xp)
55
56 cgname = f"{name}.c"
57 cg = cs.CodeGenerator(cgname)
58 cg.add(cs.Function(
59 "f",
60 [*xp],
61 [f(*xp)],
62 [*xp_names],
63 ["f"],
64 ))
65 cg.add(
66 cs.Function(
67 "grad_f",
68 [*xp],
69 [cs.gradient(f(*xp), x)],
70 [*xp_names],
71 ["grad_f"],
72 ))
73 cg.add(cs.Function(
74 "g",
75 [*xp],
76 [g(*xp)],
77 [*xp_names],
78 ["g"],
79 ))
80 cg.add(
81 cs.Function(
82 "grad_g",
83 [*xp, y],
84 [cs.jtimes(g(*xp), x, y, True)],
85 [*xp_names, "y"],
86 ["grad_g"],
87 ))
88 if second_order:
89 cg.add(
90 cs.Function(
91 "hess_L",
92 [*xp, y],
93 [cs.hessian(L, x)[0]],
94 [*xp_names, "y"],
95 ["hess_L"],
96 ))
97 cg.add(
98 cs.Function(
99 "hess_L_prod",
100 [*xp, y, v],
101 [cs.gradient(cs.jtimes(L, x, v, False), x)],
102 [*xp_names, "y", "v"],
103 ["hess_L_prod"],
104 ))
105 return cg, n, m, p
106
107
108def _load_casadi_problem(sofile, n, m, p):
109 if p > 0:
110 prob = pa.load_casadi_problem_with_param(sofile, n, m, p)
111 else:
112 prob = pa.load_casadi_problem(sofile, n, m)
113 return prob
114
115
117 f: cs.Function,
118 g: cs.Function,
119 second_order: bool = False,
120 name: str = "alpaqa_problem",
121) -> Union[pa.Problem, pa.ProblemWithParam]:
122 """Compile the objective and constraint functions into a alpaqa Problem.
123
124 :param f: Objective function.
125 :param g: Constraint function.
126 :param second_order: Whether to generate functions for evaluating Hessians.
127 :param name: Optional string description of the problem (used for filename).
128
129 :return: * Problem specification that can be passed to the solvers.
130 """
131
132 cachedir = None
133 if not cachedir:
134 homecachedir = os.path.expanduser("~/.cache")
135 if os.path.isdir(homecachedir):
136 cachedir = join(homecachedir, 'alpaqa', 'cache')
137 else:
138 cachedir = join(tempfile.gettempdir(), 'alpaqa', 'cache')
139 cachefile = join(cachedir, 'problems')
140
141 key = base64.b64encode(pickle.dumps(
142 (f, g, second_order, name))).decode('ascii')
143
144 os.makedirs(cachedir, exist_ok=True)
145 with shelve.open(cachefile) as cache:
146 if key in cache:
147 uid, soname, dim = cache[key]
148 probdir = join(cachedir, str(uid))
149 sofile = join(probdir, soname)
150 try:
151 return _load_casadi_problem(sofile, *dim)
152 except:
153 del cache[key]
154 # if os.path.exists(probdir) and os.path.isdir(probdir):
155 # shutil.rmtree(probdir)
156 raise
157 uid = uuid.uuid1()
158 projdir = join(cachedir, "build")
159 builddir = join(projdir, "build")
160 os.makedirs(builddir, exist_ok=True)
161 probdir = join(cachedir, str(uid))
162 cgen, n, m, p = generate_casadi_problem(f, g, second_order, name)
163 cfile = cgen.generate(join(projdir, ""))
164 with open(join(projdir, 'CMakeLists.txt'), 'w') as f:
165 f.write(f"""
166 cmake_minimum_required(VERSION 3.17)
167 project(CasADi-{name} LANGUAGES C)
168 set(CMAKE_SHARED_LIBRARY_PREFIX "")
169 add_library({name} SHARED {basename(cfile)})
170 install(FILES $<TARGET_FILE:{name}>
171 DESTINATION lib)
172 install(FILES {basename(cfile)}
173 DESTINATION src)
174 """)
175 build_type = 'Release'
176 configure_cmd = ['cmake', '-B', builddir, '-S', projdir]
177 if platform.system() != 'Windows':
178 configure_cmd += ['-G', 'Ninja Multi-Config']
179 build_cmd = ['cmake', '--build', builddir, '--config', build_type]
180 install_cmd = [
181 'cmake', '--install', builddir, '--config', build_type, '--prefix',
182 probdir
183 ]
184 subprocess.run(configure_cmd, check=True)
185 subprocess.run(build_cmd, check=True)
186 subprocess.run(install_cmd, check=True)
187 sofile = glob.glob(join(probdir, "lib", name + ".*"))
188 if len(sofile) == 0:
189 raise RuntimeError(
190 f"Unable to find compiled CasADi problem '{name}'")
191 elif len(sofile) > 1:
192 raise RuntimeWarning(
193 f"Multiple compiled CasADi problem files were found for '{name}'"
194 )
195 sofile = sofile[0]
196 soname = os.path.relpath(sofile, probdir)
197 cache[key] = uid, soname, (n, m, p)
198
199 return _load_casadi_problem(sofile, n, m, p)
Tuple[cs.CodeGenerator, int, int, int] generate_casadi_problem(cs.Function f, cs.Function g, bool second_order=False, str name="alpaqa_problem")
Union[pa.Problem, pa.ProblemWithParam] generate_and_compile_casadi_problem(cs.Function f, cs.Function g, bool second_order=False, str name="alpaqa_problem")
auto project(const Vec &v, const Box &box)
Project a vector onto a box.
Definition: box.hpp:15