This is an example that builds a problem in C++ that can be loaded dynamically by the alpaqa solvers.
This is an example that builds a problem in C++ that can be loaded dynamically by the alpaqa solvers. The problem is a simple sparse logistic regression task.
The entry point also accepts an argument with type-erased user data. You can use this to pass any additional data to the problem constructor, such as problem parameters. When using the alpaqa-driver program, the type of this user data is always a span of string views containing the problem-specific command line options (indicated by type being set to alpaqa_register_arg_strings).
In this example, the datafile and λ_factor options are supported. The former selects the CSV file to load the data from, the latter controls the regularization parameter.
#include <sparse-logistic-regression/export.h>
#include <guanaqo/io/csv.hpp>
#include <algorithm>
#include <any>
#include <cassert>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <random>
#include <span>
#include <stdexcept>
#include <string_view>
#include <utility>
namespace fs = std::filesystem;
struct Problem {
length_t n;
length_t m;
real_t λ;
real_t μ;
mat A;
vec b;
vec Aᵀb;
mutable vec Ax;
fs::path data_file;
std::string name;
real_t logistic_loss(crvec x) const {
auto &&xa = x.array();
auto &&ba = b.array();
return ((-ba * xa).exp() + 1).log().sum();
}
void neg_deriv_logistic_loss(crvec x, rvec g) const {
auto &&xa = x.array();
auto &&ba = b.array();
g = ba / ((ba * xa).exp() + 1);
}
void sigmoid2(crvec x, rvec g) const {
auto &&xa = x.array();
auto &&ba = b.array();
g = (ba * xa).exp();
g = ba * g.array() / ((g.array() + 1) * (g.array() + 1));
}
real_t eval_objective(
const real_t *x_)
const {
Ax.noalias() = A * x;
return μ * logistic_loss(Ax);
}
void eval_objective_gradient(const real_t *x_, real_t *g_) const {
Ax.noalias() = A * x;
neg_deriv_logistic_loss(Ax, Ax);
g.noalias() = -μ * (A.transpose() * Ax);
}
void eval_hess_f_prod(const real_t *x_, const real_t *v_,
real_t *Hv_) const {
Ax.noalias() = A * x;
sigmoid2(Ax, Ax);
Ax.noalias() = Ax.asDiagonal() * (A * v);
Hv.noalias() = μ * (A.transpose() * Ax);
}
void eval_hess_f(const real_t *x_, real_t *H_) const {
Ax.noalias() = A * x;
sigmoid2(Ax, Ax);
H.noalias() = A.transpose() * (μ * Ax.asDiagonal()) * A;
}
void eval_lagrangian_hessian_product(const real_t *x,
[[maybe_unused]] const real_t *y,
real_t scale, const real_t *v,
real_t *Hv) const {
eval_hess_f_prod(x, v, Hv);
if (scale != 1)
}
void eval_augmented_lagrangian_hessian_product(
const real_t *x, const real_t *y, [[maybe_unused]] const real_t *Σ,
real_t scale, [[maybe_unused]] const real_t *zl,
[[maybe_unused]] const real_t *zu, const real_t *v, real_t *Hv) const {
eval_lagrangian_hessian_product(x, y, scale, v, Hv);
}
void eval_lagrangian_hessian(const real_t *x,
[[maybe_unused]] const real_t *y, real_t scale,
real_t *H) const {
eval_hess_f(x, H);
if (scale != 1)
}
void eval_augmented_lagrangian_hessian(const real_t *x, const real_t *y,
[[maybe_unused]] const real_t *Σ,
real_t scale,
[[maybe_unused]] const real_t *zl,
[[maybe_unused]] const real_t *zu,
real_t *H) const {
eval_lagrangian_hessian(x, y, scale, H);
}
real_t eval_objective_and_gradient(
const real_t *x_, real_t *g_)
const {
Ax.noalias() = A * x;
real_t f = μ * logistic_loss(Ax);
neg_deriv_logistic_loss(Ax, Ax);
g.noalias() = -μ * (A.transpose() * Ax);
return f;
}
void eval_constraints(const real_t *, real_t *) const {}
void eval_constraints_gradient_product(const real_t *, const real_t *,
real_t *gr_) const {
}
void eval_constraints_jacobian(const real_t *, real_t *) const {}
void initialize_l1_reg(real_t *lambda, length_t *size) const {
if (!lambda) {
*size = 1;
} else {
assert(*size == 1);
*lambda = λ;
}
}
void load_data() {
std::ifstream csv_file{data_file};
if (!csv_file)
throw std::runtime_error("Unable to open file '" +
data_file.string() + "'");
csv_file >> m >> n;
csv_file.ignore(1, '\n');
if (!csv_file)
throw std::runtime_error(
"Unable to read dimensions from data file");
b.resize(m);
A.resize(m, n);
Aᵀb.resize(n);
Ax.resize(m);
for (length_t i = 0; i < n; ++i)
name = "sparse logistic regression (\"" + data_file.string() + "\")";
}
Problem(fs::path csv_filename, real_t λ_factor)
: data_file(std::move(csv_filename)) {
load_data();
Aᵀb.noalias() = A.transpose() * b;
real_t λ_max = Aᵀb.lpNorm<Eigen::Infinity>() /
static_cast<real_t>(m);
λ = λ_factor * λ_max;
μ = 1. /
static_cast<real_t>(m);
using P = Problem;
funcs.
name = name.c_str();
if (λ > 0)
}
};
if (!user_data_v.data)
throw std::invalid_argument("Missing user data");
throw std::invalid_argument("Invalid user data type");
using param_t = std::span<std::string_view>;
const auto &opts = *reinterpret_cast<param_t *>(user_data_v.data);
std::vector<unsigned> used(opts.size());
std::string_view datafilename;
if (datafilename.empty())
throw std::invalid_argument("Missing option problem.datafile");
auto unused_opt = std::find(used.begin(), used.end(), 0);
auto unused_idx = static_cast<size_t>(unused_opt - used.begin());
if (unused_opt != used.end())
throw std::invalid_argument("Unused problem option: " +
std::string(opts[unused_idx]));
auto problem = std::make_unique<Problem>(datafilename, λ_factor);
result.
cleanup = [](
void *instance) {
delete static_cast<Problem *>(instance);
};
return result;
} catch (...) {
}
register_alpaqa_problem_version() {
}
#define USING_ALPAQA_CONFIG(Conf)
#define ALPAQA_DL_ABI_VERSION
alpaqa_length_t num_variables
Number of decision variables.
@ alpaqa_register_arg_strings
The data pointer points to a dynamic C++ std::span of std::string_view.
void(* cleanup)(void *)
Pointer to the function to clean up instance.
void(* eval_objective_gradient)(void *instance, const alpaqa_real_t *x, alpaqa_real_t *grad_fx)
Gradient of the cost function.
void(* eval_lagrangian_hessian_product)(void *instance, const alpaqa_real_t *x, const alpaqa_real_t *y, alpaqa_real_t scale, const alpaqa_real_t *v, alpaqa_real_t *Hv)
Hessian-vector product of the Lagrangian.
uint64_t alpaqa_dl_abi_version_t
alpaqa_real_t(* eval_objective)(void *instance, const alpaqa_real_t *x)
Cost function.
void(* eval_augmented_lagrangian_hessian_product)(void *instance, const alpaqa_real_t *x, const alpaqa_real_t *y, const alpaqa_real_t *Σ, alpaqa_real_t scale, const alpaqa_real_t *zl, const alpaqa_real_t *zu, const alpaqa_real_t *v, alpaqa_real_t *Hv)
Hessian-vector product of the augmented Lagrangian.
struct alpaqa_exception_ptr_s alpaqa_exception_ptr_t
Opaque type for a C++-only exception pointer.
const char * name
Name of the problem.
void(* initialize_l1_reg)(void *instance, alpaqa_real_t *lambda, alpaqa_length_t *size)
Provide the initial values for l1_reg, the ℓ₁-regularization factor.
alpaqa_length_t num_constraints
Number of constraints.
alpaqa_real_t(* eval_objective_and_gradient)(void *instance, const alpaqa_real_t *x, alpaqa_real_t *grad_fx)
Cost and its gradient.
void(* eval_constraints_jacobian)(void *instance, const alpaqa_real_t *x, alpaqa_real_t *J_values)
Jacobian of the constraints function.
void(* eval_constraints)(void *instance, const alpaqa_real_t *x, alpaqa_real_t *gx)
Constraints function.
void(* eval_constraints_gradient_product)(void *instance, const alpaqa_real_t *x, const alpaqa_real_t *y, alpaqa_real_t *grad_gxy)
Gradient-vector product of the constraints function.
void(* eval_lagrangian_hessian)(void *instance, const alpaqa_real_t *x, const alpaqa_real_t *y, alpaqa_real_t scale, alpaqa_real_t *H_values)
Hessian of the Lagrangian.
void(* eval_augmented_lagrangian_hessian)(void *instance, const alpaqa_real_t *x, const alpaqa_real_t *y, const alpaqa_real_t *Σ, alpaqa_real_t scale, const alpaqa_real_t *zl, const alpaqa_real_t *zu, alpaqa_real_t *H_values)
Hessian of the augmented Lagrangian.
void * instance
Owning pointer.
alpaqa_problem_functions_t * functions
Non-owning pointer, lifetime at least as long as instance.
C API providing function pointers to problem functions.
User-provided argument that is passed to the problem registration functions.
void set_params(T &t, std::string_view prefix, std::span< const std::string_view > options, std::optional< std::span< unsigned > > used=std::nullopt)
Overwrites t based on the options that start with prefix.
auto as_span(Eigen::DenseBase< Derived > &v)
Convert an Eigen vector view to a std::span.
EigenConfigd DefaultConfig
typename Conf::real_t real_t
typename Conf::cmvec cmvec
static auto member_caller()
Wrap the given member function or variable into a (possibly generic) lambda function that accepts the...