OGS
NumericalDifferentiation.h
Go to the documentation of this file.
1
11#pragma once
12
13#include <Eigen/Core>
14#include <tuple>
15#include <utility>
16
17#include "BaseLib/StrongType.h"
18
19namespace NumLib
20{
24
25namespace detail
26{
27template <typename T>
28struct IsScalar : std::true_type
29{
30};
31
32template <int N>
33struct IsScalar<Eigen::Matrix<double, N, 1, Eigen::ColMajor, N, 1>>
34 : std::false_type
35{
36};
37
38template <std::size_t IndexInTuple, typename Tuple>
39double getScalarOrVectorComponent(Tuple const& tuple, Eigen::Index component)
40{
41 auto const& value = std::get<IndexInTuple>(tuple);
42
43 if constexpr (IsScalar<std::remove_cvref_t<decltype(value)>>::value)
44 {
45 return value;
46 }
47 else
48 {
49 return value[component];
50 }
51}
52
58{
59 template <typename Function, typename TupleOfArgs,
60 typename PerturbationStrategy, std::size_t PerturbedArgIdx,
61 std::size_t... AllArgIdcs>
62 auto operator()(Function const& f, TupleOfArgs const& args,
63 PerturbationStrategy const& pert_strat,
64 std::integral_constant<std::size_t, PerturbedArgIdx>,
65 Eigen::Index const perturbed_arg_component,
66 std::index_sequence<AllArgIdcs...>) const
67 {
68 auto const value_plus = f(pert_strat.perturbIf(
69 std::bool_constant<PerturbedArgIdx == AllArgIdcs>{},
70 std::get<AllArgIdcs>(args), 1.0, perturbed_arg_component)...);
71
72 auto const value_minus = f(pert_strat.perturbIf(
73 std::bool_constant<PerturbedArgIdx == AllArgIdcs>{},
74 std::get<AllArgIdcs>(args), -1.0, perturbed_arg_component)...);
75
76 auto const pert = pert_strat.getPerturbation(
78 args, perturbed_arg_component));
79
80 // decltype enforces evaluation of Eigen expressions
81 decltype(value_plus) deriv = (value_plus - value_minus) / (2 * pert);
82
83 return deriv;
84 }
85};
86
91template <typename Value>
93{
94 explicit ComputeDerivativeWrtOneScalar_FD(Value&& unperturbed_value)
95 : unperturbed_value_{std::move(unperturbed_value)}
96 {
97 }
98
99 template <typename Function, typename TupleOfArgs,
100 typename PerturbationStrategy, std::size_t PerturbedArgIdx,
101 std::size_t... AllArgIdcs>
102 Value operator()(Function const& f, TupleOfArgs const& args,
103 PerturbationStrategy const& pert_strat,
104 std::integral_constant<std::size_t, PerturbedArgIdx>,
105 Eigen::Index const perturbed_arg_component,
106 std::index_sequence<AllArgIdcs...>) const
107 {
108 auto const value_plus = f(pert_strat.perturbIf(
109 std::bool_constant<PerturbedArgIdx == AllArgIdcs>{},
110 std::get<AllArgIdcs>(args), 1.0, perturbed_arg_component)...);
111
112 auto const pert = pert_strat.getPerturbation(
114 args, perturbed_arg_component));
115
116 return (value_plus - unperturbed_value_) / pert;
117 }
118
119private:
121};
122
126{
128 MinimumPerturbation const& min_pert)
129 : rel_eps_{*rel_eps}, min_pert_{*min_pert}
130 {
131 }
132
133 double getPerturbation(double const value) const
134 {
135 auto const pert = std::abs(value) * rel_eps_;
136
137 if (std::abs(pert) >= std::abs(min_pert_))
138 {
139 return pert;
140 }
141
142 return min_pert_;
143 }
144
145 template <typename T>
146 static T const& perturbIf(std::false_type, T const& value,
147 double const /*plus_or_minus*/,
148 Eigen::Index /*comp*/)
149 {
150 return value;
151 }
152
153 double perturbIf(std::true_type, double value, double const plus_or_minus,
154 Eigen::Index /*comp*/) const
155 {
156 return value + plus_or_minus * getPerturbation(value);
157 }
158
159 template <int N>
160 Eigen::Vector<double, N> perturbIf(
161 std::true_type,
162 Eigen::Matrix<double, N, 1, Eigen::ColMajor, N, 1> const& vec,
163 double const plus_or_minus,
164 Eigen::Index comp) const
165 {
166 Eigen::Vector<double, N> vec_pert = vec;
167 vec_pert[comp] += plus_or_minus * getPerturbation(vec[comp]);
168 return vec_pert;
169 }
170
171private:
172 double rel_eps_;
173 double min_pert_;
174};
175} // namespace detail
176
181{
182 template <typename Function, typename... Args>
184 Function const& /*f*/, Args const&... /*args*/)
185 {
186 return {};
187 }
188};
189
194{
195 template <typename Function, typename... Args>
196 static auto createDByDScalar(Function const& f, Args const&... args)
197 {
199 }
200};
201
202// TODO better call it NumericalDifferentiationAlgorithm?
207template <typename DerivativeStrategy>
209{
211 MinimumPerturbation const& min_pert)
212 : pert_strat_{rel_eps, min_pert}
213 {
214 }
215
216 template <typename Function, typename... Args>
217 auto operator()(Function const& f, Args const&... args) const
218 {
219 auto const d_by_dScalar =
220 DerivativeStrategy::createDByDScalar(f, args...);
221
222 // TODO also return value from the function, not only the derivatives?
223 return differentiate(f,
224 std::forward_as_tuple(args...),
225 d_by_dScalar,
226 std::make_index_sequence<sizeof...(Args)>{});
227 }
228
229private:
230 template <typename Function, typename TupleOfArgs, typename DByDScalar,
231 std::size_t... AllArgIdcs>
232 auto differentiate(Function const& f, TupleOfArgs const& args,
233 DByDScalar const& d_by_dScalar,
234 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
235 {
237 detail::IsScalar<std::remove_cvref_t<
238 std::tuple_element_t<AllArgIdcs, TupleOfArgs>>>{},
239 f, args, d_by_dScalar,
240 std::integral_constant<std::size_t, AllArgIdcs>{},
241 all_arg_idcs)... /* "for each function argument" */};
242 }
243
244 // scalar case
245 template <typename Function, typename TupleOfArgs, typename DByDScalar,
246 std::size_t... AllArgIdcs, std::size_t PerturbedArgIdx>
248 std::true_type /* is_scalar */, Function const& f,
249 TupleOfArgs const& args, DByDScalar const& d_by_dScalar,
250 std::integral_constant<std::size_t, PerturbedArgIdx> perturbed_arg_idx,
251 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
252 {
253 constexpr Eigen::Index component_does_not_matter = -1;
254
255 return d_by_dScalar(f, args, pert_strat_, perturbed_arg_idx,
256 component_does_not_matter, all_arg_idcs);
257 }
258
259 // vectorial case
260 template <typename Function, typename TupleOfArgs, typename DByDScalar,
261 std::size_t... AllArgIdcs, std::size_t PerturbedArgIdx>
263 std::false_type /* is_scalar */, Function const& f,
264 TupleOfArgs const& args, DByDScalar const& d_by_dScalar,
265 std::integral_constant<std::size_t, PerturbedArgIdx> perturbed_arg_idx,
266 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
267 {
268 using VectorialArg = std::remove_cvref_t<
269 std::tuple_element_t<PerturbedArgIdx, TupleOfArgs>>;
270 constexpr int N = VectorialArg::RowsAtCompileTime;
271
272 static_assert(N != Eigen::Dynamic);
273 static_assert(VectorialArg::ColsAtCompileTime == 1,
274 "Row vectors are not supported, yet. If you implement "
275 "support for them, make sure to test your implementation "
276 "thoroughly.");
277
279 f, args, d_by_dScalar,
280 std::make_integer_sequence<Eigen::Index, N>{}, perturbed_arg_idx,
281 all_arg_idcs);
282 }
283
284 template <typename Function, typename TupleOfArgs, typename DByDScalar,
285 Eigen::Index... PerturbedArgComponents, std::size_t... AllArgIdcs,
286 std::size_t PerturbedArgIdx>
288 Function const& f, TupleOfArgs const& args,
289 DByDScalar const& d_by_dScalar,
290 std::integer_sequence<Eigen::Index, PerturbedArgComponents...>,
291 std::integral_constant<std::size_t, PerturbedArgIdx> perturbed_arg_idx,
292 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
293 {
294 return std::array{
295 d_by_dScalar(f, args, pert_strat_, perturbed_arg_idx,
296 PerturbedArgComponents, all_arg_idcs)...
297 /* "for each component of the vectorial function argument being
298 perturbed" */
299 };
300 }
301
303};
304
305} // namespace NumLib
double getScalarOrVectorComponent(Tuple const &tuple, Eigen::Index component)
static detail::ComputeDerivativeWrtOneScalar_CD createDByDScalar(Function const &, Args const &...)
static auto createDByDScalar(Function const &f, Args const &... args)
auto differentiateWrtScalarOrVectorialArgument(std::false_type, Function const &f, TupleOfArgs const &args, DByDScalar const &d_by_dScalar, std::integral_constant< std::size_t, PerturbedArgIdx > perturbed_arg_idx, std::index_sequence< AllArgIdcs... > all_arg_idcs) const
detail::DefaultPerturbationStrategy pert_strat_
auto operator()(Function const &f, Args const &... args) const
NumericalDerivative(RelativeEpsilon const &rel_eps, MinimumPerturbation const &min_pert)
auto differentiateWrtScalarOrVectorialArgument(std::true_type, Function const &f, TupleOfArgs const &args, DByDScalar const &d_by_dScalar, std::integral_constant< std::size_t, PerturbedArgIdx > perturbed_arg_idx, std::index_sequence< AllArgIdcs... > all_arg_idcs) const
auto differentiateWrtAllVectorComponents(Function const &f, TupleOfArgs const &args, DByDScalar const &d_by_dScalar, std::integer_sequence< Eigen::Index, PerturbedArgComponents... >, std::integral_constant< std::size_t, PerturbedArgIdx > perturbed_arg_idx, std::index_sequence< AllArgIdcs... > all_arg_idcs) const
auto differentiate(Function const &f, TupleOfArgs const &args, DByDScalar const &d_by_dScalar, std::index_sequence< AllArgIdcs... > all_arg_idcs) const
auto operator()(Function const &f, TupleOfArgs const &args, PerturbationStrategy const &pert_strat, std::integral_constant< std::size_t, PerturbedArgIdx >, Eigen::Index const perturbed_arg_component, std::index_sequence< AllArgIdcs... >) const
Value operator()(Function const &f, TupleOfArgs const &args, PerturbationStrategy const &pert_strat, std::integral_constant< std::size_t, PerturbedArgIdx >, Eigen::Index const perturbed_arg_component, std::index_sequence< AllArgIdcs... >) const
static T const & perturbIf(std::false_type, T const &value, double const, Eigen::Index)
double perturbIf(std::true_type, double value, double const plus_or_minus, Eigen::Index) const
DefaultPerturbationStrategy(RelativeEpsilon const &rel_eps, MinimumPerturbation const &min_pert)
Eigen::Vector< double, N > perturbIf(std::true_type, Eigen::Matrix< double, N, 1, Eigen::ColMajor, N, 1 > const &vec, double const plus_or_minus, Eigen::Index comp) const