tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
gn.h
1// Copyright 2026 Julien Michot.
2// SPDX-License-Identifier: Apache-2.0
3
4#pragma once
5
6#include <tinyopt/cost.h>
7#include <tinyopt/solvers/base.h>
8#include <tinyopt/traits.h>
9#include <optional>
10#include "tinyopt/math.h"
11
12namespace tinyopt::solvers {
13
14template <typename Hessian_t = MatX>
15class SolverGN
16 : public SolverBase<typename Hessian_t::Scalar, SQRT(traits::params_trait<Hessian_t>::Dims)> {
17 public:
18 static constexpr bool IsNLLS = true;
19 static constexpr bool FirstOrder = false; // this is a pseudo second order algorithm
20 using Base = SolverBase<typename Hessian_t::Scalar, SQRT(traits::params_trait<Hessian_t>::Dims)>;
21 using Scalar = typename Hessian_t::Scalar;
22 static constexpr Index Dims = SQRT(traits::params_trait<Hessian_t>::Dims);
23
24 // Hessian Type
25 using H_t = Hessian_t;
26 // Gradient Type
27 using Grad_t = Vector<Scalar, Dims>;
28 // Options
29 using Base::options_;
30
31 explicit SolverGN(const Options &options = {}) : Base(options) {}
32
34 void InitWith(const Grad_t &g, const H_t &h) {
35 grad_ = g;
36 H_ = h;
37 }
38
40 virtual void reset() { clear(); }
41
43 template <int D = Dims, std::enable_if_t<D == Dynamic, int> = 0>
44 bool resize(int dims) {
45 if (dims == Dynamic) {
46 throw std::invalid_argument("Dimensions cannot be Dynamic here");
47 }
48 if (grad_.rows() != dims || H_.rows() != dims) {
49 H_.resize(dims, dims);
50 grad_.resize(dims);
51 return true;
52 } else {
53 return false;
54 }
55 }
56
58 template <int D = Dims, std::enable_if_t<D != Dynamic, int> = 0>
59 bool resize(int dims = Dims) {
60 if (dims != Dims) {
61 throw std::invalid_argument("Error: Static and Dynamic Dimensions must match");
62 }
63 if constexpr (traits::is_sparse_matrix_v<H_t>) {
64 H_.resize(dims, dims);
65 grad_.resize(dims);
66 return true;
67 }
68 return false;
69 }
70
72 void clear() {
73 // Fill H & grad fill 0s (not needed when using auto-jet)
74 H_.setZero();
75 grad_.setZero();
76 }
77
79 template <typename X_t>
80 bool ResizeIfNeeded(const X_t &x) {
81 if constexpr (Dims == Dynamic) {
82 const int dims = traits::params_trait<X_t>::dims(x);
83 if (grad_.rows() != dims) {
84 if (options_.log.enable) TINYOPT_LOG("Need to resize the system");
85 return resize(dims);
86 }
87 }
88 return false;
89 }
90
92 template <typename X_t, typename AccFunc>
93 inline Scalar Evaluate(const X_t &x, const AccFunc &acc, bool save) {
94 std::nullptr_t nul;
95 Hessian_t H; // dummy;
96 Cost cost = acc(x, nul, H);
97 this->NormalizeCost(cost);
98 if (save) this->cost_ = cost;
99 return cost.cost;
100 }
101
103 template <typename X_t, typename AccFunc>
104 inline bool Accumulate(const X_t &x, const AccFunc &acc) {
105 this->cost_ = acc(x, grad_, H_);
106 this->NormalizeCost(this->cost_);
107 return this->cost_.isValid();
108 }
109
112 template <typename X_t, typename AccFunc>
113 inline bool Build(const X_t &x, const AccFunc &acc_func, bool resize_and_clear = true) {
114 // Resize the system if needed and clear gradient
115 if (resize_and_clear) {
117 clear();
118 }
119
120 // Accumulate residuals and update both gardient and Hessian approx (Jt*J)
121 const bool success = Accumulate(x, acc_func);
122 if (!success) return false;
123
124 // Eventually clip the gradient
125 this->Clamp(grad_, options_.opt.grad_clipping);
126
127 // Verify Hessian's diagonal
128 if (options_.hessian.check_min_H_diag > 0 &&
129 (H_.diagonal().cwiseAbs().array() < options_.hessian.check_min_H_diag).any()) {
130 if (options_.log.enable) TINYOPT_LOG("❌ Hessian has very low diagonal coefficients");
131 return false;
132 }
133
134 // Fill the lower part if H if needed
135 {
136 if (!options_.hessian.H_is_full && RequiresFullMatrix(options_.linear_solver))
137 CompleteSymmetricMatrix(H_);
138 }
139#if defined(TINYOPT_ENABLE_SUITESPARSE)
140 if constexpr (traits::is_sparse_matrix_v<H_t>) {
141 if (options_.linear_solver == LinearSolverMethod::SuiteSparse) H_.makeCompressed();
142 }
143#endif
144 return true;
145 }
146
148 inline std::optional<Vector<Scalar, Dims>> Solve() const override {
149 if (!this->cost().isValid()) return std::nullopt;
150
151 const auto dx_ = tinyopt::SolveLinearSystem(H_, -grad_, options_.linear_solver,
152 options_.svd_relative_threshold);
153 if (dx_) return dx_;
154 // Log on failure
155 if (options_.log.enable && options_.log.print_failure) {
156 TINYOPT_LOG("❌ Failed solve linear system");
157 TINYOPT_LOG("grad = \n{}", grad_);
158 TINYOPT_LOG("H = \n{}", H_);
159 }
160 return std::nullopt;
161 }
162
163 virtual Index dims() const override { return grad_.size(); }
164
167 Scalar MaxStdDev(bool use_damped = true) const {
168 const auto &H = use_damped ? H_ : Hessian();
169 if constexpr (traits::is_sparse_matrix_v<H_t>)
170 return sqrt(InvCov(H).value().coeffs().maxCoeff());
171 else
172 return sqrt(InvCov(H).value().maxCoeff());
173 }
174
176 virtual std::optional<H_t> Covariance() const { return InvCov(H_); }
177
179 const H_t &Hessian() const { return H_; }
180
181 const H_t &H() const { return H_; }
182 H_t &H() { return H_; }
183
184 const Grad_t &Gradient() const { return grad_; }
185 Grad_t &Gradient() { return grad_; }
186 Scalar GradientNorm() const { return grad_.norm(); }
187 Scalar GradientSquaredNorm() const { return grad_.squaredNorm(); }
188
189 protected:
190 H_t H_;
191 Grad_t grad_;
192};
193
194} // namespace tinyopt::solvers
void NormalizeCost(Cost &cost)
Eventually normalize the cost.
Definition base.h:41
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)
Accumulate residuals and update the gradient, returns true on success.
Definition gn.h:104
Scalar Evaluate(const X_t &x, const AccFunc &acc, bool save)
Accumulate residuals and return the final error.
Definition gn.h:93
bool ResizeIfNeeded(const X_t &x)
Check whether we need to resize the system (gradient), return true if it did.
Definition gn.h:80
virtual void reset()
Reset the solver state and clear gradient & hessian.
Definition gn.h:40
virtual std::optional< H_t > Covariance() const
Latest Covariance estimate.
Definition gn.h:176
void InitWith(const Grad_t &g, const H_t &h)
Initialize solver with specific gradient and hessian.
Definition gn.h:34
bool resize(int dims=Dims)
Resize H and grad if needed, return true if they were resized.
Definition gn.h:59
bool resize(int dims)
Resize H and grad if needed, return true if they were resized.
Definition gn.h:44
bool Build(const X_t &x, const AccFunc &acc_func, bool resize_and_clear=true)
Definition gn.h:113
Scalar MaxStdDev(bool use_damped=true) const
Definition gn.h:167
std::optional< Vector< Scalar, Dims > > Solve() const override
Solve the linear system dx = -H^-1 * grad, returns nullopt on failure.
Definition gn.h:148
const H_t & Hessian() const
Definition gn.h:179
Definition cost.h:18
Definition options.h:18