13#include <tinyopt/cost.h>
14#include <tinyopt/math.h>
15#include <tinyopt/traits.h>
17#include <tinyopt/diff/jet.h>
23template <
bool IsNLLS,
typename X_t,
typename ResidualsFunc,
typename OptimizeFunc,
25inline auto OptimizeWithAutoDiff(X_t &x,
const ResidualsFunc &residuals,
26 const OptimizeFunc &optimize,
const OptionsType &options) {
28 using Scalar =
typename ptrait::Scalar;
29 constexpr Index Dims = ptrait::Dims;
30 constexpr bool is_userdef_type =
31 !std::is_floating_point_v<X_t> && !traits::is_matrix_or_array_v<X_t>;
33 const Index dims = traits::DynDims(x);
36 using Jet = diff::Jet<Scalar, Dims>;
42 using XJetType = std::conditional_t<
43 std::is_floating_point_v<X_t>, Jet,
44 std::conditional_t<is_userdef_type, std::nullptr_t, decltype(ptrait::template cast<Jet>(x))>>;
48 using DXJetType = std::conditional_t<is_userdef_type, Vector<Jet, Dims>, std::nullptr_t>;
51 if constexpr (is_userdef_type) {
52 dx_jet = DXJetType::Zero(dims);
53 for (Index i = 0; i < dims; ++i) {
55 if constexpr (Dims == Dynamic) dx_jet[i].v = Vector<Scalar, Dynamic>::Zero(dims);
59 }
else if constexpr (std::is_floating_point_v<X_t>) {
63 x_jet = ptrait::template cast<Jet>(x);
65 for (
int c = 0; c < x.cols(); ++c) {
66 for (
int r = 0; r < x.rows(); ++r) {
67 const auto i = r + c * x.rows();
68 if constexpr (Dims == Dynamic) x_jet(r, c).v = Vector<Scalar, Dims>::Zero(dims);
76 auto update_x_jet = [&](
const auto &x) {
77 if constexpr (is_userdef_type) {
78 auto x_jet = ptrait::template cast<Jet>(x);
80 ptrait_jet::PlusEq(x_jet, dx_jet);
81 return std::move(x_jet);
82 }
else if constexpr (std::is_floating_point_v<X_t>) {
86 for (
int c = 0; c < x.cols(); ++c) {
87 for (
int r = 0; r < x.rows(); ++r) {
88 x_jet(r, c).a = x(r, c);
95 auto acc = [&](
const auto &x,
auto &grad,
auto &H) {
96 using H_t =
decltype(H);
97 constexpr bool HasGrad = !traits::is_nullptr_v<
decltype(grad)>;
98 constexpr bool HasH = !traits::is_nullptr_v<H_t>;
100 const auto &x_jet = update_x_jet(x);
101 const auto res = residuals(x_jet);
102 using ResType =
typename std::decay_t<
decltype(res)>;
104 static_assert(IsNLLS || traits::is_scalar_v<ResType> || traits::is_tuple_v<ResType>,
105 "General optimization cost function must return a scalar value");
107 auto process_tuple_item = [&](
const auto &item) ->
Cost {
108 using ItemType = std::decay_t<
decltype(item)>;
110 if constexpr (traits::is_robust_residual_v<ItemType>) {
111 const auto &raw = item.residual;
112 using RawType = std::decay_t<
decltype(raw)>;
113 static_assert(traits::is_jet_type_v<RawType> ||
114 (traits::is_matrix_or_array_v<RawType> &&
115 traits::is_jet_type_v<typename RawType::Scalar>));
117 constexpr int ResDims = traits::params_trait<RawType>::Dims;
118 const Index res_size = traits::DynDims(raw);
119 using J_t = Matrix<Scalar, ResDims, Dims>;
121 J_t J(res_size, dims);
122 Vector<Scalar, ResDims> res_f(res_size);
123 if constexpr (traits::is_matrix_or_array_v<RawType>) {
124 if constexpr (RawType::ColsAtCompileTime != 1) {
125 for (
int c = 0; c < raw.cols(); ++c)
126 for (
int r = 0; r < raw.rows(); ++r) {
127 const Index i = r + c * raw.rows();
128 J.row(i) = raw(r, c).v;
129 res_f[i] = raw(r, c).a;
132 for (Index i = 0; i < res_size; ++i) {
142 const double sq_norm =
static_cast<double>(res_f.squaredNorm());
143 const double weight =
144 sq_norm <= item.delta2
146 : item.delta / std::sqrt(std::max(sq_norm, std::numeric_limits<double>::min()));
147 const double robust_cost =
148 sq_norm <= item.delta2
151 std::sqrt(std::max(sq_norm, std::numeric_limits<double>::min())) -
154 if constexpr (HasGrad) grad.noalias() += weight * (J.transpose() * res_f);
155 if constexpr (HasH) {
156 if constexpr (traits::is_sparse_matrix_v<H_t>) {
157 H += (weight * (J.transpose() * J)).sparseView();
159 H.noalias() += weight * (J.transpose() * J);
163 return Cost(robust_cost, res_size, sq_norm <= item.delta2 ? 1.0f : 0.0f);
166 traits::is_jet_type_v<ItemType> || (traits::is_matrix_or_array_v<ItemType> &&
167 traits::is_jet_type_v<typename ItemType::Scalar>),
168 "Residual blocks must be Jet-valued vectors/matrices or robust wrappers");
170 if constexpr (traits::is_scalar_v<ItemType>) {
171 if constexpr (HasGrad) {
172 const auto &J = item.v;
173 if constexpr (std::is_floating_point_v<X_t>) {
174 grad[0] += J[0] * item.a;
175 if constexpr (HasH) H(0, 0) += J[0] * J[0];
177 grad.noalias() += J.transpose() * item.a;
178 if constexpr (HasH) {
179 if constexpr (traits::is_sparse_matrix_v<H_t>) {
180 H += (J * J.transpose()).sparseView();
182 H.noalias() += J * J.transpose();
187 return Cost(IsNLLS ? item.a * item.a : item.a, 1);
189 constexpr int ResDims = traits::params_trait<ItemType>::Dims;
190 const Index res_size = traits::DynDims(item);
191 using J_t = Matrix<Scalar, ResDims, Dims>;
193 J_t J(res_size, dims);
194 Vector<Scalar, ResDims> res_f(res_size);
195 if constexpr (traits::is_matrix_or_array_v<ItemType>) {
196 if constexpr (ItemType::ColsAtCompileTime != 1) {
197 for (
int c = 0; c < item.cols(); ++c)
198 for (
int r = 0; r < item.rows(); ++r) {
199 const Index i = r + c * item.rows();
200 J.row(i) = item(r, c).v;
201 res_f[i] = item(r, c).a;
204 for (Index i = 0; i < res_size; ++i) {
205 J.row(i) = item[i].v;
206 res_f[i] = item[i].a;
211 if constexpr (HasGrad) {
212 grad.noalias() += J.transpose() * res_f;
213 if constexpr (HasH) {
214 if constexpr (traits::is_sparse_matrix_v<H_t>) {
215 H += (J.transpose() * J).sparseView();
217 H.noalias() += J.transpose() * J;
221 return Cost(res_f.squaredNorm(), res_size);
226 if constexpr (traits::is_tuple_v<ResType>) {
228 std::apply([&](
auto &&...items) { ((total += process_tuple_item(items)), ...); }, res);
231 static_assert(IsNLLS || traits::is_scalar_v<ResType>,
232 "General optimization cost function must return a scalar value");
233 static_assert(traits::is_jet_type_v<ResType> ||
234 (traits::is_matrix_or_array_v<ResType> &&
235 traits::is_jet_type_v<typename ResType::Scalar>));
237 if constexpr (traits::is_scalar_v<ResType>) {
238 if constexpr (HasGrad) {
239 const auto &J = res.v;
240 if constexpr (std::is_floating_point_v<X_t>) {
241 if constexpr (IsNLLS)
242 grad[0] = J[0] * res.a;
245 if constexpr (HasH) H(0, 0) = J[0] * J[0];
247 if constexpr (IsNLLS)
248 grad.noalias() = J.transpose() * res.a;
251 if constexpr (HasH) {
252 if constexpr (traits::is_sparse_matrix_v<H_t>) {
253 H += (J * J.transpose()).sparseView();
255 H.noalias() += J * J.transpose();
260 return Cost{IsNLLS ? res.a * res.a : res.a};
262 constexpr int ResDims = traits::params_trait<ResType>::Dims;
263 const Index res_size = traits::DynDims(res);
264 using J_t = Matrix<Scalar, ResDims, Dims>;
266 J_t J(res_size, dims);
267 Vector<Scalar, ResDims> res_f(res.size());
268 if constexpr (traits::is_matrix_or_array_v<ResType>) {
269 if constexpr (ResType::ColsAtCompileTime != 1) {
270 for (
int c = 0; c < res.cols(); ++c)
271 for (
int r = 0; r < res.rows(); ++r) {
272 const Index i = r + c * res.rows();
273 if constexpr (HasGrad) J.row(i) = res(r, c).v;
274 res_f[i] = res(r, c).a;
277 for (Index i = 0; i < res_size; ++i) {
278 if constexpr (HasGrad) J.row(i) = res[i].v;
283 for (Index i = 0; i < res_size; ++i) {
284 if constexpr (HasGrad) J.row(i) = res.v;
288 if constexpr (HasGrad) {
289 grad = J.transpose() * res_f;
290 if constexpr (HasH) {
291 if constexpr (traits::is_sparse_matrix_v<H_t>) {
292 H = (J.transpose() * J).sparseView();
294 H = J.transpose() * J;
297 if (options.log.enable && options.log.print_J_jet)
298 TINYOPT_LOG(
"Jt:\n{}\n", J.transpose().eval());
300 return Cost(res_f.squaredNorm(), res_size);
305 return optimize(x, acc, options);
Definition parameter_traits.h:15