tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
dl.h
1// Copyright 2026 Julien Michot.
2// SPDX-License-Identifier: Apache-2.0
3
4#pragma once
5
6#include <algorithm>
7#include <cmath>
8#include <limits>
9
10#include <tinyopt/solvers/gn.h>
11
12namespace tinyopt::solvers {
13
14template <typename Hessian_t = MatX>
15class SolverDogLeg : public SolverGN<Hessian_t> {
16 public:
17 using Base = SolverGN<Hessian_t>;
18 using Scalar = typename Base::Scalar;
19 using Grad_t = typename Base::Grad_t;
20 using H_t = typename Base::H_t;
21 static constexpr Index Dims = Base::Dims;
22 static constexpr bool IsNLLS = true;
23 static constexpr bool FirstOrder = false;
24
25 explicit SolverDogLeg(const Options &options = {})
26 : Base(options), radius_(std::max<Scalar>(options.dl.radius_init,
27 std::numeric_limits<Scalar>::epsilon())) {}
28
29 void reset() override {
31 radius_ = std::max<Scalar>(this->options_.dl.radius_init,
32 std::numeric_limits<Scalar>::epsilon());
33 pending_step_ = false;
34 }
35
36 template <typename X_t, typename AccFunc>
37 bool Build(const X_t &x, const AccFunc &acc, bool resize_and_clear = true) {
38 if (!Base::Build(x, acc, resize_and_clear)) return false;
39 if (pending_step_) UpdateRadius(this->cost().cost);
40 return true;
41 }
42
43 std::optional<Vector<Scalar, Dims>> Solve() const override {
44 if (!this->cost().isValid()) return std::nullopt;
45
46 const auto &gradient = this->Gradient();
47 const Scalar gradient_norm2 = gradient.squaredNorm();
48 if (!std::isfinite(gradient_norm2)) return std::nullopt;
49 if (gradient_norm2 <= std::numeric_limits<Scalar>::epsilon()) {
50 pending_step_ = false;
51 return Vector<Scalar, Dims>::Zero(gradient.size());
52 }
53
54 const auto gauss_newton =
55 tinyopt::SolveLinearSystem(this->Hessian(), -gradient, this->options_.linear_solver,
56 this->options_.svd_relative_threshold);
57 const auto hessian_gradient = (this->Hessian() * gradient).eval();
58 const Scalar curvature = gradient.dot(hessian_gradient);
59 Vector<Scalar, Dims> cauchy;
60 if (curvature > std::numeric_limits<Scalar>::epsilon())
61 cauchy = -(gradient_norm2 / curvature) * gradient;
62 else
63 cauchy = -(radius_ / std::sqrt(gradient_norm2)) * gradient;
64
65 Vector<Scalar, Dims> step;
66 bool on_boundary = false;
67 if (gauss_newton && gauss_newton->norm() <= radius_) {
68 step = *gauss_newton;
69 } else if (cauchy.norm() >= radius_ || !gauss_newton) {
70 const Scalar cauchy_norm = cauchy.norm();
71 step = (radius_ / cauchy_norm) * cauchy;
72 on_boundary = true;
73 } else {
74 const Vector<Scalar, Dims> difference = *gauss_newton - cauchy;
75 const Scalar a = difference.squaredNorm();
76 const Scalar b = Scalar(2) * cauchy.dot(difference);
77 const Scalar c = cauchy.squaredNorm() - radius_ * radius_;
78 const Scalar discriminant = std::max<Scalar>(0, b * b - Scalar(4) * a * c);
79 const Scalar tau =
80 std::clamp((-b + std::sqrt(discriminant)) / (Scalar(2) * a), Scalar(0), Scalar(1));
81 step = cauchy + tau * difference;
82 on_boundary = true;
83 }
84
85 const auto hessian_step = (this->Hessian() * step).eval();
86 pending_prediction_ = -Scalar(2) * gradient.dot(step) - step.dot(hessian_step);
87 pending_cost_ = this->cost().cost;
88 pending_boundary_ = on_boundary;
89 pending_step_ = pending_prediction_ > 0 && std::isfinite(pending_prediction_);
90 return step;
91 }
92
93 Scalar TrustRegionRadius() const { return radius_; }
94
95 private:
96 void UpdateRadius(Scalar current_cost) {
97 const Scalar ratio = (pending_cost_ - current_cost) / pending_prediction_;
98 if (ratio < Scalar(0.25))
99 radius_ *= this->options_.dl.shrink_factor;
100 else if (ratio > Scalar(0.75) && pending_boundary_)
101 radius_ = std::min<Scalar>(this->options_.dl.radius_max,
102 radius_ * this->options_.dl.expand_factor);
103 pending_step_ = false;
104 }
105
106 Scalar radius_;
107 mutable Scalar pending_prediction_ = 0;
108 mutable Scalar pending_cost_ = 0;
109 mutable bool pending_boundary_ = false;
110 mutable bool pending_step_ = false;
111};
112
113} // namespace tinyopt::solvers
void reset() override
Reset the solver state and clear gradient & hessian.
Definition dl.h:29
std::optional< Vector< Scalar, Dims > > Solve() const override
Solve the linear system dx = -H^-1 * grad, returns nullopt on failure.
Definition dl.h:43
virtual void reset()
Definition gn.h:40
bool Build(const X_t &x, const AccFunc &acc_func, bool resize_and_clear=true)
Definition gn.h:113
const H_t & Hessian() const
Definition gn.h:179
Definition options.h:18