OpenKalman
FiniteDifferenceLinearization.hpp
Go to the documentation of this file.
1 /* This file is part of OpenKalman, a header-only C++ library for
2  * Kalman filters and other recursive filters.
3  *
4  * Copyright (c) 2020-2021 Christopher Lee Ogden <ogden@gatech.edu>
5  *
6  * This Source Code Form is subject to the terms of the Mozilla Public
7  * License, v. 2.0. If a copy of the MPL was not distributed with this
8  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
9  */
10 
16 #ifndef OPENKALMAN_FINITEDIFFERENCELINEARIZATION_HPP
17 #define OPENKALMAN_FINITEDIFFERENCELINEARIZATION_HPP
18 
19 #include <tuple>
20 
21 namespace OpenKalman
22 {
23  namespace oin = OpenKalman::internal;
24 
25 
32 #ifdef __cpp_concepts
33  template<typename Function, transformation_input InDelta, transformation_input ... PsDelta> requires
34  std::invocable<Function, InDelta, PsDelta...> and
35  (not wrapped_mean<std::invoke_result_t<Function, InDelta, PsDelta...>>) and
36  (not std::is_reference_v<InDelta>) and (((not std::is_reference_v<PsDelta>) and ...)) and
37  ((sizeof...(PsDelta) == 0) or (coordinates::compares_with<vector_space_descriptor_of_t<PsDelta, 0>,
38  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...))
39 #else
40  template<typename Function, typename InDelta, typename ... PsDelta>
41 #endif
43 
44 
45  namespace internal
46  {
47 #ifdef __cpp_concepts
48  template<typename Function, transformation_input InDelta, transformation_input ... PsDelta, std::size_t order>
49  requires (order <= 2)
50  struct is_linearized_function<FiniteDifferenceLinearization<Function, InDelta, PsDelta...>, order>
51  : std::true_type {};
52 #else
53  template<typename Function, typename InDelta, typename ... PsDelta, std::size_t order>
54  struct is_linearized_function<FiniteDifferenceLinearization<Function, InDelta, PsDelta...>, order, std::enable_if_t<
55  order <= 2>> : std::true_type {};
56 #endif
57  }
58 
59 
60 #ifdef __cpp_concepts
61  template<typename Function, transformation_input InDelta, transformation_input ... PsDelta> requires
62  std::invocable<Function, InDelta, PsDelta...> and
63  (not wrapped_mean<std::invoke_result_t<Function, InDelta, PsDelta...>>) and
64  (not std::is_reference_v<InDelta>) and (((not std::is_reference_v<PsDelta>) and ...)) and
65  ((sizeof...(PsDelta) == 0) or (coordinates::compares_with<vector_space_descriptor_of_t<PsDelta, 0>,
66  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...))
67 #else
68  template<typename Function, typename InDelta, typename ... PsDelta>
69 #endif
71  {
72 
73  private:
74 
75 #ifndef __cpp_concepts
76  static_assert((transformation_input<InDelta> and ... and transformation_input<PsDelta>));
77  static_assert(std::is_invocable_v<Function, InDelta, PsDelta...>);
78  static_assert(not wrapped_mean<std::invoke_result_t<Function, InDelta, PsDelta...>>,
79  "For finite difference linearization, the tests function cannot return a wrapped matrix.");
80  static_assert(not std::is_reference_v<InDelta>);
81  static_assert(((not std::is_reference_v<PsDelta>) and ...));
82  static_assert((sizeof...(PsDelta) == 0) or (coordinates::compares_with<vector_space_descriptor_of_t<PsDelta, 0>,
83  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...));
84 #endif
85 
86  // Construct one Jacobian term.
87  template<std::size_t term, typename...Inputs>
88  auto jac_term(const std::tuple<Inputs...>& inputs) const
89  {
90  using Term = decltype(std::get<term>(inputs));
91  using Scalar = scalar_type_of_t<Term>;
92  constexpr auto width = index_dimension_of_v<Term, 0>;
93  auto& col = std::get<term>(inputs);
94  return apply_columnwise<width>([&](std::size_t i) {
95  const Scalar h = std::get<term>(deltas)[i];
96  const Scalar x = col[i];
97  col[i] = x + h;
98  const auto fp = Mean {std::apply(transformation, inputs)};
99  col[i] = x - h;
100  const auto fm = Mean {std::apply(transformation, inputs)};
101  col[i] = x;
102  return make_self_contained((fp - fm)/(2*h));
103  });
104  }
105 
106 
107  // Collect Jacobian terms into a tuple.
108 #ifdef __cpp_concepts
109  template<typename...Inputs, std::size_t...terms> requires (sizeof...(Inputs) == sizeof...(terms))
110 #else
111  template<typename...Inputs, std::size_t...terms, std::enable_if_t<sizeof...(Inputs) == sizeof...(terms), int> = 0>
112 #endif
113  auto jacobian_impl(const std::tuple<Inputs...>& inputs, std::index_sequence<terms...>) const
114  {
115  return std::tuple {jac_term<terms>(inputs) ...};
116  }
117 
118 
119  template<std::size_t term, std::size_t i, std::size_t j, typename...Inputs>
120  auto h_j(const std::tuple<Inputs...>& inputs) const
121  {
122  using Scalar = scalar_type_of_t<decltype(std::get<term>(inputs))>;
123  const Scalar hi = std::get<term>(deltas)[i];
124  auto& col = std::get<term>(inputs);
125  const Scalar xi = col[i];
126 
127  if constexpr (i == j)
128  {
129  const auto f0 = Mean {std::apply(transformation, inputs)};
130  col[i] = xi + hi;
131  const auto fp = Mean {std::apply(transformation, inputs)};
132  col[i] = xi - hi;
133  const auto fm = Mean {std::apply(transformation, inputs)};
134  col[i] = xi;
135 
136  // Use two separate subtractions to ensure proper wrapping:
137  auto ret {make_self_contained(((fp - f0) - (f0 - fm)) / (hi * hi))};
138  return ret;
139  }
140  else
141  {
142  const Scalar hj = std::get<term>(deltas)[j];
143  const Scalar xj = col[j];
144  col[i] = xi + hi;
145  col[j] = xj + hj;
146  const auto fpp = Mean {std::apply(transformation, inputs)};
147  col[j] = xj - hj;
148  const auto fpm = Mean {std::apply(transformation, inputs)};
149  col[i] = xi - hi;
150  const auto fmm = Mean {std::apply(transformation, inputs)};
151  col[j] = xj + hj;
152  const auto fmp = Mean {std::apply(transformation, inputs)};
153  col[i] = xi;
154  col[j] = xj;
155  auto ret {make_self_contained(((fpp - fpm) - (fmp - fmm)) / (4 * hi * hj))};
156  return ret;
157  }
158  };
159 
160 
161  template<std::size_t term, std::size_t i, typename...Inputs, std::size_t...js>
162  auto h_i(const std::tuple<Inputs...>& inputs, std::index_sequence<js...>) const
163  {
164  using A = equivalent_self_contained_t<decltype(h_j<term, 0, 0>(inputs))>;
165  return std::array<A, sizeof...(js)> {h_j<term, i, js>(inputs)...};
166  }
167 
168 
169  template<std::size_t term, typename...Inputs, std::size_t...is>
170  auto h_k(const std::tuple<Inputs...>& inputs, std::index_sequence<is...>) const
171  {
172  constexpr auto j_size = index_dimension_of_v<decltype(std::get<term>(inputs)), 0>;
173  using A = decltype(h_i<term, 0>(std::move(inputs), std::make_index_sequence<j_size> {}));
174  return std::array<A, sizeof...(is)> {h_i<term, is>(inputs, std::make_index_sequence<j_size> {})...};
175  }
176 
177 
178  // For each hessian term, construct an array of Hessian matrices, one for each output dimension ks.
179  template<std::size_t term, typename...Inputs, std::size_t...ks>
180  auto h_term(const std::tuple<Inputs...>& inputs, std::index_sequence<ks...>) const
181  {
182  using Term = decltype(std::get<term>(inputs));
183  const auto t = h_k<term>(inputs, std::make_index_sequence<index_dimension_of_v<Term, 0>> {});
186  std::tuple<index_dimension_of<Term, 0>, index_dimension_of<Term, 0>>>>;
187  return std::array<V, sizeof...(ks)> {
188  apply_coefficientwise<V>([&t](std::size_t i, std::size_t j) { return t[i][j][ks]; })...};
189  }
190 
191 
192  // Construct a tuple of Hessian terms, one array for each input/perturbation term.
193  template<typename...Inputs, std::size_t...terms>
194  auto hessian_impl(const std::tuple<Inputs...>& inputs, std::index_sequence<terms...>) const
195  {
196  static_assert(sizeof...(Inputs) == sizeof...(terms));
197  constexpr auto k_size = index_dimension_of_v<std::invoke_result_t<Function, Inputs...>, 0>;
198  return std::tuple {h_term<terms>(inputs, std::make_index_sequence<k_size> {})...};
199  }
200 
201  public:
202 
206 #ifdef __cpp_concepts
207  template<typename T, transformation_input In, transformation_input ... Ps>
208 #else
209  template<typename T, typename In, typename ... Ps, std::enable_if_t<
210  (transformation_input<In> and ... and transformation_input<Ps>), int> = 0>
211 #endif
212  FiniteDifferenceLinearization(T&& trans, In&& in_delta, Ps&& ... ps_delta)
213  : transformation(std::forward<T>(trans)), deltas(std::forward<In>(in_delta), std::forward<Ps>(ps_delta)...) {}
214 
215 
217 #ifdef __cpp_concepts
218  template<transformation_input<vector_space_descriptor_of_t<InDelta, 0>> In, perturbation ... Perturbations>
219  requires (sizeof...(Perturbations) <= sizeof...(PsDelta)) and (sizeof...(Perturbations) == 0 or
220  (coordinates::compares_with<typename oin::PerturbationTraits<Perturbations>::RowCoefficients,
221  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...))
222 #else
223  template<typename In, typename ... Perturbations, std::enable_if_t<
224  transformation_input<In, vector_space_descriptor_of_t<InDelta, 0>> and
225  (perturbation<Perturbations> and ...) and (sizeof...(Perturbations) <= sizeof...(PsDelta)) and
226  (sizeof...(Perturbations) == 0 or
227  (coordinates::compares_with<typename oin::PerturbationTraits<Perturbations>::RowCoefficients,
228  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...)), int> = 0>
229 #endif
230  auto operator()(In&& in, Perturbations&& ... ps) const
231  {
232  return transformation(std::forward<In>(in), std::forward<Perturbations>(ps)...);
233  }
234 
235 
237 #ifdef __cpp_concepts
238  template<transformation_input<vector_space_descriptor_of_t<InDelta, 0>> In, perturbation ... Perturbations>
239  requires (sizeof...(Perturbations) <= sizeof...(PsDelta)) and (sizeof...(Perturbations) == 0 or
240  (coordinates::compares_with<typename oin::PerturbationTraits<Perturbations>::RowCoefficients,
241  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...))
242 #else
243  template<typename In, typename ... Perturbations, std::enable_if_t<
244  transformation_input<In, vector_space_descriptor_of_t<InDelta, 0>> and
245  (perturbation<Perturbations> and ...) and (sizeof...(Perturbations) <= sizeof...(PsDelta)) and
246  (sizeof...(Perturbations) == 0 or
247  (coordinates::compares_with<typename oin::PerturbationTraits<Perturbations>::RowCoefficients,
248  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...)), int> = 0>
249 #endif
250  auto jacobian(In&& in, Perturbations&&...ps) const
251  {
252  return jacobian_impl(
253  std::forward_as_tuple(to_dense_object(std::forward<In>(in)),
254  to_dense_object(std::forward<Perturbations>(ps))...),
255  std::make_index_sequence<1 + sizeof...(ps)> {});
256  }
257 
258 
260 #ifdef __cpp_concepts
261  template<transformation_input<vector_space_descriptor_of_t<InDelta, 0>> In, perturbation ... Perturbations>
262  requires (sizeof...(Perturbations) <= sizeof...(PsDelta)) and (sizeof...(Perturbations) == 0 or
263  (coordinates::compares_with<typename oin::PerturbationTraits<Perturbations>::RowCoefficients,
264  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...))
265 #else
266  template<typename In, typename ... Perturbations, std::enable_if_t<
267  transformation_input<In, vector_space_descriptor_of_t<InDelta, 0>> and
268  (perturbation<Perturbations> and ...) and (sizeof...(Perturbations) <= sizeof...(PsDelta)) and
269  (sizeof...(Perturbations) == 0 or
270  (coordinates::compares_with<typename oin::PerturbationTraits<Perturbations>::RowCoefficients,
271  vector_space_descriptor_of_t<std::tuple_element_t<0, std::tuple<PsDelta...>>, 0>> and ...)), int> = 0>
272 #endif
273  auto hessian(In&& in, Perturbations&&...ps) const
274  {
275  return hessian_impl(
276  std::forward_as_tuple(to_dense_object(std::forward<In>(in)),
277  to_dense_object(std::forward<Perturbations>(ps))...),
278  std::make_index_sequence<1 + sizeof...(ps)> {});
279  }
280 
281  private:
282 
283  Function transformation;
284 
285  const std::tuple<const InDelta, const PsDelta...> deltas;
286 
287  };
288 
289 
290  /*
291  * Deduction guide
292  */
293 
294 #ifdef __cpp_concepts
295  template<typename Function, transformation_input InDelta, transformation_input ... PsDelta> requires
296  std::invocable<Function, InDelta, PsDelta...> and
297  (not wrapped_mean<std::invoke_result_t<Function, InDelta, PsDelta...>>) and
298  (not std::is_reference_v<InDelta>) and (((not std::is_reference_v<PsDelta>) and ...))
299 #else
300  template<typename Function, typename InDelta, typename ... PsDelta, std::enable_if_t<
301  (transformation_input<InDelta> and ... and transformation_input<PsDelta>) and
302  std::is_invocable_v<Function, InDelta, PsDelta...> and
303  (not wrapped_mean<std::invoke_result_t<Function, InDelta, PsDelta...>>) and
304  (not std::is_reference_v<InDelta>) and (((not std::is_reference_v<PsDelta>) and ...)), int> = 0>
305 #endif
306  FiniteDifferenceLinearization(Function&&, InDelta&&, PsDelta&&...)
307  -> FiniteDifferenceLinearization<std::decay_t<Function>, std::decay_t<InDelta>, std::decay_t<PsDelta>...>;
308 
309 }
310 
311 
312 #endif //OPENKALMAN_FINITEDIFFERENCELINEARIZATION_HPP
A set of one or more column vectors, each representing a statistical mean.
Definition: forward-class-declarations.hpp:477
typename scalar_type_of< T >::type scalar_type_of_t
helper template for scalar_type_of.
Definition: scalar_type_of.hpp:54
constexpr bool wrapped_mean
Specifies that T is a wrapped mean (i.e., its row fixed_pattern have at least one type that requires ...
Definition: object-types.hpp:52
Definition: tuple_reverse.hpp:103
Definition: TransformationTraits.hpp:136
FiniteDifferenceLinearization(T &&trans, In &&in_delta, Ps &&... ps_delta)
Constructor.
Definition: FiniteDifferenceLinearization.hpp:212
constexpr bool transformation_input
T is an acceptable input to a tests.
Definition: TransformationTraits.hpp:158
decltype(auto) constexpr to_dense_object(Arg &&arg)
Convert the argument to a dense, writable matrix of a particular scalar type.
Definition: to_dense_object.hpp:37
auto jacobian(In &&in, Perturbations &&...ps) const
Returns a tuple of the Jacobians for the input and each perturbation term.
Definition: FiniteDifferenceLinearization.hpp:250
The root namespace for OpenKalman.
Definition: basics.hpp:34
typename vector_space_descriptor_of< T, N >::type vector_space_descriptor_of_t
helper template for vector_space_descriptor_of.
Definition: vector_space_descriptor_of.hpp:56
auto hessian(In &&in, Perturbations &&...ps) const
Returns a tuple of Hessian matrices for the input and each perturbation term.
Definition: FiniteDifferenceLinearization.hpp:273
auto operator()(In &&in, Perturbations &&... ps) const
Applies the tests.
Definition: FiniteDifferenceLinearization.hpp:230
The dimension of an index for a matrix, expression, or array.
Definition: index_dimension_of.hpp:34
A tests which calculates the first and second Taylor derivatives using finite differences.
Definition: FiniteDifferenceLinearization.hpp:42
Definition: basics.hpp:48
A matrix with typed rows and columns.
Definition: forward-class-declarations.hpp:448