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 a pointer to 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 std::any *
. Inside of the std::any
, there is a span of string views containing the problem-specific command line options.
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 <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;
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_f(
const real_t *x_)
const {
Ax.noalias() = A * x;
return μ * logistic_loss(Ax);
}
void eval_grad_f(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_hess_L_prod(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_hess_ψ_prod(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_hess_L_prod(x, y, scale, v, Hv);
}
void eval_hess_L(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_hess_ψ(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_hess_L(x, y, scale, H);
}
real_t eval_f_grad_f(
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_g(const real_t *, real_t *) const {}
void eval_grad_g_prod(const real_t *, const real_t *, real_t *gr_) const {
}
void eval_jac_g(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)
}
std::string get_name() const {
return "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.n = n;
funcs.m = 0;
funcs.eval_f = member_caller<&P::eval_f>();
funcs.eval_grad_f = member_caller<&P::eval_grad_f>();
funcs.eval_f_grad_f = member_caller<&P::eval_f_grad_f>();
funcs.eval_g = member_caller<&P::eval_g>();
funcs.eval_grad_g_prod = member_caller<&P::eval_grad_g_prod>();
funcs.eval_jac_g = member_caller<&P::eval_jac_g>();
funcs.eval_hess_L_prod = member_caller<&P::eval_hess_L_prod>();
funcs.eval_hess_ψ_prod = member_caller<&P::eval_hess_ψ_prod>();
funcs.eval_hess_L = member_caller<&P::eval_hess_L>();
funcs.eval_hess_ψ = member_caller<&P::eval_hess_ψ>();
if (λ > 0)
funcs.initialize_l1_reg = member_caller<&P::initialize_l1_reg>();
}
};
alpaqa_problem_register(void *user_data_v) noexcept try {
if (!user_data_v)
throw std::invalid_argument("Missing user data");
const auto &user_data = *reinterpret_cast<std::any *>(user_data_v);
using param_t = std::span<std::string_view>;
auto opts = std::any_cast<param_t>(user_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 reinterpret_cast<Problem *>(instance);
};
return result;
} catch (...) {
}
#define USING_ALPAQA_CONFIG(Conf)
void read_row(std::istream &is, Eigen::Ref< Eigen::VectorX< float > > v, char sep)
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.
typename Conf::real_t real_t
void register_member_function(Result &result, std::string name, Ret(T::*member)(Args...))
typename Conf::cmvec cmvec
static auto member_caller()
Wrap the given member function or variable into a (possibly generic) lambda function that accepts the...
Double-precision double configuration.
C API providing function pointers to problem functions.
void(* cleanup)(void *)
Pointer to the function to clean up instance.
alpaqa_exception_ptr_t * exception
Pointer to an exception that ocurred during problem creation.
void * instance
Owning pointer.
alpaqa_problem_functions_t * functions
Non-owning pointer, lifetime at least as long as instance.