smooth
A C++ library for Lie theory
Loading...
Searching...
No Matches
optim.hpp
1// Copyright (C) 2022 Petter Nilsson. MIT License.
2
3#pragma once
4
5#include <chrono>
6#include <memory>
7#include <utility>
8
9#include <Eigen/Sparse>
10
11#ifdef SMOOTH_HAS_FMT
12#include <fmt/chrono.h>
13#include <fmt/color.h>
14#include <fmt/format.h>
15#else
16#include <iostream>
17#endif
18
19#include "detail/math.hpp"
20#include "diff.hpp"
21#include "optim/tr_solver.hpp"
22#include "optim/tr_strategy.hpp"
23
24SMOOTH_BEGIN_NAMESPACE
25
27{
29 std::shared_ptr<TrustRegionStrategy> strat{std::make_shared<CeresStrategy>()};
31 double ptol{1e-6};
33 double ftol{1e-6};
35 std::size_t max_iter{1000};
37 bool verbose{false};
38};
39
41{
42 enum class Status { Ftol, Ptol, MaxIters } status;
43 unsigned iter;
44 std::chrono::nanoseconds time;
45};
46
63template<diff::Type D>
64SolveResult minimize(auto && f, auto && x, auto && cb, const MinimizeOptions & opts = {})
65 requires(!std::is_same_v<std::decay_t<decltype(cb)>, MinimizeOptions>)
66{
67 std::optional<SolveResult::Status> status = {};
68 const auto t0 = std::chrono::high_resolution_clock::now();
69 auto iter = 0u;
70
71 // execute callback on initial value
72 std::apply(cb, x);
73
74 for (; iter < opts.max_iter && !status.has_value(); ++iter) {
75 // evaluate residuals and jacobian
76 const auto [r, J] = diff::dr<1, D>(f, x);
77
78 using JType = std::decay_t<decltype(J)>;
79 static constexpr auto N = JType::ColsAtCompileTime;
80
81 if (opts.verbose && iter == 0) {
82#ifdef SMOOTH_HAS_FMT
83 fmt::print("{0:x^69s}\n{1:x^69s}\n{0:x^69s}\n", "", " NLS SOLVER ");
84 fmt::print("Optimizing {} residuals in {} degrees of freedom\n", J.rows(), J.cols());
85#else
86 std::cout << "#define SMOOTH_HAS_FMT and link with fmt to see minimizer progress.\n";
87#endif
88 }
89
90 // diagonal scaling parameters
91 static constexpr auto clamper = [](double el) { return std::clamp(el, 1e-6, 1e32); };
92 const Eigen::Vector<double, N> d = colwise_norm(J).unaryExpr(clamper);
93
94 // trust region step
95 const double Delta = opts.strat->get_delta();
96 const auto [dx, lambda] = solve_trust_region(J, d, r, Delta);
97 const auto xp = wrt_rplus(x, dx);
98
99 // actual to relative reduction
100 const double r_n = r.stableNorm();
101 const double actu_red = 1. - fpow<2>(std::apply(f, xp).stableNorm() / r_n);
102 const double pred_red = 1. - fpow<2>((r + J * dx).stableNorm() / r_n);
103 const double rho = actu_red / pred_red;
104
105 // update trust region
106 const bool take_step = opts.strat->step_and_update(rho);
107
108 if (opts.verbose) {
109#ifdef SMOOTH_HAS_FMT
110 using namespace fmt; // NOLINT
111 using namespace std::chrono; // NOLINT
112 if (iter % 25 == 0) {
113 print("{:<6s}", "ITER");
114 print("{:^10s}", "TIME");
115 print(emphasis::bold, "{:^12s}", "∥r∥");
116 print("{:^9s}", "Δ");
117 print("{:^9}", "ρ");
118 print("{:^9s}", "∥D dx∥");
119 print("{:^9s}", "∥D∥");
120 print("{:^6s}", "STEP");
121 print("\n");
122 }
123 const auto step_col = take_step ? color::dark_green : color::dark_red;
124 const auto rho_col = rho > 0 ? color::dark_green : color::dark_red;
125
126 print("{:<6}", iter);
127 print("{:>9} ", duration_cast<milliseconds>(high_resolution_clock::now() - t0));
128 print(emphasis::bold, "{:^12.4e}", r_n);
129 print("{:^9.1e}", Delta);
130 print(fg(rho_col), "{:^9.1e}", rho);
131 print("{:^9.1e}", d.cwiseProduct(dx).stableNorm());
132 print("{:^9.1e}", d.norm());
133 print(fg(step_col), "{:^5s}", take_step ? " ✔️ " : " ❌ ");
134 print("\n");
135#endif
136 }
137
138 // step
139 if (r_n == 0 || pred_red <= 0 || take_step) {
140 x = xp;
141
142 // execute callback on updated value
143 std::apply(cb, x);
144
145 // check for convergence
146 if (std::abs(actu_red) < opts.ftol && pred_red < opts.ftol && rho <= 2.) {
147 status = SolveResult::Status::Ftol;
148 } else if (d.cwiseProduct(dx).stableNorm() < opts.ptol * static_cast<double>(dx.size())) {
149 status = SolveResult::Status::Ptol;
150 }
151 }
152 }
153
154 if (opts.verbose) {
155#ifdef SMOOTH_HAS_FMT
156 using namespace fmt; // NOLINT
157 using namespace std::chrono; // NOLINT
158
159 print("{0:x^69s}\n", "");
160 print("{:>10s}: {}\n", "Total time", duration_cast<milliseconds>(high_resolution_clock::now() - t0));
161 print("{:>10s}: {}\n", "Iterations", iter);
162 print("{:>10s}: {:.2f}\n", "Objective", std::apply(f, x).norm());
163 print("{0:x^69s}\n", "");
164#endif
165 }
166
167 return {
168 .status = status.value_or(SolveResult::Status::MaxIters),
169 .iter = iter,
170 .time = std::chrono::high_resolution_clock::now() - t0,
171 };
172}
173
188template<diff::Type D>
189SolveResult minimize(auto && f, auto && x, const MinimizeOptions & opts = {})
190{
191 static constexpr auto do_nothing = [](const auto &...) {};
192 return minimize<D>(std::forward<decltype(f)>(f), std::forward<decltype(x)>(x), do_nothing, opts);
193}
194
209SolveResult minimize(auto && f, auto && x, const MinimizeOptions & opts = {})
210{
211 static constexpr auto do_nothing = [](const auto &...) {};
212 return minimize<diff::Type::Default>(std::forward<decltype(f)>(f), std::forward<decltype(x)>(x), do_nothing, opts);
213}
214
215SMOOTH_END_NAMESPACE
Differentiation on Manifolds.
std::shared_ptr< TrustRegionStrategy > strat
strategy
Definition optim.hpp:29
double ptol
relative parameter tolerance for convergence
Definition optim.hpp:31
double ftol
relative function tolerance for convergence
Definition optim.hpp:33
bool verbose
print solver status to stdout
Definition optim.hpp:37
std::size_t max_iter
maximum number of iterations
Definition optim.hpp:35
Trust region algorithms for determining step size.
auto solve_trust_region(const auto &J, const Eigen::MatrixBase< D2 > &d, const Eigen::MatrixBase< D3 > &r, const double Delta) -> std::pair< Eigen::Vector< typename std::decay_t< decltype(J)>::Scalar, std::decay_t< decltype(J)>::ColsAtCompileTime >, double >
Approximately solve trust-region step determination problem.