Skip to content

Commit d65d1b1

Browse files
authored
Drop cppoptlib (#66)
* Drop cppoptlib from Problem and Solver * Cleanup changes * Private CMake target_link_libraries * Use TVector * Remove cppoptlib line-search methods * Add custom statuses * Move extra solver logic to Criteria * Add our cases to operator<< * Update Criteria::print * Removed nan setting * Remove unused line search names * Fix not descent direction failsafe * Add codecov.yml
1 parent 811c9a7 commit d65d1b1

File tree

18 files changed

+421
-378
lines changed

18 files changed

+421
-378
lines changed

CMakeLists.txt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,17 +311,13 @@ target_link_libraries(polysolve_linear PRIVATE polysolve::warnings)
311311
# polysolve::linear
312312
target_link_libraries(polysolve PUBLIC polysolve::linear)
313313

314-
# CppNumericalSolvers
315-
include(cppoptlib)
316-
target_link_libraries(polysolve PUBLIC cppoptlib)
317-
318314
# LBFGSpp
319315
include(LBFGSpp)
320-
target_link_libraries(polysolve PUBLIC LBFGSpp::LBFGSpp)
316+
target_link_libraries(polysolve PRIVATE LBFGSpp::LBFGSpp)
321317

322318
# finite-diff (include this after eigen)
323319
include(finite-diff)
324-
target_link_libraries(polysolve PUBLIC finitediff::finitediff)
320+
target_link_libraries(polysolve PRIVATE finitediff::finitediff)
325321

326322
# Sanitizers
327323
if(POLYSOLVE_WITH_SANITIZERS)

cmake/recipes/cppoptlib.cmake

Lines changed: 0 additions & 19 deletions
This file was deleted.

codecov.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: auto
6+
threshold: 1%
7+
patch:
8+
default:
9+
target: 75%
10+
threshold: 5%
11+
only_pulls: true

nonlinear-solver-spec.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
[
2-
{
1+
[{
32
"pointer": "/",
43
"default": null,
54
"type": "object",
@@ -562,10 +561,8 @@
562561
"type": "string",
563562
"options": [
564563
"Armijo",
565-
"ArmijoAlt",
566564
"RobustArmijo",
567565
"Backtracking",
568-
"MoreThuente",
569566
"None"
570567
],
571568
"doc": "Line-search type"

src/polysolve/nonlinear/CMakeLists.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
set(SOURCES
22
BoxConstraintSolver.cpp
33
BoxConstraintSolver.hpp
4-
Solver.hpp
5-
Solver.cpp
6-
Problem.hpp
7-
Problem.cpp
8-
PostStepData.hpp
4+
Criteria.cpp
5+
Criteria.hpp
96
PostStepData.cpp
7+
PostStepData.hpp
8+
Problem.cpp
9+
Problem.hpp
10+
Solver.cpp
11+
Solver.hpp
1012
)
1113

1214
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${SOURCES})
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Source: https://github.com/PatWie/CppNumericalSolvers/blob/7eddf28fa5a8872a956d3c8666055cac2f5a535d/include/cppoptlib/meta.h
2+
// License: MIT
3+
#include "Criteria.hpp"
4+
5+
namespace polysolve::nonlinear
6+
{
7+
bool is_converged_status(const Status s)
8+
{
9+
return s == Status::XDeltaTolerance || s == Status::FDeltaTolerance || s == Status::GradNormTolerance;
10+
}
11+
12+
Criteria::Criteria()
13+
{
14+
reset();
15+
}
16+
17+
void Criteria::reset()
18+
{
19+
iterations = 0;
20+
xDelta = 0;
21+
fDelta = 0;
22+
gradNorm = 0;
23+
firstGradNorm = 0;
24+
fDeltaCount = 0;
25+
xDeltaDotGrad = 0;
26+
}
27+
28+
void Criteria::print(std::ostream &os) const
29+
{
30+
os << "Iterations: " << iterations << std::endl;
31+
os << "xDelta: " << xDelta << std::endl;
32+
os << "fDelta: " << fDelta << std::endl;
33+
os << "GradNorm: " << gradNorm << std::endl;
34+
os << "xDeltaDotGrad: " << xDeltaDotGrad << std::endl;
35+
os << "FirstGradNorm: " << firstGradNorm << std::endl;
36+
os << "fDeltaCount: " << fDeltaCount << std::endl;
37+
}
38+
39+
Status checkConvergence(const Criteria &stop, const Criteria &current)
40+
{
41+
if (stop.iterations > 0 && current.iterations > stop.iterations)
42+
{
43+
return Status::IterationLimit;
44+
}
45+
if (stop.xDelta > 0 && current.xDelta < stop.xDelta)
46+
{
47+
return Status::XDeltaTolerance;
48+
}
49+
if (stop.fDelta > 0 && current.fDelta < stop.fDelta && current.fDeltaCount >= stop.fDeltaCount)
50+
{
51+
return Status::FDeltaTolerance;
52+
}
53+
const double stopGradNorm = current.iterations == 0 ? stop.firstGradNorm : stop.gradNorm;
54+
if (stopGradNorm > 0 && current.gradNorm < stopGradNorm)
55+
{
56+
return Status::GradNormTolerance;
57+
}
58+
// Δx⋅∇f ≥ 0 means that the search direction is not a descent direction
59+
if (stop.xDeltaDotGrad < 0 && current.xDeltaDotGrad > stop.xDeltaDotGrad)
60+
{
61+
return Status::NotDescentDirection;
62+
}
63+
return Status::Continue;
64+
}
65+
66+
std::ostream &operator<<(std::ostream &os, const Status &s)
67+
{
68+
switch (s)
69+
{
70+
case Status::NotStarted:
71+
os << "Solver not started.";
72+
break;
73+
case Status::Continue:
74+
os << "Convergence criteria not reached.";
75+
break;
76+
case Status::IterationLimit:
77+
os << "Iteration limit reached.";
78+
break;
79+
case Status::XDeltaTolerance:
80+
os << "Change in parameter vector too small.";
81+
break;
82+
case Status::FDeltaTolerance:
83+
os << "Change in cost function value too small.";
84+
break;
85+
case Status::GradNormTolerance:
86+
os << "Gradient vector norm too small.";
87+
break;
88+
case Status::ObjectiveCustomStop:
89+
os << "Objective function specified to stop.";
90+
break;
91+
case Status::NanEncountered:
92+
os << "Objective or gradient function returned NaN.";
93+
break;
94+
case Status::NotDescentDirection:
95+
os << "Search direction not a descent direction.";
96+
break;
97+
case Status::LineSearchFailed:
98+
os << "Line search failed.";
99+
break;
100+
default:
101+
os << "Unknown status.";
102+
break;
103+
}
104+
return os;
105+
}
106+
107+
std::ostream &operator<<(std::ostream &os, const Criteria &c)
108+
{
109+
c.print(os);
110+
return os;
111+
}
112+
} // namespace polysolve::nonlinear
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <iostream>
5+
6+
namespace polysolve::nonlinear
7+
{
8+
// Source: https://github.com/PatWie/CppNumericalSolvers/blob/7eddf28fa5a8872a956d3c8666055cac2f5a535d/include/cppoptlib/meta.h
9+
// License: MIT
10+
11+
enum class Status
12+
{
13+
NotStarted = -1, ///< The solver has not been started
14+
Continue = 0, ///< The solver should continue
15+
// Success cases
16+
IterationLimit, ///< The maximum number of iterations has been reached
17+
XDeltaTolerance, ///< The change in the parameter vector is below the tolerance
18+
FDeltaTolerance, ///< The change in the cost function is below the tolerance
19+
GradNormTolerance, ///< The norm of the gradient vector is below the tolerance
20+
ObjectiveCustomStop, ///< The objective function specified to stop
21+
// Failure cases
22+
NanEncountered, ///< The objective function returned NaN
23+
NotDescentDirection, ///< The search direction is not a descent direction
24+
LineSearchFailed, ///< The line search failed
25+
};
26+
27+
bool is_converged_status(const Status s);
28+
29+
class Criteria
30+
{
31+
public:
32+
size_t iterations; ///< Maximum number of iterations
33+
double xDelta; ///< Minimum change in parameter vector
34+
double fDelta; ///< Minimum change in cost function
35+
double gradNorm; ///< Minimum norm of gradient vector
36+
double firstGradNorm; ///< Initial norm of gradient vector
37+
double xDeltaDotGrad; ///< Dot product of parameter vector and gradient vector
38+
unsigned fDeltaCount; ///< Number of steps where fDelta is satisfied
39+
40+
Criteria();
41+
42+
void reset();
43+
44+
void print(std::ostream &os) const;
45+
};
46+
47+
Status checkConvergence(const Criteria &stop, const Criteria &current);
48+
49+
std::ostream &operator<<(std::ostream &os, const Status &s);
50+
51+
std::ostream &operator<<(std::ostream &os, const Criteria &c);
52+
} // namespace polysolve::nonlinear

src/polysolve/nonlinear/Problem.hpp

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,115 @@
22

33
#include <polysolve/Types.hpp>
44

5+
#include "Criteria.hpp"
56
#include "PostStepData.hpp"
67

7-
#include <cppoptlib/problem.h>
8-
98
#include <memory>
109
#include <vector>
1110

1211
namespace polysolve::nonlinear
1312
{
14-
class Problem : public cppoptlib::Problem<double>
13+
class Problem
1514
{
1615
public:
17-
using typename cppoptlib::Problem<double>::Scalar;
18-
using typename cppoptlib::Problem<double>::TVector;
19-
typedef polysolve::StiffnessMatrix THessian;
20-
21-
// disable warning for dense hessian
22-
using cppoptlib::Problem<double>::hessian;
16+
static constexpr int Dim = Eigen::Dynamic;
17+
using Scalar = double;
18+
using TVector = Eigen::Matrix<Scalar, Dim, 1>;
19+
using TMatrix = Eigen::Matrix<Scalar, Dim, Dim>;
20+
using THessian = StiffnessMatrix;
2321

22+
public:
2423
Problem() {}
25-
~Problem() = default;
24+
virtual ~Problem() = default;
2625

26+
/// @brief Initialize the problem.
27+
/// @param x0 Initial guess.
2728
virtual void init(const TVector &x0) {}
2829

29-
virtual double value(const TVector &x) override = 0;
30-
virtual void gradient(const TVector &x, TVector &gradv) override = 0;
30+
/// @brief Compute the value of the function at x.
31+
/// @param x Degrees of freedom.
32+
/// @return The value of the function at x.
33+
Scalar operator()(const TVector &x) { return value(x); }
34+
35+
/// @brief Compute the value of the function at x.
36+
/// @param x Degrees of freedom.
37+
/// @return The value of the function at x.
38+
virtual Scalar value(const TVector &x) = 0;
39+
40+
/// @brief Compute the gradient of the function at x.
41+
/// @param[in] x Degrees of freedom.
42+
/// @param[out] grad Gradient of the function at x.
43+
virtual void gradient(const TVector &x, TVector &grad) = 0;
44+
45+
/// @brief Compute the Hessian of the function at x.
46+
/// @param[in] x Degrees of freedom.
47+
/// @param[out] hessian Hessian of the function at x.
48+
virtual void hessian(const TVector &x, TMatrix &hessian)
49+
{
50+
throw std::runtime_error("Dense Hessian not implemented.");
51+
}
52+
53+
/// @brief Compute the Hessian of the function at x.
54+
/// @param[in] x Degrees of freedom.
55+
/// @param[out] hessian Hessian of the function at x.
3156
virtual void hessian(const TVector &x, THessian &hessian) = 0;
3257

58+
/// @brief Determine if the step from x0 to x1 is valid.
59+
/// @param x0 Starting point.
60+
/// @param x1 Ending point.
61+
/// @return True if the step is valid, false otherwise.
3362
virtual bool is_step_valid(const TVector &x0, const TVector &x1) { return true; }
63+
64+
/// @brief Determine a maximum step size from x0 to x1.
65+
/// @param x0 Starting point.
66+
/// @param x1 Ending point.
67+
/// @return Maximum step size.
3468
virtual double max_step_size(const TVector &x0, const TVector &x1) { return 1; }
3569

70+
// --- Callbacks ------------------------------------------------------
71+
72+
/// @brief Callback function for the start of a line search.
73+
/// @param x0 Starting point.
74+
/// @param x1 Ending point.
3675
virtual void line_search_begin(const TVector &x0, const TVector &x1) {}
76+
77+
/// @brief Callback function for the end of a line search.
3778
virtual void line_search_end() {}
79+
80+
/// @brief Callback function for the end of a step.
81+
/// @param data Post step data.
3882
virtual void post_step(const PostStepData &data) {}
3983

84+
/// @brief Set the project to PSD flag.
85+
/// @param val True if the problem should be projected to PSD, false otherwise.
4086
virtual void set_project_to_psd(bool val) {}
4187

88+
/// @brief Callback function for when the solution changes.
89+
/// @param new_x New solution.
4290
virtual void solution_changed(const TVector &new_x) {}
4391

92+
/// @brief Callback function used to determine if the solver should stop.
93+
/// @param state Current state of the solver.
94+
/// @param x Current solution.
95+
/// @return True if the solver should stop, false otherwise.
96+
virtual bool callback(const Criteria &state, const TVector &x) { return true; }
97+
98+
/// @brief Callback function used Determine if the solver should stop.
99+
/// @param x Current solution.
100+
/// @return True if the solver should stop, false otherwise.
44101
virtual bool stop(const TVector &x) { return false; }
45102

103+
/// --- Misc ----------------------------------------------------------
104+
105+
/// @brief Sample the function along a direction.
106+
/// @param[in] x Starting point.
107+
/// @param[in] direction Direction to sample along.
108+
/// @param[in] start Starting step size.
109+
/// @param[in] end Ending step size.
110+
/// @param[in] num_samples Number of samples to take.
111+
/// @param[out] alphas Sampled step sizes.
112+
/// @param[out] fs Sampled function values.
113+
/// @param[out] valid If each sample is valid.
46114
void sample_along_direction(
47115
const Problem::TVector &x,
48116
const Problem::TVector &direction,

0 commit comments

Comments
 (0)