tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
gd.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
10namespace tinyopt::solvers {
11
12template <typename Gradient_t = VecX>
13class SolverGD
14 : public SolverBase<typename Gradient_t::Scalar, traits::params_trait<Gradient_t>::Dims> {
15 public:
16 static constexpr bool IsNLLS = false; // by default it is not
17 static constexpr bool FirstOrder = true;
18 using Base = SolverBase<typename Gradient_t::Scalar, traits::params_trait<Gradient_t>::Dims>;
19 using Scalar = typename Gradient_t::Scalar;
20 static constexpr Index Dims = traits::params_trait<Gradient_t>::Dims;
21 // Gradient Type
22 using Grad_t = Gradient_t;
23 // Hessian Type (here none, so nullptr_t)
24 using H_t = std::nullptr_t;
25 // Options
26 using Base::options_;
27
28 explicit SolverGD(const Options &options = {}) : Base(options) {}
29
31 void InitWith(const Grad_t &g) { grad_ = g; }
32
34 void reset() { clear(); }
35
37 template <int D = Dims, std::enable_if_t<D == Dynamic, int> = 0>
38 bool resize(int dims) {
39 if (dims == Dynamic) {
40 throw std::invalid_argument("Dimensions cannot be Dynamic here");
41 }
42 if (grad_.rows() != dims) {
43 grad_.resize(dims);
44 return true;
45 } else {
46 return false;
47 }
48 }
49
51 template <int D = Dims, std::enable_if_t<D != Dynamic, int> = 0>
52 bool resize(int dims = Dims) {
53 if (dims != Dims) {
54 throw std::invalid_argument("Error: Static and Dynamic Dimensions must match");
55 }
56 if constexpr (traits::is_sparse_matrix_v<Grad_t>) {
57 grad_.resize(dims);
58 return true;
59 }
60 return false;
61 }
62
64 void clear() { grad_.setZero(); }
65
67 template <typename X_t>
68 bool ResizeIfNeeded(const X_t &x) {
69 if constexpr (Dims == Dynamic) {
70 const int dims = traits::params_trait<X_t>::dims(x);
71 if (grad_.rows() != dims) {
72 if (options_.log.enable) TINYOPT_LOG("Need to resize the system");
73 return resize(dims);
74 }
75 }
76 return false;
77 }
78
80 template <typename AccFunc>
81 inline auto GetAccFunc(const AccFunc &acc) const {
82 return [&](const auto &x, auto &grad) -> Cost {
83 using X_t = decltype(x);
84 if constexpr (std::is_invocable_v<AccFunc, const X_t &, std::nullptr_t &>)
85 return acc(x, grad);
86 else {
87 std::nullptr_t nul;
88 return acc(x, grad, nul);
89 }
90 };
91 }
92
94 template <typename X_t, typename AccFunc>
95 inline Scalar Evaluate(const X_t &x, const AccFunc &acc, bool save) {
96 std::nullptr_t nul;
97 const auto acc2 = GetAccFunc(acc);
98 const auto &cost = acc2(x, nul);
99 this->NormalizeCost(cost);
100 if (save) this->cost_ = cost;
101 return cost.cost;
102 }
103
105 template <typename X_t, typename AccFunc>
106 inline bool Accumulate(const X_t &x, const AccFunc &acc) {
107 const auto acc2 = GetAccFunc(acc);
108 this->cost_ = acc2(x, grad_);
109 this->NormalizeCost(this->cost_);
110 return this->cost_.isValid();
111 }
112
115 template <typename X_t, typename AccFunc>
116 inline bool Build(const X_t &x, const AccFunc &acc, bool resize_and_clear = true) {
117 // Resize the system if needed and clear gradient
118 if (resize_and_clear) {
120 clear();
121 }
122 // Update gradient by accumulating changes
123 const bool ok = Accumulate(x, acc);
124 // Eventually clip the gradients
125 this->Clamp(grad_, options_.opt.grad_clipping);
126 return ok;
127 }
128
130 inline std::optional<Vector<Scalar, Dims>> Solve() const override {
131 if (!this->cost().isValid()) return std::nullopt;
132 return -options_.gd.lr * grad_;
133 }
134
135 Index dims() const override { return grad_.size(); }
136
137 const Grad_t &Gradient() const { return grad_; }
138 Grad_t &Gradient() { return grad_; }
139 Scalar GradientNorm() const { return grad_.norm(); }
140 Scalar GradientSquaredNorm() const { return grad_.squaredNorm(); }
141
142 protected:
143 Grad_t grad_;
144};
145
146} // 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 gd.h:106
auto GetAccFunc(const AccFunc &acc) const
Ugly function to make sure the returned type is always a pair<scalar, scalar>...
Definition gd.h:81
std::optional< Vector< Scalar, Dims > > Solve() const override
Solve the linear system dx = -lr * grad, returns nullopt on failure.
Definition gd.h:130
bool Build(const X_t &x, const AccFunc &acc, bool resize_and_clear=true)
Definition gd.h:116
void reset()
Reset the solver state and clear gradient & hessian.
Definition gd.h:34
Scalar Evaluate(const X_t &x, const AccFunc &acc, bool save)
Accumulate residuals and return the final error.
Definition gd.h:95
bool resize(int dims=Dims)
Resize H and grad if needed, return true if they were resized.
Definition gd.h:52
bool ResizeIfNeeded(const X_t &x)
Check whether we need to resize the system (gradient), return true if it did.
Definition gd.h:68
void InitWith(const Grad_t &g)
Initialize solver with specific gradient and hessian.
Definition gd.h:31
bool resize(int dims)
Resize H and grad if needed, return true if they were resized.
Definition gd.h:38
Definition cost.h:18
Definition options.h:18