6#include <tinyopt/diff/num_diff.h>
7#include <tinyopt/math.h>
9#include <tinyopt/log.h>
10#include <tinyopt/traits.h>
13#include "tinyopt/losses/norms.h"
15namespace tinyopt::diff {
50template <
typename X_t,
typename AccFunc>
51inline bool CheckGradient(
52 X_t &x,
const AccFunc &acc,
53 double eps = std::is_same_v<
typename traits::params_trait<X_t>::Scalar,
float> ? 1e-2 : 1e-5,
54 const diff::Method method = diff::Method::kCentral,
bool verbose =
true) {
55 using Scalar = traits::params_trait<X_t>::Scalar;
56 constexpr int Dims = traits::params_trait<X_t>::Dims;
57 const int dims = traits::DynDims(x);
58 using Gradient_t = Vector<Scalar, Dims>;
59 constexpr bool HasH = !std::is_invocable_v<AccFunc, const X_t &, Gradient_t &>;
60 constexpr bool DenseH =
61 std::is_invocable_v<AccFunc, const X_t &, Gradient_t &, Matrix<Scalar, Dims, Dims> &>;
62 using MaybeHessian_t = std::conditional_t<
63 HasH, std::conditional_t<DenseH, Matrix<Scalar, Dims, Dims>, SparseMatrix<Scalar>>,
68 Gradient_t grad(dims);
71 auto fg = [&](
const X_t &x, Gradient_t &g) {
73 !std::is_invocable_v<AccFunc, const X_t &, Gradient_t &, MaybeHessian_t &>)
74 return acc(x, g, nul);
75 else if constexpr (HasH) {
86 auto f = [&](
const X_t &x) {
91 return acc(x, nul, H);
96 const auto grad_num = EstimateNumJac(x, f, method, eps / 10.0f).transpose().eval();
97 const double max_dist = (grad_num - grad).cwiseAbs().maxCoeff();
98 const bool is_within_tol = max_dist < eps;
99 if (!is_within_tol && verbose)
100 TINYOPT_LOG(
"Wrong gradient {:.3e}>eps. \nGradient:{}, Numerial Gradient:{}", max_dist, grad,
102 return is_within_tol;
143template <
typename X_t,
typename AccWithRes
idualsFunc>
144inline bool CheckResidualsGradient(
145 X_t &x,
const AccWithResidualsFunc &acc_res_func,
146 double eps = std::is_same_v<
typename traits::params_trait<X_t>::Scalar,
float> ? 1e-2 : 1e-5,
147 const diff::Method method = diff::Method::kCentral,
bool verbose =
true,
bool check_H =
true,
148 bool downscale =
true) {
149 using Scalar = traits::params_trait<X_t>::Scalar;
150 constexpr int Dims = traits::params_trait<X_t>::Dims;
151 const int dims = traits::DynDims(x);
152 using Gradient_t = Vector<Scalar, Dims>;
153 constexpr bool HasH = !std::is_invocable_v<AccWithResidualsFunc, const X_t &, std::nullptr_t &>;
154 constexpr bool DenseH = std::is_invocable_v<AccWithResidualsFunc,
const X_t &, Gradient_t &,
155 Matrix<Scalar, Dims, Dims> &>;
156 using MaybeHessian_t = std::conditional_t<
157 HasH, std::conditional_t<DenseH, Matrix<Scalar, Dims, Dims>, SparseMatrix<Scalar>>,
159 const double s = downscale ? 0.5 : 1.0;
162 auto loss = [&](
const X_t &x,
auto &g, MaybeHessian_t &H) {
163 if constexpr (HasH) {
164 auto res = acc_res_func(x, g, H);
166 using ResidualsType = std::decay_t<
decltype(res)>;
167 static_assert(!std::is_same_v<ResidualsType, Cost>,
168 "The function must return residuals, not a cost");
171 auto res = acc_res_func(x, g);
173 using ResidualsType = std::decay_t<
decltype(res)>;
174 static_assert(!std::is_same_v<ResidualsType, Cost>,
175 "The function must return residuals, not a cost");
180 bool success = CheckGradient(x, loss, eps, method, verbose);
183 if constexpr (HasH) {
184 if (!check_H)
return success;
191 H.resize(dims, dims);
193 acc_res_func(x, g, H);
196 auto f = [&](
const X_t &x) {
198 return acc_res_func(x, nul, H);
201 const auto J_num = EstimateNumJac(x, f, method, eps / 10.0f);
202 const auto H_num = (J_num.transpose() * J_num).eval();
205 if constexpr (DenseH)
206 max_dist = (H_num - H).cwiseAbs().maxCoeff();
208 MaybeHessian_t H_num_sparse = H_num.sparseView();
209 H_num_sparse.makeCompressed();
210 max_dist = MaxAbsDiff(H_num_sparse, H);
213 const bool is_within_tol = max_dist < eps;
214 if (!is_within_tol && verbose)
215 TINYOPT_LOG(
"Wrong gradient {:.3e}>eps. \nH:\n{}, Numerial JtJ:\n{}", max_dist, H, H_num);
216 return success && is_within_tol;
auto SquaredL2(const T &x)
Return the squared L2 norm of a vector or scalar (a.k.a Sum of Squares).
Definition norms.h:19