tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
auto_diff.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/log.h>
8#include <tinyopt/losses/robust_norms.h>
9#include <tinyopt/math.h>
10
11#include <tinyopt/diff/jet.h>
12
13namespace tinyopt::diff {
14
17template <typename X_t, typename CostOrResFunc>
18auto Eval(const X_t &x, const CostOrResFunc &cost_or_res_func, bool check_residuals = true) {
19 using ptrait = traits::params_trait<X_t>;
20 using Scalar = typename ptrait::Scalar;
21 constexpr Index Dims = ptrait::Dims;
22 constexpr bool is_userdef_type =
23 !std::is_floating_point_v<X_t> && !traits::is_matrix_or_array_v<X_t>;
24 const Index dims = traits::DynDims(x);
25
26 // Construct the Jet
27 using Jet = diff::Jet<Scalar, Dims>;
28
29 // Construct the Jet for scalar or Matrix, so {Jet, Vector<Jet, N> ot nullptr_t}
30 auto x_jet = ptrait::template cast<Jet>(x);
31
32 if constexpr (is_userdef_type) { // X is user defined object
33 Vector<Jet, Dims> dx_jet = Vector<Jet, Dims>::Zero(dims);
34 for (Index i = 0; i < dims; ++i) {
35 // If X size at compile time is not known, we need to set the Jet.v
36 if constexpr (Dims == Dynamic) dx_jet[i].v = Vector<Scalar, Dynamic>::Zero(dims);
37 dx_jet[i].v[i] = 1;
38 }
39 using ptrait_jet = traits::params_trait<std::decay_t<decltype(x_jet)>>;
40 ptrait_jet::PlusEq(x_jet, dx_jet);
41 } else if constexpr (std::is_floating_point_v<X_t>) { // X is scalar
42 x_jet.v[0] = 1;
43 } else { // X is a Vector or Matrix
44 // Set Jet's v
45 for (int c = 0; c < x.cols(); ++c) {
46 for (int r = 0; r < x.rows(); ++r) {
47 const auto i = r + c * x.rows();
48 if constexpr (Dims == Dynamic) x_jet(r, c).v = Vector<Scalar, Dims>::Zero(dims);
49 x_jet(r, c).v[i] = 1;
50 }
51 }
52 }
53
54 // Support different function signatures
55 auto fg = [&](const auto &x) {
56 std::nullptr_t nul;
57 if constexpr (std::is_invocable_v<CostOrResFunc, const X_t &>)
58 return cost_or_res_func(x);
59 else if constexpr (std::is_invocable_v<CostOrResFunc, const X_t &, std::nullptr_t &>)
60 return cost_or_res_func(x, nul);
61 else if constexpr (std::is_invocable_v<CostOrResFunc, const X_t &, std::nullptr_t &,
62 std::nullptr_t &>)
63 return cost_or_res_func(x, nul, nul);
64 else { // likely a SparseMatrix<Scalar> hessian
65 SparseMatrix<Scalar> H;
66 H.resize(dims, dims);
67 return cost_or_res_func(x, nul, H);
68 }
69 };
70
71 // Retrieve the residuals
72 const auto res = fg(x_jet);
73 using ResType = typename std::decay_t<decltype(res)>;
74
75 if constexpr (traits::is_robust_residual_v<ResType>) {
76 const auto &raw = losses::UnwrapResidual(res);
77 using RawType = std::decay_t<decltype(raw)>;
78 static_assert(
79 traits::is_jet_type_v<RawType> || (traits::is_matrix_or_array_v<RawType> &&
80 traits::is_jet_type_v<typename RawType::Scalar>),
81 "Robust residuals must wrap Jet-valued residuals");
82
83 if constexpr (traits::is_jet_type_v<RawType>) {
84 return std::make_pair(raw.a, raw.v.transpose().eval());
85 } else {
86 constexpr int ResDims = traits::params_trait<RawType>::Dims;
87 const Index res_dims = traits::DynDims(raw);
88 Matrix<Scalar, ResDims, Dims> J(res_dims, dims);
89 Vector<Scalar, ResDims> res_f(res.size());
90 if constexpr (RawType::ColsAtCompileTime != 1) {
91 for (int c = 0; c < raw.cols(); ++c)
92 for (int r = 0; r < raw.rows(); ++r) {
93 const Index i = r + c * raw.rows();
94 res_f[i] = raw(r, c).a;
95 if constexpr (Dims == Dynamic) {
96 if (check_residuals && raw(r, c).v.size() != dims) {
97 TINYOPT_LOG("⚠️ Residual ({},{}) is not connected to the parameters", r, c);
98 J.row(i).setZero();
99 continue;
100 }
101 }
102 J.row(i) = raw(r, c).v;
103 }
104 } else {
105 for (Index i = 0; i < res_dims; ++i) {
106 res_f[i] = raw[i].a;
107 if constexpr (Dims == Dynamic) {
108 if (check_residuals && raw[i].v.size() != dims) {
109 TINYOPT_LOG("⚠️ Residual #{} is not connected to the parameters", i);
110 J.row(i).setZero();
111 continue;
112 }
113 }
114 J.row(i) = raw[i].v;
115 }
116 }
117 return std::make_pair(res_f, J);
118 }
119 } else {
120 // Make sure the return type is either a Jet or Matrix/Array<Jet>
121 static_assert(
122 traits::is_jet_type_v<ResType> ||
123 (traits::is_matrix_or_array_v<ResType> && traits::is_jet_type_v<typename ResType::Scalar>));
124
125 if constexpr (!traits::is_matrix_or_array_v<ResType>) { // One residual
126 return std::make_pair(res.a, res.v.transpose().eval());
127 } else {
128 constexpr int ResDims = traits::params_trait<ResType>::Dims;
129 const Index res_dims = traits::DynDims(res);
130
131 Matrix<Scalar, ResDims, Dims> J(res_dims, dims);
132 Vector<Scalar, ResDims> res_f(res.size());
133 if constexpr (traits::is_matrix_or_array_v<ResType>) {
134 if constexpr (ResType::ColsAtCompileTime != 1) { // Matrix or Vector with dynamic size
135 for (int c = 0; c < res.cols(); ++c)
136 for (int r = 0; r < res.rows(); ++r) {
137 const Index i = r + c * res.rows();
138 res_f[i] = res(r, c).a;
139 if constexpr (Dims == Dynamic) {
140 if (check_residuals && res(r, c).v.size() != dims) {
141 TINYOPT_LOG("⚠️ Residual ({},{}) is not connected to the parameters", r, c);
142 J.row(i).setZero();
143 continue;
144 }
145 }
146 J.row(i) = res(r, c).v;
147 }
148 } else { // Vector
149 for (Index i = 0; i < res_dims; ++i) {
150 res_f[i] = res[i].a;
151 if constexpr (Dims == Dynamic) {
152 if (check_residuals && res[i].v.size() != dims) {
153 TINYOPT_LOG("⚠️ Residual #{} is not connected to the parameters", i);
154 J.row(i).setZero();
155 continue;
156 }
157 }
158 J.row(i) = res[i].v;
159 }
160 }
161 } else { // scalar
162 for (Index i = 0; i < res_dims; ++i) {
163 res_f[i] = res.a;
164 if constexpr (Dims == Dynamic) {
165 if (check_residuals && res.v.size() != dims) {
166 TINYOPT_LOG("⚠️ Residual is not connected to the parameters");
167 J.row(i).setZero();
168 continue;
169 }
170 }
171 J.row(i) = res.v;
172 }
173 }
174 return std::make_pair(res_f, J);
175 }
176 }
177}
178
181template <typename X_t, typename CostOrResFunc>
182auto CalculateJac(const X_t &x, const CostOrResFunc &cost_func, bool check_residuals = true) {
183 const auto &[res, J] = Eval(x, cost_func, check_residuals);
184 return J;
185}
186
187} // namespace tinyopt::diff