OGS
NumericalDifferentiation.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#pragma once
5
6#include <Eigen/Core>
7#include <tuple>
8#include <utility>
9
10#include "BaseLib/StrongType.h"
11
12namespace NumLib
13{
17
18namespace detail
19{
20template <typename T>
21struct IsScalar : std::true_type
22{
23};
24
25template <int N>
26struct IsScalar<Eigen::Matrix<double, N, 1, Eigen::ColMajor, N, 1>>
27 : std::false_type
28{
29};
30
31template <std::size_t IndexInTuple, typename Tuple>
32double getScalarOrVectorComponent(Tuple const& tuple, Eigen::Index component)
33{
34 auto const& value = std::get<IndexInTuple>(tuple);
35
36 if constexpr (IsScalar<std::remove_cvref_t<decltype(value)>>::value)
37 {
38 return value;
39 }
40 else
41 {
42 return value[component];
43 }
44}
45
51{
52 template <typename Function, typename TupleOfArgs,
53 typename PerturbationStrategy, std::size_t PerturbedArgIdx,
54 std::size_t... AllArgIdcs>
56 Function const& f, TupleOfArgs const& args,
57 PerturbationStrategy const& pert_strat,
58 std::integral_constant<std::size_t, PerturbedArgIdx> /*unused*/,
59 Eigen::Index const perturbed_arg_component,
60 std::index_sequence<AllArgIdcs...> /*unused*/) const
61 {
62 auto const value_plus = f(pert_strat.perturbIf(
63 std::bool_constant<PerturbedArgIdx == AllArgIdcs>{},
64 std::get<AllArgIdcs>(args), 1.0, perturbed_arg_component)...);
65
66 auto const value_minus = f(pert_strat.perturbIf(
67 std::bool_constant<PerturbedArgIdx == AllArgIdcs>{},
68 std::get<AllArgIdcs>(args), -1.0, perturbed_arg_component)...);
69
70 auto const pert = pert_strat.getPerturbation(
72 args, perturbed_arg_component));
73
74 // decltype enforces evaluation of Eigen expressions
75 decltype(value_plus) deriv = (value_plus - value_minus) / (2 * pert);
76
77 return deriv;
78 }
79};
80
85template <typename Value>
87{
88 explicit ComputeDerivativeWrtOneScalar_FD(Value&& unperturbed_value)
89 : unperturbed_value_{std::move(unperturbed_value)}
90 {
91 }
92
93 template <typename Function, typename TupleOfArgs,
94 typename PerturbationStrategy, std::size_t PerturbedArgIdx,
95 std::size_t... AllArgIdcs>
97 Function const& f, TupleOfArgs const& args,
98 PerturbationStrategy const& pert_strat,
99 std::integral_constant<std::size_t, PerturbedArgIdx> /*unused*/,
100 Eigen::Index const perturbed_arg_component,
101 std::index_sequence<AllArgIdcs...> /*unused*/) const
102 {
103 auto const value_plus = f(pert_strat.perturbIf(
104 std::bool_constant<PerturbedArgIdx == AllArgIdcs>{},
105 std::get<AllArgIdcs>(args), 1.0, perturbed_arg_component)...);
106
107 auto const pert = pert_strat.getPerturbation(
109 args, perturbed_arg_component));
110
111 return (value_plus - unperturbed_value_) / pert;
112 }
113
114private:
116};
117
121{
123 MinimumPerturbation const& min_pert)
124 : rel_eps_{*rel_eps}, min_pert_{*min_pert}
125 {
126 }
127
128 double getPerturbation(double const value) const
129 {
130 auto const pert = std::abs(value) * rel_eps_;
131
132 if (std::abs(pert) >= std::abs(min_pert_))
133 {
134 return pert;
135 }
136
137 return min_pert_;
138 }
139
140 template <typename T>
141 static T const& perturbIf(std::false_type /*unused*/, T const& value,
142 double const /*plus_or_minus*/,
143 Eigen::Index /*comp*/)
144 {
145 return value;
146 }
147
148 double perturbIf(std::true_type /*unused*/, double value,
149 double const plus_or_minus, Eigen::Index /*comp*/) const
150 {
151 return value + plus_or_minus * getPerturbation(value);
152 }
153
154 template <int N>
155 Eigen::Vector<double, N> perturbIf(
156 std::true_type /*unused*/,
157 Eigen::Matrix<double, N, 1, Eigen::ColMajor, N, 1> const& vec,
158 double const plus_or_minus,
159 Eigen::Index comp) const
160 {
161 Eigen::Vector<double, N> vec_pert = vec;
162 vec_pert[comp] += plus_or_minus * getPerturbation(vec[comp]);
163 return vec_pert;
164 }
165
166private:
167 double rel_eps_;
168 double min_pert_;
169};
170} // namespace detail
171
176{
177 template <typename Function, typename... Args>
179 Function const& /*f*/, Args const&... /*args*/)
180 {
181 return {};
182 }
183};
184
189{
190 template <typename Function, typename... Args>
191 static auto createDByDScalar(Function const& f, Args const&... args)
192 {
194 }
195};
196
197// TODO better call it NumericalDifferentiationAlgorithm?
202template <typename DerivativeStrategy>
204{
206 MinimumPerturbation const& min_pert)
207 : pert_strat_{rel_eps, min_pert}
208 {
209 }
210
211 template <typename Function, typename... Args>
212 auto operator()(Function const& f, Args const&... args) const
213 {
214 auto const d_by_dScalar =
215 DerivativeStrategy::createDByDScalar(f, args...);
216
217 // TODO also return value from the function, not only the derivatives?
218 return differentiate(f,
219 std::forward_as_tuple(args...),
220 d_by_dScalar,
221 std::make_index_sequence<sizeof...(Args)>{});
222 }
223
224private:
225 template <typename Function, typename TupleOfArgs, typename DByDScalar,
226 std::size_t... AllArgIdcs>
227 auto differentiate(Function const& f, TupleOfArgs const& args,
228 DByDScalar const& d_by_dScalar,
229 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
230 {
232 detail::IsScalar<std::remove_cvref_t<
233 std::tuple_element_t<AllArgIdcs, TupleOfArgs>>>{},
234 f, args, d_by_dScalar,
235 std::integral_constant<std::size_t, AllArgIdcs>{},
236 all_arg_idcs)... /* "for each function argument" */};
237 }
238
239 // scalar case
240 template <typename Function, typename TupleOfArgs, typename DByDScalar,
241 std::size_t... AllArgIdcs, std::size_t PerturbedArgIdx>
243 std::true_type /* is_scalar */, Function const& f,
244 TupleOfArgs const& args, DByDScalar const& d_by_dScalar,
245 std::integral_constant<std::size_t, PerturbedArgIdx> perturbed_arg_idx,
246 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
247 {
248 constexpr Eigen::Index component_does_not_matter = -1;
249
250 return d_by_dScalar(f, args, pert_strat_, perturbed_arg_idx,
251 component_does_not_matter, all_arg_idcs);
252 }
253
254 // vectorial case
255 template <typename Function, typename TupleOfArgs, typename DByDScalar,
256 std::size_t... AllArgIdcs, std::size_t PerturbedArgIdx>
258 std::false_type /* is_scalar */, Function const& f,
259 TupleOfArgs const& args, DByDScalar const& d_by_dScalar,
260 std::integral_constant<std::size_t, PerturbedArgIdx> perturbed_arg_idx,
261 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
262 {
263 using VectorialArg = std::remove_cvref_t<
264 std::tuple_element_t<PerturbedArgIdx, TupleOfArgs>>;
265 constexpr int N = VectorialArg::RowsAtCompileTime;
266
267 static_assert(N != Eigen::Dynamic);
268 static_assert(VectorialArg::ColsAtCompileTime == 1,
269 "Row vectors are not supported, yet. If you implement "
270 "support for them, make sure to test your implementation "
271 "thoroughly.");
272
274 f, args, d_by_dScalar,
275 std::make_integer_sequence<Eigen::Index, N>{}, perturbed_arg_idx,
276 all_arg_idcs);
277 }
278
279 template <typename Function, typename TupleOfArgs, typename DByDScalar,
280 Eigen::Index... PerturbedArgComponents, std::size_t... AllArgIdcs,
281 std::size_t PerturbedArgIdx>
283 Function const& f, TupleOfArgs const& args,
284 DByDScalar const& d_by_dScalar,
285 std::integer_sequence<Eigen::Index,
286 PerturbedArgComponents...> /*unused*/,
287 std::integral_constant<std::size_t, PerturbedArgIdx> perturbed_arg_idx,
288 std::index_sequence<AllArgIdcs...> all_arg_idcs) const
289 {
290 return std::array{
291 d_by_dScalar(f, args, pert_strat_, perturbed_arg_idx,
292 PerturbedArgComponents, all_arg_idcs)...
293 /* "for each component of the vectorial function argument being
294 perturbed" */
295 };
296 }
297
299};
300
301} // namespace NumLib
double getScalarOrVectorComponent(Tuple const &tuple, Eigen::Index component)
BaseLib::StrongType< double, struct RelativeEpsilonTag > RelativeEpsilon
BaseLib::StrongType< double, struct MinimumPerturbationTag > MinimumPerturbation
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