11#include <alpaqa-version.h>
23#ifdef ALPAQA_HAVE_CASADI
24#include <casadi/config.h>
26#ifdef ALPAQA_WITH_JSON
27#include <nlohmann/json_fwd.hpp>
30#include <IpoptConfig.h>
44namespace fs = std::filesystem;
45using namespace std::string_view_literals;
51 dl: Dynamically loaded problem using the DLProblem class.
52 Specify the name of the registration function using the
53 problem.register option, e.g. problem.register=register_alpaqa_problem.
54 Further options can be passed to the problem using
55 problem.<key>[=<value>].
56 cs: Load a CasADi problem using the CasADiProblem class.
57 If a .tsv file with the same name as the shared library file exists,
58 the bounds and parameters will be loaded from that file. See
59 CasADiProblem::load_numerical_data for more details.
60 The problem parameter can be set using the problem.param option.
61 cu: Load a CUTEst problem using the CUTEstProblem class.
65 PANOC solver with the given direction.
66 Directions include: lbfgs, struclbfgs, anderson.
67 zerofpr[.<direction>]:
68 ZeroFPR solver, supports the same directions as PANOC.
70 PANTR solver. Requires products with the Hessian of the augmented
71 Lagrangian (unless dir.finite_diff=true).
73 FISTA (fast iterative shrinkage-thresholding algorithm). Only for
76 Ipopt interior point solver. Requires Jacobian of the constraints
77 and Hessian of the Lagrangian (unless finite memory is enabled).
79 QPALM proximal ALM QP solver. Assumes that the problem is a QP.
80 Requires Jacobian of the constraints and Hessian of the Lagrangian.
83 Solver-specific options can be specified as key-value pairs, where the
84 keys use periods to access struct members. For example,
85 solver.Lipschitz.L_0=1e3.
87 solver: Parameters for the main (inner) solver.
88 alm: Parameters for the outer ALM solver (if applicable).
89 dir: Parameters for solver's direction provider (if applicable).
90 accel: Parameters for direction's accelerator (if applicable).
91 out: File to write output to (default: -, i.e. standard output).
92 sol: Folder to write the solutions (and optional statistics) to.
93 x0: Initial guess for the solution.
94 mul_g0: Initial guess for the multipliers of the general constraints.
95 mul_x0: Initial guess for the multipliers of the bound constraints on x.
96 num_exp: Repeat the experiment this many times for more accurate timings.
97 extra_stats: Log more per-iteration solver statistics, such as step sizes,
98 Newton step acceptance, and residuals. Requires `sol' to be set.
100 The prefix @ can be added to the values of x0, mul_g0 and mul_x0 to read
101 the values from the given CSV file.
103 Options can be loaded from a JSON file by using an @ prefix. For example,
104 an argument @options.json loads the options from a file options.json in the
105 current directory. Multiple JSON files are processed in the order they
106 appear in the command line arguments. Options specified on the command line
107 always have precedence over options in a JSON file, regardless of order.
110 alpaqa-driver problem.so \
111 problem.register=register_alpaqa_problem \
112 problem.custom_arg=foo \
113 method=panoc.struclbfgs \
115 alm.{tolerance,dual_tolerance}=1e-8 \
116 solver.print_interval=50 \
119 alpaqa-driver cs:build/casadi_problem.so \
120 @options/default.json \
121 problem.param=1,2,3 \
123 solver.tol=1e-8 solver.constr_viol_tol=1e-8 \
124 solver.warm_start_init_point=yes \
126 mul_g0=@/some/other/file.csv \
127 mul_x0=@/yet/another/file.csv
131 const auto *opts =
" [<problem-type>:][<path>/]<name> [method=<solver>] "
132 "[<key>=<value>...]\n";
133 std::cout <<
"alpaqa-driver " ALPAQA_VERSION_FULL
" (" << alpaqa_build_time
135 " Command-line interface to the alpaqa solvers.\n"
136 " alpaqa is published under the LGPL-3.0.\n"
137 " https://github.com/kul-optec/alpaqa"
140 << a0 << opts <<
docs << std::endl;
141 std::cout <<
"Third-party libraries:\n"
142 <<
" * Eigen " << EIGEN_WORLD_VERSION <<
'.'
143 << EIGEN_MAJOR_VERSION <<
'.' << EIGEN_MINOR_VERSION
144 <<
" (https://gitlab.com/libeigen/eigen) - MPL-2.0\n"
145#ifdef ALPAQA_HAVE_CASADI
146 <<
" * CasADi " CASADI_VERSION_STRING
147 " (https://github.com/casadi/casadi) - LGPL-3.0-or-later\n"
149#ifdef ALPAQA_HAVE_CUTEST
151 " (https://github.com/ralna/CUTEst) - BSD-3-Clause\n"
154 <<
" * L-BFGS-B 3.0 "
155 "(http://users.iems.northwestern.edu/~nocedal/lbfgsb.html) - "
159 <<
" * Ipopt " IPOPT_VERSION
160 " (https://github.com/coin-or/Ipopt) - EPL-2.0\n"
161 <<
" * MUMPS (https://mumps-solver.org) - CECILL-C\n"
162 <<
" * OpenBLAS (https://github.com/OpenMathLib/OpenBLAS) - "
166 <<
" * QPALM " QPALM_VERSION_STR
167 " (https://github.com/kul-optec/QPALM) - LGPL-3.0\n"
169#ifdef ALPAQA_WITH_JSON
170 <<
" * nlohmann/json " << NLOHMANN_JSON_VERSION_MAJOR <<
'.'
171 << NLOHMANN_JSON_VERSION_MINOR <<
'.'
172 << NLOHMANN_JSON_VERSION_PATCH
173 <<
" (https://github.com/nlohmann/json) - MIT\n"
179 std::cout << ALPAQA_VERSION_FULL
" (" << ALPAQA_BUILD_TIME <<
")\n";
185 auto tok_pos = s.find(tok);
186 if (tok_pos == s.npos)
187 return std::make_tuple(std::string_view{}, s);
188 std::string_view key{s.begin(), s.begin() + tok_pos};
189 std::string_view rem{s.begin() + tok_pos + 1, s.end()};
190 return std::make_tuple(key, rem);
194 std::string out_path =
"-";
197 if (out_fstream.open(out_path); !out_fstream)
198 throw std::runtime_error(
"Unable to open '" + out_path +
"'");
199 return out_fstream.is_open() ? out_fstream : std::cout;
203 std::string sol_path;
209 bool rel_to_exe = argv[1][0] ==
'^';
210 std::string_view prob_path_s = argv[1] +
static_cast<ptrdiff_t
>(rel_to_exe);
211 std::string_view prob_type;
212 std::tie(prob_type, prob_path_s) =
split_once(prob_path_s,
':');
213 fs::path prob_path{prob_path_s};
215 prob_path = fs::canonical(fs::path(argv[0])).parent_path() / prob_path;
216 return std::make_tuple(std::move(prob_path), prob_type);
220 os <<
"Loaded problem \"" << problem.
name <<
"\"\n"
221 <<
"Number of variables: " << problem.
problem.
get_n() <<
"\n"
222 <<
"Number of constraints: " << problem.
problem.
get_m() <<
"\n";
224 os <<
"Box constraints:"
231 os <<
"General constraints:"
237 os <<
"Provided functions:\n";
242 std::string method =
"panoc", direction;
246 std::map<std::string_view, solver_builder_func> solvers{
253 auto solver_it = solvers.find(method);
254 if (solver_it == solvers.end())
255 throw std::invalid_argument(
256 "Unknown solver '" + std::string(method) +
"'\n" +
257 " Available solvers: " +
260 return std::make_tuple(std::move(solver_it->second), direction);
265 std::span<const char *> argv) {
267 auto timestamp_str = std::to_string(results.
timestamp);
270 if (name ==
"PROBLEM")
272 auto suffix =
'_' + name +
'_' + timestamp_str +
'_' + rnd_str;
273 fs::create_directories(sol_output_dir);
274 std::array solutions{
275 std::tuple{
"solution",
"sol_x", &sol_res.solution},
276 std::tuple{
"multipliers for g",
"mul_g", &sol_res.multipliers},
277 std::tuple{
"multipliers for x",
"mul_x", &sol_res.multipliers_bounds},
279 for (
auto [name, fname, value] : solutions) {
280 if (value->size() == 0)
282 auto pth = sol_output_dir / (std::string(fname) + suffix +
".csv");
283 os <<
"Writing " << name <<
" to " << pth << std::endl;
284 std::ofstream output_file(pth);
288 auto pth = sol_output_dir / (
"cmdline" + suffix +
".txt");
289 os <<
"Writing arguments to " << pth << std::endl;
290 std::ofstream output_file(pth);
291 for (
const char *arg : argv)
292 output_file << std::quoted(arg,
'\'') <<
' ';
295 if (solver->has_statistics()) {
296 auto pth = sol_output_dir / (
"stats" + suffix +
".csv");
297 os <<
"Writing statistics to " << pth << std::endl;
298 std::ofstream output_file(pth);
299 solver->write_statistics_to_stream(output_file);
303int main(
int argc,
const char *argv[])
try {
305 SetConsoleOutputCP(CP_UTF8);
314 if (argv[1] ==
"-h"sv || argv[1] ==
"--help"sv || argv[1] ==
"?"sv)
316 if (argv[1] ==
"-v"sv || argv[1] ==
"--version"sv)
319 std::span args{argv,
static_cast<size_t>(argc)};
320 Options opts{argc - 2, argv + 2};
323 std::ofstream out_fstream;
336 auto solver = solver_builder(direction, opts);
339 os <<
"Loading " << prob_path <<
" ..." << std::endl;
340 auto problem =
load_problem(prob_type, prob_path.parent_path(),
341 prob_path.filename(), opts);
346 auto used = opts.used();
347 auto unused_opt = std::ranges::find(used, 0);
348 auto unused_idx =
static_cast<size_t>(unused_opt - used.begin());
349 if (unused_opt != used.end())
350 throw std::invalid_argument(
"Unused option: " +
351 std::string(opts.options()[unused_idx]));
354 auto solver_results = solver->run(problem, os);
357 real_t f = problem.problem.eval_f(solver_results.solution);
359 problem.problem, solver_results.solution, solver_results.multipliers);
362 .solver_results = solver_results,
363 .objective = f + solver_results.h,
364 .smooth_objective = f,
366 .options = opts.options(),
367 .timestamp = timestamp_ms<std::chrono::system_clock>().count(),
374 if (!sol_output_dir.empty())
377}
catch (std::exception &e) {
379 << e.what() << std::endl;
auto get_solver_builder(Options &opts)
std::string get_output_paths(Options &opts)
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)
void print_problem_description(std::ostream &os, LoadedProblem &problem)
auto split_once(std::string_view s, char tok='.')
Split the string s on the first occurrence of tok.
int main(int argc, const char *argv[])
length_t get_n() const
[Required] Number of decision variables.
length_t get_m() const
[Required] Number of constraints.
#define USING_ALPAQA_CONFIG(Conf)
std::string demangled_typename(const std::type_info &t)
Get the pretty name of the given type as a string.
SharedSolverWrapper make_fista_driver(std::string_view direction, Options &opts)
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.
std::string join(std::ranges::input_range auto strings, join_opt opt={})
Join the list of strings into a single string, using the separator given by opt.
std::ostream & print_csv(std::ostream &os, const Eigen::DenseBase< Derived > &M, Args &&...args)
KKTError< Conf > compute_kkt_error(const TypeErasedProblem< Conf > &problem, crvec< Conf > x, crvec< Conf > y)
decltype(auto) set_params(T &t, std::string_view prefix, Options &opts)
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)
std::optional< ConstrCount > general_constr_count
alpaqa::TypeErasedProblem< config_t > problem
std::optional< ConstrCount > box_constr_count
SharedSolverWrapper make_qpalm_driver(std::string_view, Options &)
std::string random_hex_string(auto &&rng)
void print_results(std::ostream &os, const BenchmarkResults &results)
SolverResults solver_results
Double-precision double configuration.