cppnumericalsolvers

a lightweight header-only C++17 library of numerical optimization methods for (un-)constrained nonlinear functions and expression templates

https://github.com/patwie/cppnumericalsolvers

Science Score: 54.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
    1 of 18 committers (5.6%) from academic institutions
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (13.4%) to scientific vocabulary

Keywords

bfgs constrained-optimization eigen expression-templates gradient-computation lbfgs lbfgs-solver lbfgsb-solver mathematics minimization minimization-algorithm newton numerical-optimization-methods optimization optimization-algorithms optimization-methods optimization-tools solver solvers
Last synced: 4 months ago · JSON representation ·

Repository

a lightweight header-only C++17 library of numerical optimization methods for (un-)constrained nonlinear functions and expression templates

Basic Info
  • Host: GitHub
  • Owner: PatWie
  • License: mit
  • Language: C++
  • Default Branch: main
  • Homepage:
  • Size: 700 KB
Statistics
  • Stars: 910
  • Watchers: 52
  • Forks: 205
  • Open Issues: 2
  • Releases: 3
Topics
bfgs constrained-optimization eigen expression-templates gradient-computation lbfgs lbfgs-solver lbfgsb-solver mathematics minimization minimization-algorithm newton numerical-optimization-methods optimization optimization-algorithms optimization-methods optimization-tools solver solvers
Created over 11 years ago · Last pushed 5 months ago
Metadata Files
Readme License Citation

README.md

CppNumericalSolvers: A Modern C++17 Header-Only Optimization Library

CppNumericalSolvers is a lightweight, header-only C++17 library for numerical optimization. It provides a suite of modern, efficient solvers for both unconstrained and constrained problems, designed for easy integration and high performance. A key feature is its use of function expression templates, which allow you to build complex objective functions on-the-fly without writing boilerplate code.

The library is built on Eigen3 and is distributed under the permissive MIT license, making it suitable for both academic and commercial projects.

Core Features

  • Header-Only & Easy Integration: Simply include the headers in your project. No complex build steps required.
  • Modern C++17 Design: Utilizes modern C++ features for a clean, type-safe, and expressive API.
  • Powerful Expression Templates: Compose complex objective functions from simpler parts using operator overloading (+, -, *). This avoids manual implementation of wrapper classes and promotes reusable code.
  • Comprehensive Solver Suite:
    • Unconstrained Solvers: Gradient Descent, Conjugate Gradient, Newton's Method, BFGS, L-BFGS, and Nelder-Mead.
    • Constrained Solvers: L-BFGS-B (for box constraints) and the Augmented Lagrangian method (for general equality and inequality constraints).
  • Automatic Differentiation Utilities: Includes tools to verify the correctness of your analytical gradients and Hessians using finite difference approximations.
  • Permissive MIT License: Free to use in any project, including commercial applications.

Quick Start: Basic Minimization

Here’s how to solve a simple unconstrained optimization problem. We'll minimize the function f(x)=5x₀² + 100x₁² + 5.

1. Define the Objective Function

First, create a class for your objective function that inherits from cppoptlib::function::FunctionCRTP. You need to implement operator() which returns the function value and optionally computes the gradient and Hessian.

```cpp

include

include "cppoptlib/function.h"

include "cppoptlib/solver/lbfgs.h"

// Use a CRTP-based class to define a 2D function with second-order derivatives. class MyObjective : public cppoptlib::function::FunctionCRTP { public: EIGENMAKEALIGNEDOPERATORNEW

// The objective function: f(x) = 5x₀² + 100x₁² + 5 ScalarType operator()(const VectorType &x, VectorType gradient, MatrixType *hessian) const { if (gradient) { (gradient)(0) = 10 * x(0); (gradient)(1) = 200 * x(1); } if (hessian) { (hessian)(0, 0) = 10; (hessian)(0, 1) = 0; (hessian)(1, 0) = 0; (*hessian)(1, 1) = 200; } return 5 * x(0) * x(0) + 100 * x(1) * x(1) + 5; } }; ```

2. Solve the Problem

Instantiate your function, pick a solver, set a starting point, and run the minimization.

```cpp int main() { MyObjective f; Eigen::Vector2d xinit; xinit << -10, 2;

// Choose a solver (L-BFGS is a great all-rounder) cppoptlib::solver::Lbfgs solver;

// Create the initial state for the solver auto initialstate = cppoptlib::function::FunctionState(xinit);

// Set a callback to print progress solver.SetCallback(cppoptlib::solver::PrintProgressCallback(std::cout));

// Run the minimization auto [solution, solverstate] = solver.Minimize(f, initialstate);

std::cout << "\nSolver finished!" << std::endl; std::cout << "Final Status: " << solver_state.status << std::endl; std::cout << "Found minimum at: " << solution.x.transpose() << std::endl; std::cout << "Function value: " << f(solution.x) << std::endl;

return 0; } ```

The Power of Function Expression Templates

Manually creating a new C++ class for every objective function is tedious, especially when objectives are just different combinations of standard cost terms. CppNumericalSolvers uses expression templates to let you build complex objective functions from modular, reusable "cost functions".

Let's demonstrate this with a practical example: Ridge Regression. The goal is to find model parameters, x, that minimize a combination of two terms:

  • Data Fidelity: How well does the model fit the data? We measure this with the squared error: ∥Ax−y∥².
  • Regularization: How complex is the model? We penalize complexity with the L2 norm: ∥x∥².

The final objective is a weighted sum: F(x) = ∥Ax−y∥² + λ∥x∥², where λ is a scalar weight that controls the trade-off.

With expression templates, we can define each term as a separate, reusable class and then combine them with a single line of C++: DataTerm + lambda * RegularizationTerm.


1. Define the Building Blocks

First, we create classes for our two cost terms. Each is a self-contained, differentiable function.

```cpp

include

include "cppoptlib/function.h"

include "cppoptlib/solver/lbfgs.h"

// The first term: Data Fidelity as Squared Error: f(x) = ||Ax - y||^2 class SquaredError : public cppoptlib::function::FunctionCRTP { private: const Eigen::MatrixXd &A; const Eigen::VectorXd &y;

public: SquaredError(const Eigen::MatrixXd &A, const Eigen::VectorXd &y) : A(A), y(y) {}

int GetDimension() const { return A.cols(); }

ScalarType operator()(const VectorType &x, VectorType *grad, MatrixType *hess) const {
    Eigen::VectorXd residual = A * x - y;
    if (grad) {
        *grad = 2 * A.transpose() * residual;
    }
    if (hess) {
        *hess = 2 * A.transpose() * A;
    }
    return residual.squaredNorm();
}

};

// The second term: L2 Regularization: g(x) = ||x||^2 class L2Regularization : public cppoptlib::function::FunctionCRTP { public: int dim; explicit L2Regularization(int d) : dim(d) {}

int GetDimension() const { return dim; }

ScalarType operator()(const VectorType &x, VectorType *grad, MatrixType *hess) const {
    if (grad) {
        *grad = 2 * x;
    }
    if (hess) {
        hess->setIdentity(dim, dim);
        *hess *= 2;
    }
    return x.squaredNorm();
}

}; ```

2. Compose and Solve in main

Now, we can combine these building blocks to create our final objective function and solve the problem.

```cpp int main() { // 1. Define the problem data Eigen::MatrixXd A(3, 2); A << 1, 2, 3, 4, 5, 6; Eigen::VectorXd y(3); y << 7, 8, 9;

const double lambda = 0.1; // Regularization weight

// 2. Create instances of our reusable cost functions
auto data_term = SquaredError(A, y);
auto reg_term = L2Regularization(A.cols());

// 3. Compose the final objective using expression templates!
// F(x) = (||Ax - y||^2) + lambda * (||x||^2)
auto objective_expr = data_term + lambda * reg_term;

// 4. Wrap the expression for the solver. The library automatically handles
// the combination of values, gradients, and Hessians.
cppoptlib::function::FunctionExpr objective(objective_expr);

// 5. Solve as usual
Eigen::VectorXd x_init = Eigen::VectorXd::Zero(A.cols());
cppoptlib::solver::Lbfgs<decltype(objective)> solver;

auto [solution, solver_state] = solver.Minimize(objective, cppoptlib::function::FunctionState(x_init));

std::cout << "Found minimum at: " << solution.x.transpose() << std::endl;
std::cout << "Function value: " << objective(solution.x) << std::endl;

} ```

Constrained Optimization

Solve constrained problems using the Augmented Lagrangian method. Here, we solve min f(x) = x₀ + x₁ subject to the equality constraint x₀² + x₁² - 2 = 0. The optimal solution lies on the circle of radius √2 at the point (-1, -1).

```cpp

include "cppoptlib/function.h"

include "cppoptlib/solver/augmented_lagrangian.h"

include "cppoptlib/solver/lbfgs.h"

// Objective: f(x) = x0 + x1 class SumObjective : public cppoptlib::function::FunctionXd { public: ScalarType operator()(const VectorType &x, VectorType *grad) const { if (grad) *grad = VectorType::Ones(x.size()); return x.sum(); } };

// Constraint: c(x) = x0^2 + x1^2 class Circle : public cppoptlib::function::FunctionXd { public: ScalarType operator()(const VectorType &x, VectorType *grad) const { if (grad) *grad = 2 * x; return x.squaredNorm(); } };

int main() { cppoptlib::function::FunctionExpr objective = SumObjective(); cppoptlib::function::FunctionExpr circleconstraintbase = Circle();

cppoptlib::function::FunctionExpr equalityconstraint = circleconstraint_base - 2.0;

cppoptlib::function::ConstrainedOptimizationProblem problem( objective, {equality_constraint});

cppoptlib::solver::Lbfgs innersolver; cppoptlib::solver::AugmentedLagrangian solver(problem, innersolver);

Eigen::VectorXd xinit(2); xinit << 5, -3;

cppoptlib::solver::AugmentedLagrangeState state(x_init, 1, 0, 10.0);

auto [solution, solver_state] = solver.Minimize(problem, state);

std::cout << "Solver finished!" << std::endl; std::cout << "Found minimum at: " << solution.x.transpose() << std::endl; std::cout << "Function value: " << objective(solution.x) << std::endl;

return 0; } ```

Installation

CppNumericalSolvers is header-only. You just need a C++17 compatible compiler and Eigen3.

Recommended: CMake FetchContent

Add this to your CMakeLists.txt:

```cmake include(FetchContent)

FetchContentDeclare( cppoptlib GITREPOSITORY https://github.com/PatWie/CppNumericalSolvers.git GIT_TAG main # Or a specific commit/tag )

FetchContent_MakeAvailable(cppoptlib)

... then in your target:

targetlinklibraries(your_target PRIVATE CppNumericalSolvers) ```

Manual Integration

Clone the repository:

bash git clone https://github.com/PatWie/CppNumericalSolvers.git

Add the include/ directory to your project's include path. Ensure Eigen3 is available in your include path.

Citation

If you use this library in your research, please cite it:

bibtex @misc{wieschollek2016cppoptimizationlibrary, title={CppOptimizationLibrary}, author={Wieschollek, Patrick}, year={2016}, howpublished={\url{https://github.com/PatWie/CppNumericalSolvers}}, }

Owner

  • Name: Patrick Wieschollek
  • Login: PatWie
  • Kind: user
  • Location: Tübingen

Citation (CITATION.cff)

cff-version: 1.2.0
type: software
title: "CppNumericalSolvers: A Modern C++17 Header-Only Optimization Library"
abstract: "CppNumericalSolvers is a lightweight, header-only C++17 library for numerical optimization. It provides a suite of modern, efficient solvers for both unconstrained and constrained problems, designed for easy integration and high performance. A key feature is its use of function expression templates, which allow you to build complex objective functions on-the-fly without writing boilerplate code."
authors:
  - family-names: "Wieschollek"
    given-names: "Patrick"
repository-code: "https://github.com/PatWie/CppNumericalSolvers"
url: "https://github.com/PatWie/CppNumericalSolvers"
license: MIT
date-released: 2014-11-09
version: "2.0.0"
keywords:
  - "optimization"
  - "numerical-methods"
  - "cpp17"
  - "header-only"
  - "eigen"
  - "bfgs"
  - "lbfgs"
  - "conjugate-gradient"
  - "newton-method"
  - "constrained-optimization"
  - "expression-templates"

GitHub Events

Total
  • Create event: 14
  • Release event: 1
  • Issues event: 7
  • Watch event: 46
  • Delete event: 15
  • Issue comment event: 6
  • Push event: 51
  • Pull request review comment event: 59
  • Pull request review event: 9
  • Pull request event: 24
  • Fork event: 4
Last Year
  • Create event: 14
  • Release event: 1
  • Issues event: 7
  • Watch event: 46
  • Delete event: 15
  • Issue comment event: 6
  • Push event: 51
  • Pull request review comment event: 59
  • Pull request review event: 9
  • Pull request event: 24
  • Fork event: 4

Committers

Last synced: 7 months ago

All Time
  • Total Commits: 161
  • Total Committers: 18
  • Avg Commits per committer: 8.944
  • Development Distribution Score (DDS): 0.522
Past Year
  • Commits: 19
  • Committers: 2
  • Avg Commits per committer: 9.5
  • Development Distribution Score (DDS): 0.053
Top Committers
Name Email Commits
PatWie m****l@p****m 77
PatWie p****k@w****o 38
Tobias Wood t****s@s****k 17
Carter Green c****r@u****u 9
daiver r****1@y****u 4
Tobias Ulvgard t****d@g****m 2
simon-hrabec s****c@g****m 2
Andreas Horst S****8 2
Nilesh Patra n****h@r****t 1
Björn Ingvar Dahlgren b****h@g****m 1
Karel van de Plassche k****e@g****m 1
Pierre Moulon p****n@g****m 1
Tim Lyon 4****n 1
Wolfgang Merkt w****t 1
Yixuan Qiu y****u@c****e 1
jonghee j****e@m****m 1
lzhbrian l****n@g****m 1
Фёдоров Николай u****l@l****u 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 4 months ago

All Time
  • Total issues: 59
  • Total pull requests: 69
  • Average time to close issues: 3 months
  • Average time to close pull requests: 10 days
  • Total issue authors: 40
  • Total pull request authors: 18
  • Average comments per issue: 3.2
  • Average comments per pull request: 0.87
  • Merged pull requests: 48
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 4
  • Pull requests: 23
  • Average time to close issues: about 23 hours
  • Average time to close pull requests: about 11 hours
  • Issue authors: 4
  • Pull request authors: 2
  • Average comments per issue: 1.25
  • Average comments per pull request: 0.0
  • Merged pull requests: 14
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • PatWie (10)
  • hugandr (5)
  • tvatter (3)
  • spinicist (2)
  • RobertLRead (2)
  • iwillburnyou (2)
  • rperrot (2)
  • stefano2734 (1)
  • nfc456 (1)
  • SebDyn (1)
  • nikitaved (1)
  • dingzeyuli (1)
  • SVincelette (1)
  • GMellar (1)
  • tvercaut (1)
Pull Request Authors
  • PatWie (36)
  • spinicist (11)
  • Unril (3)
  • Daiver (2)
  • simon-hrabec (2)
  • nileshpatra (2)
  • Sora1248 (2)
  • lzhbrian (1)
  • pmoulon (1)
  • wxmerkt (1)
  • yixuan (1)
  • ulvgard (1)
  • selkovjr (1)
  • AlexanderFabisch (1)
  • yyuting (1)
Top Labels
Issue Labels
enhancement (15) bug (4) Should be checked (3) help wanted (1) unclear (1)
Pull Request Labels

Dependencies

.ci/Dockerfile docker
  • ubuntu 18.04 build