alpaqa 0.0.1
Nonconvex constrained optimization
CUTEstLoader.cpp
Go to the documentation of this file.
2
3#include <cutest.h>
4#include <dlfcn.h>
5
6#include <functional>
7#include <iostream>
8#include <stdexcept>
9#include <string>
10#include <string_view>
11
12using namespace std::string_literals;
13
15 private:
16 static void throw_error(std::string s, int code) {
17 throw std::runtime_error(s + " (" + std::to_string(code) + ")");
18 }
19 static void throw_if_error(std::string s, int code) {
20 if (code)
21 throw_error(s, code);
22 }
23
24 public:
25 CUTEstLoader(const char *so_fname, const char *outsdif_fname) {
26 // Open the shared library
27 so_handle = dlopen(so_fname, RTLD_LAZY);
28 if (!so_handle)
29 throw std::runtime_error("Failed to open "s + so_fname);
30
31 // Open the OUTSDIF.d file
32 integer ierr;
33 auto fptr_open = dlfun<decltype(FORTRAN_open)>("fortran_open_");
34 fptr_open(&funit, outsdif_fname, &ierr);
35 if (ierr)
36 throw std::runtime_error("Failed to open "s + outsdif_fname);
37
38 // Get the dimensions of the problem
39 integer status;
40 auto fptr_cdimen = dlfun<decltype(CUTEST_cdimen)>("cutest_cdimen_");
41 fptr_cdimen(&status, &funit, &nvar, &ncon);
42 throw_if_error("Failed to call cutest_cdimen", status);
43
44 // Set up the datastructures
45 x.resize(nvar);
46 x_l.resize(nvar);
47 x_u.resize(nvar);
48 y.resize(ncon);
49 c_l.resize(ncon);
50 c_u.resize(ncon);
51 equatn.resize(ncon);
52 linear.resize(ncon);
53 integer e_order = 0;
54 integer l_order = 0;
55 integer v_order = 0;
56
57 // Constrained problem
58 if (ncon > 0) {
59 auto fptr_csetup =
60 dlfun<decltype(CUTEST_csetup)>("cutest_cint_csetup_");
61 fptr_csetup(&status, &funit, &iout, &io_buffer, &nvar, &ncon,
62 x.data(), x_l.data(), x_u.data(), y.data(), c_l.data(),
63 c_u.data(), (logical *)equatn.data(),
64 (logical *)linear.data(), &e_order, &l_order, &v_order);
65 throw_if_error("Failed to call cutest_csetup", status);
66 }
67 // Unconstrained problem
68 else {
69 auto fptr_usetup = dlfun<decltype(CUTEST_usetup)>("cutest_usetup_");
70 fptr_usetup(&status, &funit, &iout, &io_buffer, &nvar, x.data(),
71 x_l.data(), x_u.data());
72 throw_if_error("Failed to call cutest_usetup", status);
73 }
74 if (ncon > 0)
75 work.resize(std::max(nvar, ncon));
76
77 eval_obj_p = ncon > 0 //
78 ? dlfun<void>("cutest_cfn_") //
79 : dlfun<void>("cutest_ufn_");
80 eval_obj_grad_p = ncon > 0 //
81 ? dlfun<void>("cutest_cint_cofg_") //
82 : dlfun<void>("cutest_ugr_");
83 eval_constr_p = ncon > 0 //
84 ? eval_obj_p //
85 : nullptr;
87 ? dlfun<void>("cutest_cint_cjprod_") //
88 : nullptr;
90 ? dlfun<void>("cutest_cint_ccifg_") //
91 : nullptr;
93 ? dlfun<void>("cutest_cint_chprod_")
94 : dlfun<void>("cutest_cint_uhprod_");
95 eval_lagr_hess_p = ncon > 0 //
96 ? dlfun<void>("cutest_cdh_") //
97 : dlfun<void>("cutest_udh_");
98 }
99
100 template <class F>
101 static inline constexpr auto call_as(void *funp) {
102 return (F *)funp;
103 }
104
106 assert(x.size() == nvar);
107 assert(ncon > 0);
108 integer status;
109 doublereal f;
110 call_as<decltype(CUTEST_cfn)>(eval_obj_p)(&status, &nvar, &ncon,
111 x.data(), &f, work.data());
112 throw_if_error("Failed to call cutest_cfn", status);
113 return f;
114 }
115
117 assert(x.size() == nvar);
118 assert(ncon == 0);
119 integer status;
120 doublereal f;
121 call_as<decltype(CUTEST_ufn)>(eval_obj_p)(&status, &nvar, x.data(), &f);
122 throw_if_error("Failed to call cutest_ufn", status);
123 return f;
124 }
125
127 assert(x.size() == nvar);
128 assert(grad_f.size() == nvar);
129 assert(ncon > 0);
130 integer status;
131 logical grad = true;
132 call_as<decltype(CUTEST_cofg)>(eval_obj_grad_p)(
133 &status, &nvar, x.data(), work.data(), grad_f.data(), &grad);
134 throw_if_error("Failed to call cutest_cfn", status);
135 }
136
138 assert(x.size() == nvar);
139 assert(grad_f.size() == nvar);
140 assert(ncon == 0);
141 integer status;
142 call_as<decltype(CUTEST_ugr)>(eval_obj_grad_p)(&status, &nvar, x.data(),
143 grad_f.data());
144 throw_if_error("Failed to call cutest_ugr", status);
145 }
146
148 assert(x.size() == nvar);
149 assert(g.size() == ncon);
150 if (ncon == 0)
151 return;
152 integer status;
153 call_as<decltype(CUTEST_cfn)>(eval_constr_p)(
154 &status, &nvar, &ncon, x.data(), work.data(), g.data());
155 throw_if_error("Failed to call cutest_cfn", status);
156 }
157
159 alpaqa::rvec grad_g_v) const {
160 assert(x.size() == nvar);
161 assert(v.size() == ncon);
162 assert(grad_g_v.size() == nvar);
163 if (ncon == 0) {
164 grad_g_v.setZero();
165 return;
166 }
167 integer status;
168 logical gotJ = false;
169 logical jtrans = true;
170 call_as<decltype(CUTEST_cjprod)>(eval_constr_grad_prod_p)(
171 &status, &nvar, &ncon, &gotJ, &jtrans, x.data(), v.data(), &ncon,
172 grad_g_v.data(), &nvar);
173 throw_if_error("Failed to call cutest_cjprod", status);
174 }
175
177 alpaqa::rvec grad_gi) const {
178 assert(x.size() == nvar);
179 assert(grad_gi.size() == nvar);
180 if (ncon == 0) {
181 grad_gi.setZero();
182 return;
183 }
184 integer status;
185 integer icon = i + 1;
186 assert(icon >= 1);
187 assert(icon <= ncon);
189 logical grad = true;
190 call_as<decltype(CUTEST_ccifg)>(eval_constr_i_grad_p)(
191 &status, &nvar, &icon, x.data(), &ci, grad_gi.data(), &grad);
192 throw_if_error("Failed to call cutest_ccifg", status);
193 }
194
196 alpaqa::rvec Hv) const {
197 assert(x.size() == nvar);
198 assert(y.size() == ncon);
199 assert(v.rows() == nvar);
200 assert(Hv.rows() == nvar);
201 integer status;
202 logical gotH = false;
203 if (ncon == 0) {
204 call_as<decltype(CUTEST_uhprod)>(eval_lagr_hess_prod_p)(
205 &status, &nvar, &gotH, x.data(), v.data(), Hv.data());
206 throw_if_error("Failed to call cutest_uhprod", status);
207 } else {
208 call_as<decltype(CUTEST_chprod)>(eval_lagr_hess_prod_p)(
209 &status, &nvar, &ncon, &gotH, x.data(), y.data(),
210 const_cast<alpaqa::real_t *>(v.data()), Hv.data());
211 // TODO: why is the VECTOR argument not const?
212 throw_if_error("Failed to call cutest_chprod", status);
213 }
214 }
215
217 assert(x.size() == nvar);
218 assert(y.size() == ncon);
219 assert(H.rows() >= nvar);
220 assert(H.cols() >= nvar);
221 integer status;
222 integer ldH = H.rows();
223 if (ncon == 0) {
224 call_as<decltype(CUTEST_udh)>(eval_lagr_hess_p)(
225 &status, &nvar, x.data(), &ldH, H.data());
226 throw_if_error("Failed to call cutest_udh", status);
227 } else {
228 call_as<decltype(CUTEST_cdh)>(eval_lagr_hess_p)(
229 &status, &nvar, &ncon, x.data(), y.data(), &ldH, H.data());
230 throw_if_error("Failed to call cutest_cdh", status);
231 }
232 }
233
234 unsigned count_box_constraints() const {
235 return std::count_if(x_l.data(), x_l.data() + nvar,
236 [](alpaqa::real_t x) { return x > -CUTE_INF; }) +
237 std::count_if(x_u.data(), x_u.data() + nvar,
238 [](alpaqa::real_t x) { return x < +CUTE_INF; });
239 }
240
241 std::string get_name() {
242 std::string name;
243 name.resize(10);
244 integer status;
245 dlfun<decltype(CUTEST_probname)>("cutest_probname_")(&status,
246 name.data());
247 throw_if_error("Failed to call cutest_probname", status);
248 auto nspace = std::find_if(name.rbegin(), name.rend(),
249 [](char c) { return c != ' '; });
250 name.resize(name.size() - (nspace - name.rbegin()));
251 return name;
252 }
253
254 integer get_report(doublereal *calls, doublereal *time) {
255 integer status;
256 auto fptr_report =
257 ncon > 0 ? dlfun<decltype(CUTEST_creport)>("cutest_creport_")
258 : dlfun<decltype(CUTEST_ureport)>("cutest_ureport_");
259 fptr_report(&status, calls, time);
260 return status;
261 }
262
264 // Terminate CUTEst
265 if (ncon > 0) {
266 integer status;
267 auto fptr_cterminate =
268 dlfun<decltype(CUTEST_cterminate)>("cutest_cterminate_");
269 fptr_cterminate(&status);
270 throw_if_error("Failed to call cutest_cterminate", status);
271 } else {
272 integer status;
273 auto fptr_uterminate =
274 dlfun<decltype(CUTEST_uterminate)>("cutest_uterminate_");
275 fptr_uterminate(&status);
276 throw_if_error("Failed to call cutest_uterminate", status);
277 }
278
279 // Close the OUTSDIF.d file
280 integer ierr;
281 auto fptr_close = dlfun<decltype(FORTRAN_close)>("fortran_close_");
282 fptr_close(&funit, &ierr);
283 throw_if_error("Failed to close OUTSDIF.d file", ierr);
284
285 // Close the shared library
286 if (so_handle)
287 dlclose(so_handle);
288 }
289
290 template <class T>
291 T *dlfun(const char *name) {
292 (void)dlerror();
293 auto res = (T *)dlsym(so_handle, name);
294 if (const char *error = dlerror(); error)
295 throw std::runtime_error(error);
296 return res;
297 }
298
299 void *so_handle = nullptr; ///< dlopen handle to shared library
300
301 integer funit = 42; ///< Fortran Unit Number for OUTSDIF.d file
302 integer iout = 6; ///< Fortran Unit Number for standard output
303 integer io_buffer = 11; ///< Fortran Unit Number for internal IO
304
305 integer nvar; ///< Number of decision variabls
306 integer ncon; ///< Number of constraints
307
308 using logical_vec = Eigen::Matrix<logical, Eigen::Dynamic, 1>;
309 alpaqa::vec x; ///< decision variable
310 alpaqa::vec x_l; ///< lower bound on x
311 alpaqa::vec x_u; ///< upper bound on x
312 alpaqa::vec y; ///< lagrange multipliers
313 alpaqa::vec c_l; ///< lower bounds on constraints
314 alpaqa::vec c_u; ///< upper bounds on constraints
315 logical_vec equatn; ///< whether the constraint is an equality
316 logical_vec linear; ///< whether the constraint is linear
317 mutable alpaqa::vec work; ///< work vector
318
319 void *eval_obj_p = nullptr;
320 void *eval_obj_grad_p = nullptr;
321 void *eval_constr_p = nullptr;
322 void *eval_constr_grad_prod_p = nullptr;
323 void *eval_constr_i_grad_p = nullptr;
324 void *eval_lagr_hess_prod_p = nullptr;
325 void *eval_lagr_hess_p = nullptr;
326};
327
328CUTEstProblem::CUTEstProblem(const char *so_fname, const char *outsdif_fname) {
329 implementation = std::make_unique<CUTEstLoader>(so_fname, outsdif_fname);
330 auto *l = implementation.get();
331 name = l->get_name();
332 number_box_constraints = l->count_box_constraints();
333 problem.n = l->nvar;
334 problem.m = l->ncon;
335 problem.C.lowerbound = std::move(l->x_l);
336 problem.C.upperbound = std::move(l->x_u);
337 problem.D.lowerbound = std::move(l->c_l);
338 problem.D.upperbound = std::move(l->c_u);
339 using namespace std::placeholders;
340 if (problem.m > 0) {
342 problem.grad_f = std::bind(
344 } else {
345 problem.f =
347 problem.grad_f = std::bind(
349 }
350 problem.g = std::bind(&CUTEstLoader::eval_constraints, l, _1, _2);
352 std::bind(&CUTEstLoader::eval_constraints_grad_prod, l, _1, _2, _3);
354 std::bind(&CUTEstLoader::eval_constraint_i_grad, l, _1, _2, _3);
356 std::bind(&CUTEstLoader::eval_lagr_hess_prod, l, _1, _2, _3, _4);
357 problem.hess_L = std::bind(&CUTEstLoader::eval_lagr_hess, l, _1, _2, _3);
358 x0 = std::move(l->x);
359 y0 = std::move(l->y);
360}
361
362CUTEstProblem::CUTEstProblem(const std::string &so_fname,
363 const std::string &outsdif_fname)
364 : CUTEstProblem(so_fname.c_str(), outsdif_fname.c_str()) {}
365
369
371 doublereal calls[7];
372 doublereal time[2];
373 Report r;
374 using stat_t = decltype(r.status);
375 r.status = static_cast<stat_t>(implementation->get_report(calls, time));
376 r.name = implementation->get_name();
377 r.nvar = implementation->nvar;
378 r.ncon = implementation->ncon;
379 r.calls.objective = calls[0];
380 r.calls.objective_grad = calls[1];
381 r.calls.objective_hess = calls[2];
382 r.calls.hessian_times_vector = calls[3];
383 r.calls.constraints = implementation->ncon > 0 ? calls[4] : 0;
384 r.calls.constraints_grad = implementation->ncon > 0 ? calls[5] : 0;
385 r.calls.constraints_hess = implementation->ncon > 0 ? calls[6] : 0;
386 r.time_setup = time[0];
387 r.time = time[1];
388 return r;
389}
390
392 using Status = CUTEstProblem::Report::Status;
393 switch (s) {
394 case Status::Success: return "Success";
395 case Status::AllocationError: return "AllocationError";
396 case Status::ArrayBoundError: return "ArrayBoundError";
397 case Status::EvaluationError: return "EvaluationError";
398 }
399 throw std::out_of_range(
400 "invalid value for alpaqa::CUTEstProblem::Report::Status");
401}
402
403std::ostream &operator<<(std::ostream &os, CUTEstProblem::Report::Status s) {
404 return os << enum_name(s);
405}
406
407std::ostream &operator<<(std::ostream &os, const CUTEstProblem::Report &r) {
408 os << "CUTEst problem: " << r.name << "\r\n\n" //
409 << "Number of variables: " << r.nvar << "\r\n" //
410 << "Number of constraints: " << r.ncon << "\r\n\n" //
411 << "Status: " << r.status << " (" << +r.status << ")\r\n\n" //
412 << "Objective function evaluations: " << r.calls.objective
413 << "\r\n"
414 << "Objective function gradient evaluations: "
415 << r.calls.objective_grad << "\r\n"
416 << "Objective function Hessian evaluations: "
417 << r.calls.objective_hess << "\r\n"
418 << "Hessian times vector products: "
419 << r.calls.objective_hess << "\r\n\n";
420 if (r.ncon > 0) {
421 os << "Constraint function evaluations: "
422 << r.calls.constraints << "\r\n"
423 << "Constraint function gradients evaluations: "
424 << r.calls.constraints_grad << "\r\n"
425 << "Constraint function Hessian evaluations: "
426 << r.calls.constraints_hess << "\r\n\n";
427 }
428 return os << "Setup time: " << r.time_setup << "s\r\n"
429 << "Time since setup: " << r.time << "s";
430}
const char * enum_name(CUTEstProblem::Report::Status s)
doublereal eval_objective_unconstrained(alpaqa::crvec x) const
void * eval_constr_grad_prod_p
alpaqa::vec y
lagrange multipliers
void eval_constraints_grad_prod(alpaqa::crvec x, alpaqa::crvec v, alpaqa::rvec grad_g_v) const
integer ncon
Number of constraints.
static constexpr auto call_as(void *funp)
alpaqa::vec x
decision variable
void eval_objective_grad_unconstrained(alpaqa::crvec x, alpaqa::rvec grad_f) const
alpaqa::vec c_u
upper bounds on constraints
CUTEstLoader(const char *so_fname, const char *outsdif_fname)
doublereal eval_objective_constrained(alpaqa::crvec x) const
integer iout
Fortran Unit Number for standard output.
integer get_report(doublereal *calls, doublereal *time)
std::string get_name()
integer funit
Fortran Unit Number for OUTSDIF.d file.
alpaqa::vec x_l
lower bound on x
static void throw_if_error(std::string s, int code)
alpaqa::vec x_u
upper bound on x
Eigen::Matrix< logical, Eigen::Dynamic, 1 > logical_vec
integer nvar
Number of decision variabls.
T * dlfun(const char *name)
void eval_lagr_hess(alpaqa::crvec x, alpaqa::crvec y, alpaqa::rmat H) const
void eval_constraints(alpaqa::crvec x, alpaqa::rvec g) const
void eval_objective_grad_constrained(alpaqa::crvec x, alpaqa::rvec grad_f) const
void * eval_lagr_hess_prod_p
unsigned count_box_constraints() const
void * eval_obj_grad_p
void eval_lagr_hess_prod(alpaqa::crvec x, alpaqa::crvec y, alpaqa::crvec v, alpaqa::rvec Hv) const
void * eval_constr_i_grad_p
void * so_handle
dlopen handle to shared library
alpaqa::vec work
work vector
void * eval_lagr_hess_p
logical_vec equatn
whether the constraint is an equality
static void throw_error(std::string s, int code)
void eval_constraint_i_grad(alpaqa::crvec x, unsigned i, alpaqa::rvec grad_gi) const
logical_vec linear
whether the constraint is linear
void * eval_constr_p
integer io_buffer
Fortran Unit Number for internal IO.
alpaqa::vec c_l
lower bounds on constraints
Wrapper for CUTEst problems loaded from an external shared library.
alpaqa::vec x0
Initial value of decision variables.
unsigned number_box_constraints
The number of box constraints on x.
std::string name
Problem name.
CUTEstProblem(const char *so_fname, const char *outsdif_fname)
Load a CUTEst problem from the given shared library and OUTSDIF.d file.
alpaqa::vec y0
Initial value of Lagrange multipliers.
Report get_report() const
alpaqa::Problem problem
Problem statement (bounds, objective, constraints)
std::unique_ptr< class CUTEstLoader > implementation
CUTEstProblem & operator=(CUTEstProblem &&)
Eigen::Ref< const vec > crvec
Default type for immutable references to vectors.
Definition: vec.hpp:18
vec upperbound
Definition: box.hpp:8
realvec vec
Default type for vectors.
Definition: vec.hpp:14
vec lowerbound
Definition: box.hpp:9
Eigen::Ref< mat > rmat
Default type for mutable references to matrices.
Definition: vec.hpp:22
double real_t
Default floating point type.
Definition: vec.hpp:8
Eigen::Ref< vec > rvec
Default type for mutable references to vectors.
Definition: vec.hpp:16
H
Definition: main.py:8
The report generated by CUTEst.
double time_setup
CPU time (in seconds) for CUTEST_csetup.
std::ostream & operator<<(std::ostream &, const CUTEstProblem::Report &)
struct CUTEstProblem::Report::@1 calls
Function call counters.
Status
Status returned by CUTEst.
std::string name
Name of the problem.
unsigned ncon
Number of constraints.
double time
CPU time (in seconds) since the end of CUTEST_csetup.
unsigned nvar
Number of independent variables.
enum CUTEstProblem::Report::Status status
Exit status.
Box C
Constraints of the decision variables, .
std::function< hess_L_sig > hess_L
Hessian of the Lagrangian function .
std::function< f_sig > f
Cost function .
std::function< grad_gi_sig > grad_gi
Gradient of a specific constraint .
unsigned int m
Number of constraints, dimension of g(x) and z.
std::function< grad_f_sig > grad_f
Gradient of the cost function .
unsigned int n
Number of decision variables, dimension of x.
std::function< hess_L_prod_sig > hess_L_prod
Hessian of the Lagrangian function times vector .
std::function< g_sig > g
Constraint function .
Box D
Other constraints, .
std::function< grad_g_prod_sig > grad_g_prod
Gradient of the constraint function times vector .