alpaqa 1.0.0a13
Nonconvex constrained optimization
Loading...
Searching...
No Matches
alpaqa-driver.cpp
Go to the documentation of this file.
1#ifdef _WIN32
2#define NOMINMAX
3#include <Windows.h>
4#endif
5
10#include <alpaqa-version.h>
11
12#include "ipopt-driver.hpp"
13#include "lbfgsb-driver.hpp"
14#include "options.hpp"
15#include "panoc-driver.hpp"
16#include "pantr-driver.hpp"
17#include "qpalm-driver.hpp"
18#include "results.hpp"
19#include "solver-driver.hpp"
20#include "util.hpp"
21
22#ifdef ALPAQA_HAVE_CASADI
23#include <casadi/config.h>
24#endif
25#ifdef WITH_IPOPT
26#include <IpoptConfig.h>
27#endif
28
29#include <algorithm>
30#include <filesystem>
31#include <fstream>
32#include <iostream>
33#include <random>
34#include <span>
35#include <stdexcept>
36#include <string>
37#include <string_view>
38#include <tuple>
39#include <type_traits>
40namespace fs = std::filesystem;
41
43
44const auto *docs = R"==(
45problem types:
46 dl: Dynamically loaded problem using the DLProblem class.
47 Specify the name of the registration function using the
48 problem.register option, e.g. problem.register=register_alpaqa_problem.
49 Further options can be passed to the problem using
50 problem.<key>[=<value>].
51 cs: Load a CasADi problem using the CasADiProblem class.
52 If a .tsv file with the same name as the shared library file exists,
53 the bounds and parameters will be loaded from that file. See
54 CasADiProblem::load_numerical_data for more details.
55 The problem parameter can be set using the problem.param option.
56 cu: Load a CUTEst problem using the CUTEstProblem class.
57
58methods:
59 panoc[.<direction>]:
60 PANOC solver with the given direction.
61 Directions include: lbfgs, struclbfgs, anderson.
62 zerofpr[.<direction>]:
63 ZeroFPR solver, supports the same directions as PANOC.
64 pantr:
65 PANTR solver. Requires products with the Hessian of the augmented
66 Lagrangian (unless dir.finite_diff=true).
67 ipopt:
68 Ipopt interior point solver. Requires Jacobian of the constraints
69 and Hessian of the Lagrangian (unless finite memory is enabled).
70 qpalm:
71 QPALM proximal ALM QP solver. Assumes that the problem is a QP.
72 Requires Jacobian of the constraints and Hessian of the Lagrangian.
73
74options:
75 Solver-specific options can be specified as key-value pairs, where the
76 keys use periods to access struct members. For example,
77 solver.Lipschitz.L_0=1e3.
78
79 solver: Parameters for the main (inner) solver.
80 alm: Parameters for the outer ALM solver (if applicable).
81 dir: Parameters for solver's direction provider (if applicable).
82 accel: Parameters for direction's accelerator (if applicable).
83 out: File to write output to (default: -, i.e. standard output).
84 sol: Folder to write the solutions (and optional statistics) to.
85 x0: Initial guess for the solution.
86 mul_g0: Initial guess for the multipliers of the general constraints.
87 mul_x0: Initial guess for the multipliers of the bound constraints on x.
88 num_exp: Repeat the experiment this many times for more accurate timings.
89 extra_stats: Log more per-iteration solver statistics, such as step sizes,
90 Newton step acceptance, and residuals. Requires `sol' to be set.
91
92 The prefix @ can be added to the values of x0, mul_g0 and mul_x0 to read
93 the values from the given CSV file.
94
95examples:
96 alpaqa-driver problem.so \
97 problem.register=register_alpaqa_problem \
98 problem.custom_arg=foo \
99 method=panoc.struclbfgs \
100 accel.memory=50 \
101 alm.{tolerance,dual_tolerance}=1e-8 \
102 solver.print_interval=50 \
103 x0=@/some/file.csv
104
105 alpaqa-driver cs:build/casadi_problem.so \
106 problem.param=1,2,3 \
107 method=ipopt \
108 solver.tol=1e-8 solver.constr_viol_tol=1e-8 \
109 solver.warm_start_init_point=yes \
110 x0=@/some/file.csv \
111 mul_g0=@/some/other/file.csv \
112 mul_x0=@/yet/another/file.csv
113)==";
114
115void print_usage(const char *a0) {
116 const auto *opts = " [<problem-type>:][<path>/]<name> [method=<solver>] "
117 "[<key>=<value>...]\n";
118 std::cout << "alpaqa-driver (" ALPAQA_VERSION_FULL ")\n\n"
119 " Command-line interface to the alpaqa solvers.\n"
120 " alpaqa is published under the LGPL-3.0.\n"
121 " https://github.com/kul-optec/alpaqa"
122 "\n\n"
123 " Usage: "
124 << a0 << opts << docs << std::endl;
125 std::cout
126 << "Third-party libraries:\n"
127 << " * Eigen " << EIGEN_WORLD_VERSION << '.' << EIGEN_MAJOR_VERSION
128 << '.' << EIGEN_MINOR_VERSION
129 << " (https://gitlab.com/libeigen/eigen) - MPL-2.0\n"
130#ifdef ALPAQA_HAVE_CASADI
131 << " * CasADi " CASADI_VERSION_STRING
132 " (https://github.com/casadi/casadi) - LGPL-3.0-or-later\n"
133#endif
134#ifdef ALPAQA_HAVE_CUTEST
135 << " * CUTEst"
136 " (https://github.com/ralna/CUTEst) - BSD-3-Clause\n"
137#endif
138#ifdef WITH_LBFGSB
139 << " * L-BFGS-B 3.0 "
140 "(http://users.iems.northwestern.edu/~nocedal/lbfgsb.html) - "
141 "BSD-3-Clause\n"
142#endif
143#ifdef WITH_IPOPT
144 << " * Ipopt " IPOPT_VERSION
145 " (https://github.com/coin-or/Ipopt) - EPL-2.0\n"
146 << " * MUMPS (https://mumps-solver.org) - CECILL-C\n"
147 << " * OpenBLAS (https://github.com/xianyi/OpenBLAS) - BSD-3-Clause\n"
148#endif
149#ifdef WITH_QPALM
150 << " * QPALM " QPALM_VERSION_STR
151 " (https://github.com/kul-optec/QPALM) - LGPL-3.0\n"
152#endif
153 << std::endl;
154}
155
156/// Split the string @p s on the first occurrence of @p tok.
157/// Returns ("", s) if tok was not found.
158auto split_once(std::string_view s, char tok = '.') {
159 auto tok_pos = s.find(tok);
160 if (tok_pos == s.npos)
161 return std::make_tuple(std::string_view{}, s);
162 std::string_view key{s.begin(), s.begin() + tok_pos};
163 std::string_view rem{s.begin() + tok_pos + 1, s.end()};
164 return std::make_tuple(key, rem);
165}
166
167std::ostream &get_output_stream(Options &opts, std::ofstream &out_fstream) {
168 std::string out_path = "-";
169 set_params(out_path, "out", opts);
170 if (out_path != "-")
171 if (out_fstream.open(out_path); !out_fstream)
172 throw std::runtime_error("Unable to open '" + out_path + "'");
173 return out_fstream.is_open() ? out_fstream : std::cout;
174}
175
176std::string_view get_output_paths(Options &opts) {
177 std::string_view sol_path;
178 set_params(sol_path, "sol", opts);
179 return sol_path;
180}
181
182auto get_problem_path(const char *const *argv) {
183 bool rel_to_exe = argv[1][0] == '^';
184 std::string_view prob_path_s = argv[1] + static_cast<ptrdiff_t>(rel_to_exe);
185 std::string_view prob_type;
186 std::tie(prob_type, prob_path_s) = split_once(prob_path_s, ':');
187 fs::path prob_path{prob_path_s};
188 if (rel_to_exe)
189 prob_path = fs::canonical(fs::path(argv[0])).parent_path() / prob_path;
190 return std::make_tuple(std::move(prob_path), prob_type);
191}
192
194 std::string_view method = "panoc", direction;
195 set_params(method, "method", opts);
196 std::tie(method, direction) = alpaqa::params::split_key(method, '.');
197 // Dictionary of available solver builders
198 std::map<std::string_view, solver_builder_func> solvers{
199 {"panoc", make_panoc_driver}, {"zerofpr", make_zerofpr_driver},
200 {"pantr", make_pantr_driver}, {"lbfgsb", make_lbfgsb_driver},
201 {"ipopt", make_ipopt_driver}, {"qpalm", make_qpalm_driver},
202 };
203 // Find the selected solver builder
204 auto solver_it = solvers.find(method);
205 if (solver_it == solvers.end())
206 throw std::invalid_argument(
207 "Unknown solver '" + std::string(method) + "'\n" +
208 " Available solvers: " +
209 format_string_list(solvers, [](const auto &x) { return x.first; }));
210 return std::make_tuple(std::move(solver_it->second), direction);
211}
212
213void store_solution(const fs::path &sol_output_dir, std::ostream &os,
214 BenchmarkResults &results, auto &solver,
215 std::span<const char *> argv) {
216 const auto &sol_res = results.solver_results;
217 auto timestamp_str = std::to_string(results.timestamp);
218 auto rnd_str = random_hex_string(std::random_device());
219 auto name = results.problem.path.stem().string();
220 if (name == "PROBLEM")
221 name = results.problem.name;
222 auto suffix = '_' + name + '_' + timestamp_str + '_' + rnd_str;
223 fs::create_directories(sol_output_dir);
224 std::array solutions{
225 std::tuple{"solution", "sol_x", &sol_res.solution},
226 std::tuple{"multipliers for g", "mul_g", &sol_res.multipliers},
227 std::tuple{"multipliers for x", "mul_x", &sol_res.multipliers_bounds},
228 };
229 for (auto [name, fname, value] : solutions) {
230 if (value->size() == 0)
231 continue;
232 auto pth = sol_output_dir / (std::string(fname) + suffix + ".csv");
233 os << "Writing " << name << " to " << pth << std::endl;
234 std::ofstream output_file(pth);
235 alpaqa::print_csv(output_file, *value);
236 }
237 {
238 auto pth = sol_output_dir / ("cmdline" + suffix + ".txt");
239 os << "Writing arguments to " << pth << std::endl;
240 std::ofstream output_file(pth);
241 for (const char *arg : argv)
242 output_file << std::quoted(arg, '\'') << ' ';
243 output_file << '\n';
244 }
245 if (solver->has_statistics()) {
246 auto pth = sol_output_dir / ("stats" + suffix + ".csv");
247 os << "Writing statistics to " << pth << std::endl;
248 std::ofstream output_file(pth);
249 solver->write_statistics_to_stream(output_file);
250 }
251}
252
253int main(int argc, const char *argv[]) try {
254#ifdef _WIN32
255 SetConsoleOutputCP(CP_UTF8);
256#endif
257 // Check command line options
258 if (argc < 1)
259 return -1;
260 if (argc == 1)
261 return print_usage(argv[0]), 0;
262 if (argc < 2)
263 return print_usage(argv[0]), -1;
264 std::span args{argv, static_cast<size_t>(argc)};
265 Options opts{argc - 2, argv + 2};
266
267 // Check where to write the output to
268 std::ofstream out_fstream;
269 std::ostream &os = get_output_stream(opts, out_fstream);
270
271 // Check which problem to load
272 auto [prob_path, prob_type] = get_problem_path(argv);
273
274 // Check which solver to use
275 auto [solver_builder, direction] = get_solver_builder(opts);
276
277 // Check output paths
278 fs::path sol_output_dir = get_output_paths(opts);
279
280 // Build solver
281 auto solver = solver_builder(direction, opts);
282
283 // Load problem
284 os << "Loading problem " << prob_path << std::endl;
285 auto problem = load_problem(prob_type, prob_path.parent_path(),
286 prob_path.filename(), opts);
287 os << "Loaded problem \"" << problem.name << "\" from " << problem.path
288 << "\nProvided functions:\n";
289 alpaqa::print_provided_functions(os, problem.problem);
290 os << std::endl;
291
292 // Check options
293 auto used = opts.used();
294 auto unused_opt = std::ranges::find(used, 0);
295 auto unused_idx = static_cast<size_t>(unused_opt - used.begin());
296 if (unused_opt != used.end())
297 throw std::invalid_argument("Unused option: " +
298 std::string(opts.options()[unused_idx]));
299
300 // Solve
301 auto solver_results = solver->run(problem, os);
302
303 // Compute more statistics
304 real_t f = problem.problem.eval_f(solver_results.solution);
305 auto kkt_err = alpaqa::compute_kkt_error(
306 problem.problem, solver_results.solution, solver_results.multipliers);
307 BenchmarkResults results{
308 .problem = problem,
309 .solver_results = solver_results,
310 .objective = f + solver_results.h,
311 .smooth_objective = f,
312 .error = kkt_err,
313 .options = opts.options(),
314 .timestamp = timestamp_ms<std::chrono::system_clock>().count(),
315 };
316
317 // Print results
318 print_results(os, results);
319
320 // Store solution
321 if (!sol_output_dir.empty())
322 store_solution(sol_output_dir, os, results, solver, args);
323
324} catch (std::exception &e) {
325 std::cerr << "Error: " << demangled_typename(typeid(e)) << ":\n "
326 << e.what() << std::endl;
327 return -1;
328}
auto get_solver_builder(Options &opts)
const auto * docs
std::ostream & get_output_stream(Options &opts, std::ofstream &out_fstream)
auto get_problem_path(const char *const *argv)
void print_usage(const char *a0)
void store_solution(const fs::path &sol_output_dir, std::ostream &os, BenchmarkResults &results, auto &solver, std::span< const char * > argv)
auto split_once(std::string_view s, char tok='.')
Split the string s on the first occurrence of tok.
std::string_view get_output_paths(Options &opts)
int main(int argc, const char *argv[])
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:56
std::string demangled_typename(const std::type_info &t)
Get the pretty name of the given type as a string.
void print_provided_functions(std::ostream &os, const TypeErasedProblem< Conf > &problem)
SharedSolverWrapper make_ipopt_driver(std::string_view, Options &)
SharedSolverWrapper make_lbfgsb_driver(std::string_view, Options &)
auto split_key(std::string_view full, char tok='.')
Split the string full on the first occurrence of tok.
Definition params.hpp:32
std::ostream & print_csv(std::ostream &os, const Eigen::DenseBase< Derived > &M, Args &&...args)
Definition print.hpp:67
KKTError< Conf > compute_kkt_error(const TypeErasedProblem< Conf > &problem, crvec< Conf > x, crvec< Conf > y)
Definition kkt-error.hpp:17
decltype(auto) set_params(T &t, std::string_view prefix, Options &opts)
Definition options.hpp:27
SharedSolverWrapper make_zerofpr_driver(std::string_view direction, Options &opts)
SharedSolverWrapper make_panoc_driver(std::string_view direction, Options &opts)
SharedSolverWrapper make_pantr_driver(std::string_view direction, Options &opts)
LoadedProblem load_problem(std::string_view type, const fs::path &dir, const fs::path &file, Options &opts)
Definition problem.cpp:152
std::string name
Definition problem.hpp:18
fs::path path
Definition problem.hpp:17
SharedSolverWrapper make_qpalm_driver(std::string_view, Options &)
std::string random_hex_string(auto &&rng)
Definition results.hpp:53
void print_results(std::ostream &os, const BenchmarkResults &results)
Definition results.hpp:136
LoadedProblem & problem
Definition results.hpp:45
int64_t timestamp
Definition results.hpp:50
SolverResults solver_results
Definition results.hpp:46
Double-precision double configuration.
Definition config.hpp:135
std::string format_string_list(const auto &container, const auto &proj=[](const auto &x) -> decltype(auto) { return x;})
Definition util.hpp:7