6#include <tinyopt/cost.h>
7#include <tinyopt/log.h>
8#include <tinyopt/solvers/gn.h>
16using SolverOptions = tinyopt::Options;
19namespace tinyopt::solvers {
21template <
typename Hessian_t = MatX>
24 static constexpr bool IsNLLS =
true;
25 static constexpr bool FirstOrder =
false;
27 using Scalar =
typename Hessian_t::Scalar;
28 static constexpr Index Dims = Base::Dims;
31 using H_t = Hessian_t;
33 using Grad_t = Vector<Scalar, Dims>;
37 explicit SolverLM(
const Options &options = {}) : Base(options), options_{options} {
reset(); }
42 lambda_ = options_.lm.damping_init;
53 template <
typename X_t,
typename AccFunc>
54 inline bool Build(
const X_t &x,
const AccFunc &acc_func,
bool resize_and_clear =
true) {
57 if (resize_and_clear) {
63 const bool success = this->
Accumulate(x, acc_func);
67 if (options_.log.enable)
68 TINYOPT_LOG(
"❌ Failed to accumulate residuals: {}", this->cost().toString());
73 this->
Clamp(this->grad_, options_.opt.grad_clipping);
76 if (options_.hessian.check_min_H_diag > 0 &&
77 (this->H_.diagonal().cwiseAbs().array() < options_.hessian.check_min_H_diag).any()) {
78 if (options_.log.enable) TINYOPT_LOG(
"❌ Hessian has very low diagonal coefficients");
83 if (!options_.hessian.H_is_full && RequiresFullMatrix(options_.linear_solver))
84 CompleteSymmetricMatrix(this->H_);
86 if (options_.lm.jacobi_scaling) ApplyJacobiScaling();
91 const bool success = this->cost().isValid();
94 if (options_.log.enable) TINYOPT_LOG(
"❌ Failed to accumulate residuals");
103 for (
int i = 0; i < this->H_.rows(); ++i) {
104 if constexpr (traits::is_matrix_or_array_v<H_t>)
107 this->H_.coeffRef(i, i) *= s;
111#if defined(TINYOPT_ENABLE_SUITESPARSE)
112 if constexpr (traits::is_sparse_matrix_v<H_t>) {
113 if (options_.linear_solver == LinearSolverMethod::SuiteSparse) this->H_.makeCompressed();
122 Scalar s = options_.lm.good_factor;
125 if (quality != Scalar(0.0)) {
126 s = std::max<Scalar>(s, 1.0f - std::pow(2.0f * quality - 1.0f, 3.0f));
134 std::clamp<Scalar>(
lambda_ * s, options_.lm.damping_range[0], options_.lm.damping_range[1]);
143 std::clamp<Scalar>(
lambda_ * s, options_.lm.damping_range[0], options_.lm.damping_range[1]);
150 std::optional<Grad_t>
Solve()
const override {
152 if (delta && options_.lm.jacobi_scaling) delta->array() *= scaling_.array();
156 std::string stateAsString()
const override {
157 std::ostringstream oss;
158 oss << TINYOPT_FORMAT_NS::format(
"○:{:.2e} ", 1.0 /
lambda_);
167 for (
int i = 0; i < this->H_.cols(); ++i) {
168 if constexpr (traits::is_matrix_or_array_v<H_t>)
171 H.coeffRef(i, i) = this->H_.coeff(i, i) / s;
174 if (options_.lm.jacobi_scaling) ApplyJacobiScaling(H,
true);
184 H_t H = use_damped ? this->H_ :
Hessian();
185 if (use_damped && options_.lm.jacobi_scaling) ApplyJacobiScaling(H,
true);
186 const auto I = InvCov(H);
189 if constexpr (traits::is_sparse_matrix_v<H_t>)
190 return sqrt(I.value().coeffs().maxCoeff());
192 return sqrt(I.value().maxCoeff());
196 void ApplyJacobiScaling() {
197 constexpr Scalar MinDiagonal = Scalar(1e-6);
198 constexpr Scalar MaxDiagonal = Scalar(1e32);
199 scaling_.resize(this->H_.rows());
200 for (Index index = 0; index < this->H_.rows(); ++index) {
201 const Scalar diagonal = std::clamp(this->H_.coeff(index, index), MinDiagonal, MaxDiagonal);
202 scaling_[index] = Scalar(1) / std::sqrt(diagonal);
204 this->grad_.array() *= scaling_.array();
205 ApplyJacobiScaling(this->H_,
false);
208 void ApplyJacobiScaling(H_t &hessian,
bool inverse)
const {
209 const auto scale_entry = [&](
auto &value, Index row, Index column) {
210 const Scalar factor = scaling_[row] * scaling_[column];
216 if constexpr (traits::is_sparse_matrix_v<H_t>) {
217 for (Index outer = 0; outer < hessian.outerSize(); ++outer) {
218 for (
typename H_t::InnerIterator entry(hessian, outer); entry; ++entry)
219 scale_entry(entry.valueRef(), entry.row(), entry.col());
222 for (Index column = 0; column < hessian.cols(); ++column)
223 for (Index row = 0; row < hessian.rows(); ++row)
224 scale_entry(hessian(row, column), row, column);
228 const Options options_;
bool Clamp(Grad_t &g, Scalar minmax) const
Clamp the gradient 'g' to within [-minmax, minmax], if minmax is not 0. Returns true if 'g' was clamp...
Definition base.h:30
bool Accumulate(const X_t &x, const AccFunc &acc)
Definition gn.h:104
Scalar Evaluate(const X_t &x, const AccFunc &acc, bool save)
Definition gn.h:93
bool ResizeIfNeeded(const X_t &x)
Definition gn.h:80
void clear()
Definition gn.h:72
std::optional< Vector< Scalar, Dims > > Solve() const override
Definition gn.h:148
Scalar lambda_
Definition lm.h:230
Scalar bad_factor_
Definition lm.h:232
std::optional< H_t > Covariance() const override
Latest Covariance estimate.
Definition lm.h:179
void FailedStep() override
Damping stategy for a failure to solve the linear system, decrease the damping factor \lambda.
Definition lm.h:148
bool Build(const X_t &x, const AccFunc &acc_func, bool resize_and_clear=true)
Definition lm.h:54
void BadStep(Scalar=0.0f) override
Damping stategy for a bad step: decrease the damping factor \lambda.
Definition lm.h:139
Scalar MaxStdDev(bool use_damped=true) const
Definition lm.h:183
void GoodStep(Scalar quality) override
Damping stategy for a good step: increase the damping factor \lambda.
Definition lm.h:121
Scalar prev_lambda_
Definition lm.h:231
std::optional< Grad_t > Solve() const override
Solve the linear system dx = -H^-1 * grad, returns nullopt on failure.
Definition lm.h:150
void Rebuild(bool b) override
Force the solver to rebuild or skip it.
Definition lm.h:49
bool rebuild_linear_system_
Definition lm.h:233
H_t Hessian() const
Latest Hessian approximation (JtJ), un-damped.
Definition lm.h:163
void reset() override
Reset the solver state and clear gradient & hessian.
Definition lm.h:40
Levenberg-Marquardt specific solver, optimizer and their options.
Definition lm.h:13