tinyopt 700
Tinyopt, a lightweight, header only optimization library
Loading...
Searching...
No Matches
optimizer.h
1// Copyright 2026 Julien Michot.
2// SPDX-License-Identifier: Apache-2.0
3
4#pragma once
5
6#include <cmath>
7#include <cstddef>
8#include <cstdint>
9#include <functional>
10#include <limits>
11#include <optional>
12#include <tuple>
13#include <type_traits>
14#include <variant>
15#include "tinyopt/math.h"
16#include "tinyopt/stop_reasons.h"
17
18#include <tinyopt/log.h>
19#include <tinyopt/summary.h>
20#include <tinyopt/time.h>
21#include <tinyopt/traits.h>
22
23#include <tinyopt/optimizers/options.h>
24
25#ifndef TINYOPT_DISABLE_AUTODIFF
26#include <tinyopt/diff/optimize_autodiff.h>
27#endif
28#ifndef TINYOPT_DISABLE_NUMDIFF
29#include <tinyopt/diff/num_diff.h>
30#endif
31
32namespace tinyopt {
33
34namespace detail {
35
36#if defined(TINYOPT_ENFORCE_NO_DYNAMIC_ALLOCATIONS)
37class EigenMallocGuard {
38 public:
39 explicit EigenMallocGuard(bool active)
40 : active_(active), previous_(Eigen::internal::is_malloc_allowed()) {
41 if (active_) Eigen::internal::set_is_malloc_allowed(false);
42 }
43
44 ~EigenMallocGuard() {
45 if (active_) Eigen::internal::set_is_malloc_allowed(previous_);
46 }
47
48 private:
49 bool active_;
50 bool previous_;
51};
52#endif
53
54template <typename Func, typename... Params>
56 const Func &func;
57
58 template <typename Pack>
59 decltype(auto) operator()(const Pack &pack) const {
60 return std::apply(func, pack.values);
61 }
62};
63
64template <typename Func, typename... Params>
66 const Func &func;
67
68 template <typename Pack, typename Gradient, typename Hessian>
69 requires std::is_invocable_v<const Func &, const Params &..., Gradient &, Hessian &>
70 decltype(auto) operator()(const Pack &pack, Gradient &gradient, Hessian &hessian) const {
71 return std::apply(
72 [&](const auto &...params) { return std::invoke(func, params..., gradient, hessian); },
73 pack.values);
74 }
75
76 template <typename Pack, typename Gradient>
77 requires std::is_invocable_v<const Func &, const Params &..., Gradient &>
78 decltype(auto) operator()(const Pack &pack, Gradient &gradient) const {
79 return std::apply([&](const auto &...params) { return std::invoke(func, params..., gradient); },
80 pack.values);
81 }
82};
83
84template <typename Func, typename... Params>
85auto make_packed_adapter(const Func &func) {
86 if constexpr (std::is_invocable_v<const Func &, const Params &...>)
88 else
89 return PackedAccumulationAdapter<Func, Params...>{func};
90}
91
92} // namespace detail
93
94/***
95 * @brief Optimizer
96 */
97template <typename SolverType>
98class Optimizer_ {
99 public:
100 using Scalar = typename SolverType::Scalar;
101 static constexpr Index Dims = SolverType::Dims;
102 using Options = tinyopt::Options;
103
104 public:
105 Optimizer_(const Options &_options = {})
106 : options_{PrepareOptions(_options)}, solver_(options_) {}
107
109 template <int FO = SolverType::FirstOrder, std::enable_if_t<!FO, int> = 0>
110 void InitWith(const auto &g, const auto &h) {
111 solver_.InitWith(g, h);
112 }
113
115 template <int FO = SolverType::FirstOrder, std::enable_if_t<FO, int> = 0>
116 void InitWith(const auto &g) {
117 solver_.InitWith(g);
118 }
119
121 void reset() { solver_.reset(); }
122
123 template <typename X_t>
124 std::variant<StopReason, bool> ResizeIfNeeded(X_t &x) {
125 const Index dims = traits::DynDims(x); // Dynamic size
126 if (Dims == Dynamic && dims == 0) {
127 TINYOPT_LOG(
128 "❌ Error: Parameters dimensions cannot be 0 or Dynamic at "
129 "execution time");
130 return StopReason::kSkipped;
131 } else if (dims < 0) {
132 TINYOPT_LOG("❌ Error: Parameters dimensions is negative: {} ", dims);
133 return StopReason::kSkipped;
134 }
135
136 // Resize the solver if needed TODO move?
137 bool resized = false;
138 try {
139 resized = solver_.resize(dims);
140 } catch (const std::bad_alloc &) {
141 if (options_.log.enable) {
142 int num_hessians = 1;
143 if (options_.hessian.save_last) num_hessians++;
144 TINYOPT_LOG(
145 "❌ Failed to allocate {} Hessian(s) of size {}x{}, "
146 "mem:{}GB, maybe use a SparseMatrix?",
147 num_hessians, dims, dims, 1e-9f * static_cast<float>(dims * dims * sizeof(Scalar)));
148 }
149 return StopReason::kOutOfMemory;
150 } catch (const std::invalid_argument &e) {
151 TINYOPT_LOG("❌ Error: Failed to resize the linear solver. {}", e.what());
152 return StopReason::kSkipped;
153 }
154 return resized;
155 }
156
208 template <typename T, typename U, typename... Rest, typename Func>
209 requires(!std::is_same_v<std::remove_cvref_t<Func>, Options>)
210 Summary Optimize(T &x, U &y, Rest &...rest, const Func &cost_or_acc) {
211 tinyopt::ParamsPack pack(x, y, rest...);
212 auto wrapped = detail::make_packed_adapter<Func, T, U, Rest...>(cost_or_acc);
213 return OptimizeSingle(pack, wrapped);
214 }
215
216 private:
217 template <typename X_t, typename CostOrAccFunc>
218 Summary OptimizeSingle(X_t &x, const CostOrAccFunc &cost_or_acc, int max_iters = -1) {
219#if defined(TINYOPT_ENFORCE_NO_DYNAMIC_ALLOCATIONS)
220 detail::EigenMallocGuard eigen_malloc_guard(Dims != Dynamic);
221#endif
222 // Detect if we need to do differentiation
223 if constexpr (std::is_invocable_v<CostOrAccFunc, const X_t &>) {
224 // Try to run AD
225#ifndef TINYOPT_DISABLE_AUTODIFF
226 using Jet = diff::Jet<Scalar, Dims>;
227 using XJetType =
228 std::conditional_t<std::is_floating_point_v<X_t>, Jet,
229 decltype(traits::params_trait<X_t>::template cast<Jet>(x))>;
230 if constexpr (std::is_invocable_v<CostOrAccFunc, const XJetType &>) {
231 using ResType = std::invoke_result_t<CostOrAccFunc, const XJetType &>;
232 if constexpr (traits::is_tuple_v<ResType>) {
233 const auto optimize = [&](auto &x, const auto &func, const auto &) {
234 return OptimizeAcc(x, func, max_iters);
235 };
236 constexpr bool kIsNLLS = SolverType::IsNLLS;
237 return tinyopt::OptimizeWithAutoDiff<kIsNLLS>(x, cost_or_acc, optimize, options_);
238 } else if constexpr (traits::is_jet_type_v<ResType>) {
239 const auto optimize = [&](auto &x, const auto &func, const auto &) {
240 return OptimizeAcc(x, func, max_iters);
241 };
242 constexpr bool kIsNLLS = SolverType::IsNLLS;
243 return tinyopt::OptimizeWithAutoDiff<kIsNLLS>(x, cost_or_acc, optimize, options_);
244 } else if constexpr (traits::is_matrix_or_array_v<ResType>) {
245 static_assert(traits::is_jet_type_v<typename ResType::Scalar>,
246 "Matrix residuals in autodiff must be Jet-valued");
247 const auto optimize = [&](auto &x, const auto &func, const auto &) {
248 return OptimizeAcc(x, func, max_iters);
249 };
250 constexpr bool kIsNLLS = SolverType::IsNLLS;
251 return tinyopt::OptimizeWithAutoDiff<kIsNLLS>(x, cost_or_acc, optimize, options_);
252 } else {
253 Summary sum;
254 sum.num_diff_used = true;
255 if constexpr (SolverType::FirstOrder) {
256 auto loss = diff::CreateNumDiffFunc1(x, cost_or_acc);
257 sum = OptimizeAcc(x, loss, max_iters);
258 } else {
259 auto loss = diff::CreateNumDiffFunc2(x, cost_or_acc);
260 sum = OptimizeAcc(x, loss, max_iters);
261 }
262 return sum;
263 }
264 } else {
265 Summary sum;
266 sum.num_diff_used = true;
267 if constexpr (SolverType::FirstOrder) {
268 auto loss = diff::CreateNumDiffFunc1(x, cost_or_acc);
269 sum = OptimizeAcc(x, loss, max_iters);
270 } else {
271 auto loss = diff::CreateNumDiffFunc2(x, cost_or_acc);
272 sum = OptimizeAcc(x, loss, max_iters);
273 }
274 return sum;
275 }
276#else
277#ifndef TINYOPT_DISABLE_NUMDIFF
278 Summary sum;
279 sum.num_diff_used = true;
280 if constexpr (SolverType::FirstOrder) {
281 auto loss = diff::CreateNumDiffFunc1(x, cost_or_acc);
282 sum = OptimizeAcc(x, loss, max_iters);
283 } else {
284 auto loss = diff::CreateNumDiffFunc2(x, cost_or_acc);
285 sum = OptimizeAcc(x, loss, max_iters);
286 }
287 return sum;
288#else
289 throw std::invalid_argument(
290 "Automatic and numerical differentiation are disabled for cost-only functions");
291#endif
292#endif // TINYOPT_DISABLE_AUTODIFF
293 } else {
294 return OptimizeAcc(x, cost_or_acc, max_iters);
295 }
296 }
297
298 public:
299 template <typename X_t, typename CostOrAccFunc>
300 Summary Optimize(X_t &x, const CostOrAccFunc &cost_or_acc, int max_iters = -1) {
301 return OptimizeSingle(x, cost_or_acc, max_iters);
302 }
303
309 template <typename T, typename U, typename... Rest, typename Func>
310 requires(!std::is_same_v<std::remove_cvref_t<Func>, tinyopt::Options>)
311 Summary operator()(T &x, U &y, Rest &...rest, const Func &cost_or_acc, int max_iters = -1) {
312 tinyopt::ParamsPack pack(x, y, rest...);
313 auto wrapped = detail::make_packed_adapter<Func, T, U, Rest...>(cost_or_acc);
314 return OptimizeSingle(pack, wrapped, max_iters);
315 }
316
317 template <typename X_t, typename CostOrAccFunc>
318 Summary operator()(X_t &x, const CostOrAccFunc &cost_or_acc, int max_iters = -1) {
319 return OptimizeSingle(x, cost_or_acc, max_iters);
320 }
321
361 template <typename X_t, typename AccFunc>
362 Summary OptimizeAcc(X_t &x, const AccFunc &acc, int max_iters = -1) {
363#if defined(TINYOPT_ENFORCE_NO_DYNAMIC_ALLOCATIONS)
364 detail::EigenMallocGuard eigen_malloc_guard(Dims != Dynamic);
365#endif
366 using ptrait = traits::params_trait<X_t>;
367 Summary sum;
368 // Set start time
369 sum.start_time = tic();
370 if (max_iters < 0) max_iters = options_.stop.max_iters;
371 max_iters++; // +1 to potentially roll-back
372 if (options_.opt.check_final_cost) max_iters++;
373
374#if !defined(TINYOPT_ENFORCE_NO_DYNAMIC_ALLOCATIONS)
375 sum.hist.errs.reserve(max_iters + 1);
376 sum.hist.deltas2.reserve(max_iters + 1);
377 sum.hist.successes.reserve(max_iters + 1);
378#endif
379
380 // Keep track of the last good 'x'
381 constexpr bool kNoCopyX = true; // TODO offer static alternative to the user
382 using BestXType = std::conditional<kNoCopyX, std::nullptr_t, X_t>;
383 BestXType *best_x = nullptr;
384 if constexpr (!kNoCopyX) best_x = new X_t(x); // using the copy constructor
385
386 std::optional<Vector<Scalar, Dims>> last_dx;
387 bool last_was_success = true; // Last iteration was a success
388
389 // Reports a parameter update to the user, who may ask to stop
390 const auto notify_step = [&](const Vector<Scalar, Dims> &step, bool is_rollback) {
391 if (options_.stop.step_callback &&
392 options_.stop.step_callback(step.template cast<float>(), is_rollback) &&
393 sum.stop_reason == StopReason::kNone)
394 sum.stop_reason = StopReason::kUserStopped;
395 };
396
397 // Run several optimization iterations
398 for (int iter = 0; iter < max_iters; ++iter) {
399 const auto t = tic();
400 const auto &[success, maybe_dx] = Step(x, acc, sum);
401 bool eval_only = false;
402
403 if (success) { // Great, let's keep the good work
404
405 ptrait::PlusEq(x, maybe_dx.value()); // Move X by dX
406 notify_step(maybe_dx.value(), false);
407 last_dx = maybe_dx.value();
408 last_was_success = true;
409
410 // On the very last iteration, we check that the final error is actually
411 // lower
412 if (options_.opt.check_final_cost && iter + 1 == max_iters) eval_only = true;
413
414 } else { // Failure to decrease error
415
416 if (last_dx) { // Roll-back
417 if constexpr (kNoCopyX)
418 ptrait::PlusEq(x, -last_dx.value()); // Move X by -dX
419 else
420 x = *best_x;
421 notify_step(-last_dx.value(), true);
422 last_dx.reset();
423 } else if (maybe_dx) { // We failed several times in a row so just
424 // evaluate the new x+dx
425 ptrait::PlusEq(x, maybe_dx.value()); // Move X by dX
426 notify_step(maybe_dx.value(), false);
427 last_dx = maybe_dx.value();
428 }
429
430 eval_only = last_was_success == false; // No need to build the linear system
431 last_was_success = false;
432 }
433
434 solver_.Rebuild(!eval_only);
435
436 // Check for a time out
437 sum.duration_ms += static_cast<float>(toc_ms(t));
438 if (options_.stop.max_duration_ms > 0 && sum.duration_ms > options_.stop.max_duration_ms) {
439 sum.stop_reason = StopReason::kTimedOut;
440 }
441 // Iteration done
442 sum.num_iters++;
443 // Stop now?
444 if (sum.stop_reason != StopReason::kNone) break;
445 }
446
447 // Copy the very last hessian
448 if constexpr (SolverType::FirstOrder == 0) {
449#if defined(TINYOPT_ENFORCE_NO_DYNAMIC_ALLOCATIONS)
450 if constexpr (Dims == Dynamic) {
451 if (options_.hessian.save_last)
452 sum.final_hessian = solver_.Hessian().template cast<double>().eval();
453 }
454#else
455 if (options_.hessian.save_last)
456 sum.final_hessian = solver_.Hessian().template cast<double>().eval();
457#endif
458 }
459
460 if constexpr (!kNoCopyX) delete best_x;
461
462 if (sum.stop_reason == StopReason::kNone && sum.num_iters >= max_iters)
463 sum.stop_reason = StopReason::kMaxIters;
464 // Print stop reason
465 if (options_.log.enable && sum.stop_reason != StopReason::kNone)
466 TINYOPT_LOG("{}, cost: [{}]", StopReasonDescription(sum, options_),
467 sum.final_cost.toString(options_.log.e, options_.log.print_inliers));
468 return sum;
469 }
470
473 template <typename X_t, typename AccFunc>
474 std::pair<bool, std::optional<Vector<Scalar, Dims>>> Step(X_t &x, const AccFunc &acc,
475 Summary &sum) {
476 const auto iter = sum.num_iters;
477 std::pair<bool, std::optional<Vector<Scalar, Dims>>> status{false, std::nullopt};
478
479 // Set start time if not set already
480 const auto t = tic();
481 if (sum.start_time == TimePoint::min()) sum.start_time = t;
482
483 // Resize the solver if needed
484 const auto resize_status = ResizeIfNeeded(x);
485 if (auto fail_reason = std::get_if<StopReason>(&resize_status)) {
486 sum.stop_reason = *fail_reason;
487 return status;
488 }
489
490 const bool resize_and_clear_solver = true; // for now
491
492 // Create the gradient and displacement `dx`
493 Vector<Scalar, Dims> dx;
494 Cost cost(NAN, sum.num_residuals);
495
496 bool solver_failed = true;
497 // Solver linear a few times until it's enough
498 const uint8_t max_tries = options_.stop.max_consec_failures > 0
499 ? std::max<uint8_t>(1, options_.stop.max_consec_failures)
500 : 255;
501 for (; sum.num_consec_failures <= max_tries;) {
502 // Accumulate residuals and jacobians
503 if (solver_.Build(x, acc, resize_and_clear_solver)) {
504 // Ok, let's try to solve for `dx` now
505 if (const auto &maybe_dx = solver_.Solve()) {
506 dx = maybe_dx.value(); // TODO void copy?
507 solver_failed = false;
508 }
509 }
510 // Saves errors
511 cost = solver_.cost();
512 // Check success/failure
513 if (solver_failed) { // Failure
515 sum.num_failures++;
516 // Check there's some residuals
517 if (cost.num_resisuals == 0) {
518 if (options_.log.enable) TINYOPT_LOG("❌ #{}: No residuals, stopping", iter);
519 sum.stop_reason = StopReason::kSkipped;
520 return status;
521 } else if (std::isnan(cost.cost) || std::isinf(cost.cost)) { // Check for NaNs and Inf
522 if (options_.log.enable) TINYOPT_LOG("❌ #{}: NaN/Inf in error", iter);
523 sum.stop_reason = StopReason::kSystemHasNaNOrInf;
524 return status;
525 } else if (options_.stop.max_consec_failures > 0 &&
527 if (sum.final_cost < std::numeric_limits<Scalar>::max())
528 sum.stop_reason = StopReason::kMaxConsecNoDecr;
529 break;
530 } else if (options_.log.enable)
531 TINYOPT_LOG("❌ #{}:Failed to solve the linear system", iter);
532 solver_.FailedStep(); // Tell the solver it's a failure... and try again
533 } else {
534 break; // success -> we got a step!
535 }
536 }
537
538 // Stop here if the solver failed constantly
539 if (solver_failed) {
540 sum.stop_reason = StopReason::kSolverFailed;
541 return status;
542 }
543
544 const auto &err = cost.cost;
545 const auto &nerr = cost.num_resisuals;
546
547 // Check for NaNs and Inf
548 if (std::isnan(err) || std::isinf(err)) {
549 if (options_.log.enable) TINYOPT_LOG("❌ #{}: NaN/Inf in error: ε:{}", iter, err);
550 sum.stop_reason = StopReason::kSystemHasNaNOrInf;
551 return status;
552 }
553
554 // Check the displacement magnitude
555 const double dx_norm2 = solver_failed ? 0 : dx.squaredNorm();
556 const bool has_grad_norm2 = options_.stop.min_grad_norm2 > 0.0f ||
557 options_.stop.stop_callback || options_.stop.stop_callback2;
558 const double grad_norm2 = has_grad_norm2 ? solver_.GradientSquaredNorm() : 0.0;
559 if (std::isnan(dx_norm2) || std::isinf(dx_norm2)) {
560 if (options_.log.enable && options_.log.print_failure) {
561 TINYOPT_LOG("❌ Failure, dX = \n{}", dx.template cast<float>());
562 TINYOPT_LOG("Solver: {}", solver_.stateAsString());
563 TINYOPT_LOG("grad = \n{}", solver_.Gradient());
564 if constexpr (!SolverType::FirstOrder) TINYOPT_LOG("H = \n{}", solver_.H());
565 }
566 sum.stop_reason = StopReason::kSystemHasNaNOrInf;
567 return status;
568 }
569
570 // Cost change (negative is good)
571 const double derr = err - sum.final_cost;
572 const bool is_good_step = derr < Scalar(0.0);
573 // Relative Cost change, defined as (εp-ε)/εp, εp is previous cost,
574 const double rel_derr = sum.final_cost > FloatEpsilon<Scalar>() &&
575 sum.final_cost < std::numeric_limits<Scalar>::max()
576 ? (sum.final_cost - err) / sum.final_cost
577 : 0.0f;
578 // Save history of errors and deltas
579 sum.hist.Add(err, dx_norm2, is_good_step);
580
581 // Update output struct
582 if (is_good_step || iter == 0) { /* GOOD Step */
583 // Note: we guess it's a good step in the first iteration
584 if (iter > 0) solver_.GoodStep(options_.opt.use_step_quality_approx ? rel_derr : 0.0f);
585 sum.num_consec_failures = 0;
586 sum.final_cost = cost;
587 sum.final_rerr_dec = rel_derr;
588 } else { /* BAD Step */
589 solver_.BadStep();
590 sum.num_failures++;
592 if (options_.stop.max_consec_failures > 0 &&
594 sum.stop_reason = StopReason::kMaxConsecNoDecr;
595 }
596 if (options_.stop.max_total_failures > 0 &&
598 sum.stop_reason = StopReason::kMaxNoDecr;
599 return status;
600 }
601 }
602
603 // Log
604 if (options_.log.enable) {
605 std::ostringstream oss;
606 if (options_.log.print_emoji) oss << (is_good_step ? (iter == 0 ? "ℹ️" : "✅") : "❌");
607 oss << "#" << iter << " ";
608 if (options_.log.print_x) {
609 if constexpr (traits::is_scalar_v<X_t>) {
610 oss << TINYOPT_FORMAT_NS::format("x:{:.5f} ", x);
611 } else if constexpr (traits::is_matrix_or_array_v<X_t>) { // Flattened X
612 oss << "x:["
613#ifdef TINYOPT_NO_FORMATTERS
614 << x.reshaped().transpose()
615#else
616 << TINYOPT_FORMAT_NS::format("{}", x.reshaped().transpose())
617#endif // TINYOPT_NO_FORMATTERS
618 << "] ";
619 } else if constexpr (traits::is_streamable_v<X_t>) {
620 // User must define the stream operator of ParameterType
621 oss << "{" << x << "} ";
622 }
623 } else if constexpr (Dims == Dynamic) {
624 const auto dims = traits::DynDims(x);
625 oss << "x:ℝ^" << dims << " ";
626 if (solver_.dims() != dims) oss << "∇:ℝ^" << dims << " ";
627 }
628
629 // Print error/cost
630 oss << TINYOPT_FORMAT_NS::format("{}:{:.4e} n:{} d{}:{:+.2e} r{}:{:+.1e} ", options_.log.e,
631 err, nerr, options_.log.e, iter == 0 ? 0.0f : derr,
632 options_.log.e, rel_derr);
633
634 // Print step info
635 oss << TINYOPT_FORMAT_NS::format("|δx|:{:.2e} ", sqrt(dx_norm2));
636 if (options_.log.print_dx) oss << TINYOPT_FORMAT_NS::format("δx:[{}] ", dx);
637 // Estimate max standard deviations from (co)variances
638 if constexpr (!SolverType::FirstOrder) {
639 if (is_good_step && options_.log.print_max_stdev)
640 oss << TINYOPT_FORMAT_NS::format("⎡σ⎤:{:.2f} ", solver_.MaxStdDev());
641 }
642 // Print gradient
643 if (has_grad_norm2) oss << TINYOPT_FORMAT_NS::format("|∇|:{:.2e} ", sqrt(grad_norm2));
644 // Print Solver state
645 oss << solver_.stateAsString();
646 // Print inliers
647 if (options_.log.print_inliers) {
648 oss << TINYOPT_FORMAT_NS::format("in:{:.2f}% ({}) ", cost.inlier_ratio * 100.0,
649 cost.NumInliers());
650 }
651 // Print extra log
652 if (!cost.log_str.empty()) oss << cost.log_str << " ";
653 // Print timing
654 if (options_.log.print_t) oss << TINYOPT_FORMAT_NS::format("τ:{:.2f} ", sum.duration_ms);
655 // Print now!
656 TINYOPT_LOG("{}", oss.str());
657 }
658
659 // Detect if we need to stop
660 if (sum.stop_reason == StopReason::kNone) {
661 if (solver_failed)
662 sum.stop_reason = StopReason::kSolverFailed;
663 else if (options_.stop.min_error > 0 && err < options_.stop.min_error)
664 sum.stop_reason = StopReason::kMinError;
665 else if (options_.stop.min_rerr_dec > 0 && rel_derr > 0.0 &&
666 rel_derr < options_.stop.min_rerr_dec)
667 sum.stop_reason = StopReason::kMinRelError;
668 else if (options_.stop.min_step_norm2 > 0 && dx_norm2 < options_.stop.min_step_norm2)
669 sum.stop_reason = StopReason::kMinDeltaNorm;
670 else if (options_.stop.min_grad_norm2 > 0 && grad_norm2 < options_.stop.min_grad_norm2)
671 sum.stop_reason = StopReason::kMinGradNorm;
672 else if (options_.stop.stop_callback &&
673 options_.stop.stop_callback(err, dx_norm2, grad_norm2))
674 sum.stop_reason = StopReason::kUserStopped;
675 else if (options_.stop.stop_callback2 &&
676 options_.stop.stop_callback2(float(err), dx.template cast<float>(),
677 solver_.Gradient().template cast<float>()))
678 sum.stop_reason = StopReason::kUserStopped;
679 }
680
681 status.first = is_good_step;
682 status.second = dx;
683 return status;
684 }
685
686 SolverType &solver() { return solver_; }
687 const SolverType &solver() const { return solver_; }
688
689 protected:
690 static Options PrepareOptions(const Options &options) {
691 Options prepared = options;
692#if defined(TINYOPT_ENFORCE_NO_DYNAMIC_ALLOCATIONS)
693 if constexpr (Dims != Dynamic) {
694 prepared.log.enable = false;
695 prepared.hessian.save_last = false;
696 }
697#endif
698 return prepared;
699 }
700
702 const Options options_;
704 SolverType solver_;
705};
706
707} // namespace tinyopt
void InitWith(const auto &g)
Initialize solver with specific gradient.
Definition optimizer.h:116
SolverType solver_
Linear solver.
Definition optimizer.h:704
Summary operator()(T &x, U &y, Rest &...rest, const Func &cost_or_acc, int max_iters=-1)
Performs optimization on the given parameters x using the provided cost or accumulation function cost...
Definition optimizer.h:311
std::pair< bool, std::optional< Vector< Scalar, Dims > > > Step(X_t &x, const AccFunc &acc, Summary &sum)
Definition optimizer.h:474
Summary OptimizeAcc(X_t &x, const AccFunc &acc, int max_iters=-1)
Performs optimization on the given parameters x using the provided accumulation function acc.
Definition optimizer.h:362
const Options options_
Optimization options.
Definition optimizer.h:702
Summary Optimize(T &x, U &y, Rest &...rest, const Func &cost_or_acc)
Performs optimization on the given parameters x using the provided cost or accumulation function cost...
Definition optimizer.h:210
void reset()
Reset the optimization and solver.
Definition optimizer.h:121
void InitWith(const auto &g, const auto &h)
Initialize solver with specific gradient and hessian.
Definition optimizer.h:110
Definition cost.h:18
float inlier_ratio
The ratio of inlier residuals (when robust norms are used).
Definition cost.h:95
int num_resisuals
The number of residuals (for e.g. NLLS).
Definition cost.h:94
std::string log_str
Extra information that will be printed as part of the opt. iterations.
Definition cost.h:96
Scalar cost
The function cost.
Definition cost.h:93
bool save_last
Saves the last Hessian H as part of the output results.
Definition options.h:70
bool print_dx
Log the value of step 'dx'.
Definition options.h:120
std::string e
Symbol used when logging the error, e.g ε, ε² or √ε etc.
Definition options.h:117
bool enable
Whether to enable the logging.
Definition options.h:116
bool print_t
Log the duration (in ms).
Definition options.h:122
bool print_emoji
Whether to show the emoji or not.
Definition options.h:118
bool print_max_stdev
Definition options.h:124
bool print_x
Log the value of 'x'.
Definition options.h:119
bool print_inliers
Log the inliers ratio (in %).
Definition options.h:121
bool use_step_quality_approx
Use relative error decrease as step quality; otherwise use 0.0.
Definition options.h:52
bool check_final_cost
Recompute the final cost as a rollback safety check.
Definition options.h:49
float min_grad_norm2
Minimum squared gradient norm.
Definition options.h:97
uint16_t max_iters
Maximum number of outer iterations.
Definition options.h:93
std::function< bool(const VecXf &dx, bool is_rollback)> step_callback
Definition options.h:107
uint8_t max_consec_failures
Maximum consecutive failures to decrease error.
Definition options.h:99
double max_duration_ms
Maximum optimization duration in milliseconds.
Definition options.h:100
float min_error
Minimum error/cost.
Definition options.h:94
float min_rerr_dec
Minimum relative error decrease.
Definition options.h:95
uint8_t max_total_failures
Overall max failures to decrease error.
Definition options.h:98
float min_step_norm2
Minimum squared step norm.
Definition options.h:96
Definition options.h:18
Definition params_pack.h:12
std::vector< Scalar > deltas2
Squared step sizes of all iterations.
Definition summary.h:156
std::vector< Scalar > errs
Mean squared accumulated errors of all iterations.
Definition summary.h:155
std::vector< bool > successes
Step acceptance status.
Definition summary.h:157
Definition summary.h:27
StopReason stop_reason
Stop reason.
Definition summary.h:107
Cost final_cost
Last valid error.
Definition summary.h:103
uint8_t num_failures
Final number of failures to decrease the error.
Definition summary.h:115
uint8_t num_consec_failures
Final number of the last consecutive failures to decrease the error.
Definition summary.h:116
std::variant< std::monostate, MatX, SparseMat > final_hessian
Final H, excluding any damping (only saved if options.hessian.save = true).
Definition summary.h:125
uint16_t num_residuals
Final number of residuals.
Definition summary.h:113
TimePoint start_time
Definition summary.h:119
float duration_ms
Cumulated optimization duration.
Definition summary.h:122
uint16_t num_iters
Final number of iterations.
Definition summary.h:114
Definition parameter_traits.h:15