Skip to content

Commit 6ff42cb

Browse files
authored
Improvements to logging, minor improvements to optimization behavior (#7)
- Get rid of old janky printf logger. Now all optimization outputs are serializable to JSON using nlohmann. - Add simple tracing mechanism that can generate method timings in chrome trace format. - Residual API is improved (uses a type-erased `Residual` type, no longer a vector of `unique_ptr`). - Fix a bug in the LM implementation where lambda would incorrectly reset to an overly optimistic value. - Add some CMake options to include what features are built. - Remove a bunch of places where we would assert on NaN or Inf - instead those are handled by returning recoverable error states. - Enums and the like are printable with libfmt.
1 parent e01ee0e commit 6ff42cb

24 files changed

+1702
-1131
lines changed

.github/workflows/linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
CXX: g++-13
4242
working-directory: ${{runner.workspace}}/build
4343
run: |
44-
cmake ${{github.workspace}} -DCMAKE_BUILD_TYPE=RelWithDebInfo -Wno-deprecated -G Ninja
44+
cmake ${{github.workspace}} -DCMAKE_BUILD_TYPE=RelWithDebInfo -Wno-deprecated -G Ninja -DMINI_OPT_SERIALIZATION=ON -DMINI_OPT_TRACING=ON
4545
4646
- name: Build
4747
working-directory: ${{runner.workspace}}/build

.github/workflows/windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
CXX: cl.exe
4444
working-directory: ${{runner.workspace}}/build
4545
run: |
46-
cmake ${{github.workspace}} -DCMAKE_BUILD_TYPE=RelWithDebInfo -Wno-deprecated -G Ninja
46+
cmake ${{github.workspace}} -DCMAKE_BUILD_TYPE=RelWithDebInfo -Wno-deprecated -G Ninja -DMINI_OPT_SERIALIZATION=ON -DMINI_OPT_TRACING=ON
4747
4848
- name: Build
4949
working-directory: ${{runner.workspace}}/build

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010
[submodule "dependencies/fmt"]
1111
path = dependencies/fmt
1212
url = https://github.com/fmtlib/fmt.git
13+
[submodule "dependencies/json"]
14+
path = dependencies/json
15+
url = https://github.com/nlohmann/json.git

CMakeLists.txt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
cmake_minimum_required(VERSION 3.20)
22
project(mini_opt CXX)
3-
enable_testing()
43
include(GNUInstallDirs)
54

5+
# Options controlling compilation:
6+
option(MINI_OPT_BUILD_TESTS "Build with tests enabled." ON)
7+
option(MINI_OPT_SERIALIZATION "Build with serialization enabled." OFF)
8+
option(MINI_OPT_TRACING "Build with tracing enabled." OFF)
9+
10+
if(MINI_OPT_BUILD_TESTS)
11+
enable_testing()
12+
endif()
13+
614
add_subdirectory(dependencies)
715

816
if(MSVC)
@@ -16,17 +24,26 @@ set(LIBRARY_NAME ${PROJECT_NAME})
1624
add_library(
1725
${LIBRARY_NAME} STATIC
1826
include/mini_opt/assertions.hpp
19-
include/mini_opt/logging.hpp
2027
include/mini_opt/nonlinear.hpp
2128
include/mini_opt/qp.hpp
2229
include/mini_opt/residual.hpp
2330
include/mini_opt/structs.hpp
24-
source/logging.cc
31+
include/mini_opt/serialization.hpp
32+
include/mini_opt/tracing.hpp
2533
source/nonlinear.cc
2634
source/qp.cc
2735
source/residual.cc
28-
source/structs.cc)
36+
source/structs.cc
37+
source/serialization.cc
38+
source/tracing.cc)
2939
target_link_libraries(${LIBRARY_NAME} PUBLIC fmt::fmt-header-only eigen)
40+
if(MINI_OPT_SERIALIZATION)
41+
target_link_libraries(${LIBRARY_NAME} PUBLIC nlohmann_json::nlohmann_json)
42+
target_compile_definitions(${LIBRARY_NAME} PUBLIC -DMINI_OPT_SERIALIZATION)
43+
endif()
44+
if(MINI_OPT_TRACING)
45+
target_compile_definitions(${LIBRARY_NAME} PUBLIC -DMINI_OPT_TRACING)
46+
endif()
3047
target_compile_features(${LIBRARY_NAME} PUBLIC cxx_std_17)
3148
target_compile_options(${LIBRARY_NAME} PRIVATE ${COMPILATION_FLAGS})
3249
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -49,4 +66,6 @@ install(
4966
PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIBRARY_NAME}")
5067

5168
# Unit tests
52-
add_subdirectory(test)
69+
if(MINI_OPT_BUILD_TESTS)
70+
add_subdirectory(test)
71+
endif()

dependencies/CMakeLists.txt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@ function(add_gtest)
1919
add_subdirectory(googletest)
2020
endif()
2121
endfunction()
22-
add_gtest()
22+
23+
if(MINI_OPT_BUILD_TESTS)
24+
add_gtest()
25+
endif()
26+
27+
# Add nlohmann json
28+
function(add_json)
29+
if(NOT TARGET nlohmann_json::nlohmann_json)
30+
add_subdirectory(json)
31+
endif()
32+
endfunction()
33+
34+
if(MINI_OPT_SERIALIZATION)
35+
add_json()
36+
endif()
2337

2438
# Create a target for eigen.
2539
function(add_eigen)
@@ -40,4 +54,7 @@ function(add_geometry_utils)
4054
add_subdirectory(geometry_utils)
4155
endif()
4256
endfunction()
43-
add_geometry_utils()
57+
58+
if(MINI_OPT_BUILD_TESTS)
59+
add_geometry_utils()
60+
endif()

dependencies/json

Submodule json added at 9cca280

include/mini_opt/assertions.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ inline constexpr std::string_view message_prefix = "Assertion failed: {}\nFile:
1515
// Generates an exception with a formatted string.
1616
template <typename... Ts>
1717
[[nodiscard]] auto format_assert(const std::string_view condition, const std::string_view file,
18-
const int line, const std::string_view reason_fmt = {},
19-
Ts&&... args) {
18+
const int line, const std::string_view reason_fmt = {},
19+
Ts&&... args) {
2020
std::string err = fmt::format(constants::message_prefix, condition, file, line);
2121
if (!reason_fmt.empty()) {
2222
err.append("\nReason: ");

include/mini_opt/logging.hpp

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

include/mini_opt/nonlinear.hpp

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,24 @@ namespace mini_opt {
3131
* with diagonal inequality constraints on the decision variables.
3232
*/
3333
struct Problem {
34-
using unique_ptr = std::unique_ptr<Problem>;
35-
3634
// Problem dimension. (ie. max variable index + 1)
3735
int dimension;
3836

3937
// The errors that form the sum of squares part of the cost function.
40-
std::vector<ResidualBase::unique_ptr> costs;
38+
std::vector<Residual> costs;
4139

4240
// Linear inequality constraints.
4341
std::vector<LinearInequalityConstraint> inequality_constraints;
4442

4543
// Nonlinear inequality constraints.
46-
std::vector<ResidualBase::unique_ptr> equality_constraints;
44+
std::vector<Residual> equality_constraints;
45+
46+
// Clear all factors.
47+
void clear() {
48+
costs.clear();
49+
inequality_constraints.clear();
50+
equality_constraints.clear();
51+
}
4752
};
4853

4954
/*
@@ -85,8 +90,8 @@ struct ConstrainedNonlinearLeastSquares {
8590
// Value by which to decrease alpha for the backtracking line search.
8691
double armijo_search_tau{0.8};
8792

88-
// Initial value of the equality constraint penalty.
89-
double equality_penalty_initial{0.01};
93+
// Initial value of the equality constraint penalty, µ.
94+
double equality_penalty_initial{1.0};
9095

9196
// Scale factor when increasing the equality penalty.
9297
// If µ_new > µ_old, we increase the penalty to µ_new * equality_penalty_scale_factor.
@@ -102,20 +107,24 @@ struct ConstrainedNonlinearLeastSquares {
102107
// Initial lambda value on entering the ATTEMPTING_RESTORE_LM state.
103108
double lambda_failure_init{1.0e-2};
104109

110+
// Multiplicative amount to decrease lambda on a successful step.
111+
double lambda_decrease_on_success{0.1};
112+
113+
// Multiplicative amount to decrease lambda when exiting the `ATTEMPTING_RESTORE_LM` state.
114+
double lambda_decrease_on_restore{0.8};
115+
105116
// Maximum lambda value.
106117
double max_lambda{1.};
107118

108119
// Minimum lambda value.
109120
double min_lambda{0.};
121+
122+
// If true, compute and log summary stats of the eigenvalues from the QP hessian `G`.
123+
bool log_qp_eigenvalues{false};
110124
};
111125

112126
// Signature of custom retraction operator.
113-
using Retraction =
114-
std::function<void(Eigen::VectorXd* const x, const ConstVectorBlock& dx, const double alpha)>;
115-
116-
// Signature of custom logger.
117-
using LoggingCallback =
118-
std::function<bool(const ConstrainedNonlinearLeastSquares& self, const NLSLogInfo& info)>;
127+
using Retraction = std::function<void(Eigen::VectorXd& x, ConstVectorBlock dx, double alpha)>;
119128

120129
// Construct w/ const pointer to a problem definition.
121130
explicit ConstrainedNonlinearLeastSquares(const Problem* problem,
@@ -135,28 +144,18 @@ struct ConstrainedNonlinearLeastSquares {
135144
*/
136145
NLSSolverOutputs Solve(const Params& params, const Eigen::VectorXd& variables);
137146

138-
// Set the callback which will be used for the QP solver.
139-
template <typename T>
140-
void SetQPLoggingCallback(T&& cb) {
141-
// TODO: Get rid of the logging callback altogether. For now I maintain this path for existing
142-
// unit tests.
143-
if (QPInteriorPointSolver* ip_solver = std::get_if<QPInteriorPointSolver>(&solver_);
144-
ip_solver != nullptr)
145-
ip_solver->SetLoggerCallback(std::forward<T>(cb));
146-
}
147-
148-
// Set the callback that will be used for the nonlinear solver.
149-
template <typename T>
150-
void SetLoggingCallback(T&& cb) {
151-
logging_callback_ = std::forward<T>(cb);
152-
}
153-
154147
// Get the current linearization point.
155148
constexpr const Eigen::VectorXd& variables() const noexcept { return variables_; }
156149

157150
// Evaluate the non-linear error.
158151
Errors EvaluateNonlinearErrors(const Eigen::VectorXd& vars);
159152

153+
// The user exit callback may return `false` to cause the optimization to terminate early.
154+
template <typename T>
155+
void SetUserExitCallback(T&& cb) {
156+
user_exit_callback_ = std::forward<T>(cb);
157+
}
158+
160159
private:
161160
// Update candidate_vars w/ a step size of alpha.
162161
void RetractCandidateVars(double alpha);
@@ -166,15 +165,22 @@ struct ConstrainedNonlinearLeastSquares {
166165
const Problem& problem, QP* qp);
167166

168167
// Solve the QP, and update the step direction `dx_`.
169-
std::tuple<QPSolverOutputs, std::optional<QPLagrangeMultipliers>> ComputeStepDirection(
168+
std::variant<QPNullSpaceTerminationState, QPInteriorPointSolverOutputs> ComputeStepDirection(
170169
const Params& params);
171170

171+
// Extract lagrange multipliers from the QP solver outputs, if they were used.
172+
static std::optional<QPLagrangeMultipliers> MaybeGetLagrangeMultipliers(
173+
const std::variant<QPNullSpaceTerminationState, QPInteriorPointSolverOutputs>& output);
174+
175+
// True if the QP was indefinite (in which case we will terminate optimization).
176+
static bool QPWasIndefinite(
177+
const std::variant<QPNullSpaceTerminationState, QPInteriorPointSolverOutputs>& output);
178+
172179
// Based on the outcome of the step selection, update lambda and check if
173180
// we should exit. Returns NONE if no exit is required.
174-
NLSTerminationState UpdateLambdaAndCheckExitConditions(const Params& params,
175-
StepSizeSelectionResult step_result,
176-
const Errors& initial_errors,
177-
double penalty, double& lambda);
181+
std::optional<NLSTerminationState> UpdateLambdaAndCheckExitConditions(
182+
const Params& params, StepSizeSelectionResult step_result, const Errors& initial_errors,
183+
double penalty, double& lambda);
178184

179185
// Execute a back-tracking search until the cost decreases, or we hit
180186
// a max number of iterations
@@ -185,9 +191,9 @@ struct ConstrainedNonlinearLeastSquares {
185191
double backtrack_search_tau);
186192

187193
// Repeatedly approximate the cost function as a polynomial, and find the minimum.
188-
double ComputeAlphaPolynomialApproximation(int iteration, double alpha, const Errors& errors_pre,
189-
const DirectionalDerivatives& derivatives,
190-
double penalty) const;
194+
std::optional<double> ComputeAlphaPolynomialApproximation(
195+
int iteration, const Errors& errors_pre, const DirectionalDerivatives& derivatives,
196+
double penalty) const;
191197

192198
// Compute first derivative of the QP cost function.
193199
static DirectionalDerivatives ComputeQPCostDerivative(const QP& qp, const Eigen::VectorXd& dx);
@@ -199,24 +205,18 @@ struct ConstrainedNonlinearLeastSquares {
199205

200206
// Solve the quadratic approximation of the cost function for best alpha.
201207
// Implements equation (3.57/3.58)
202-
static double QuadraticApproxMinimum(double phi_0, double phi_prime_0, double alpha_0,
203-
double phi_alpha_0);
208+
static std::optional<double> QuadraticApproxMinimum(double phi_0, double phi_prime_0,
209+
double alpha_0, double phi_alpha_0);
204210

205211
// Get the polynomial coefficients c0, c1 from the cubic approximation of the cost.
206212
// Implements equation after 3.58, returns [a, b]
207213
static Eigen::Vector2d CubicApproxCoeffs(double phi_0, double phi_prime_0, double alpha_0,
208214
double phi_alpha_0, double alpha_1, double phi_alpha_1);
209215

210216
// Get the solution of the cubic approximation.
211-
static double CubicApproxMinimum(double phi_prime_0, const Eigen::Vector2d& ab);
212-
213-
// Compute the second order correction to the equality constraints.
214-
static void ComputeSecondOrderCorrection(
215-
const Eigen::VectorXd& updated_x,
216-
const std::vector<ResidualBase::unique_ptr>& equality_constraints, QP* qp,
217-
Eigen::CompleteOrthogonalDecomposition<Eigen::MatrixXd>* solver, Eigen::VectorXd* dx_out);
217+
static std::optional<double> CubicApproxMinimum(double phi_prime_0, const Eigen::Vector2d& ab);
218218

219-
const Problem* const p_;
219+
const Problem* p_;
220220

221221
// Custom retraction operator, optional.
222222
Retraction custom_retraction_;
@@ -241,8 +241,8 @@ struct ConstrainedNonlinearLeastSquares {
241241
// Current state of the optimization
242242
OptimizerState state_{OptimizerState::NOMINAL};
243243

244-
// Logging callback.
245-
LoggingCallback logging_callback_{};
244+
// A user-specified callback that can opt to terminate the optimization early.
245+
std::function<bool(const NLSIteration&)> user_exit_callback_{};
246246

247247
friend class ConstrainedNLSTest;
248248
};

0 commit comments

Comments
 (0)