OpenKalman
adapters-interface.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) 2019-2025 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_ADAPTERS_INTERFACE_HPP
17 #define OPENKALMAN_ADAPTERS_INTERFACE_HPP
18 
19 #include <complex>
20 
21 
22 namespace OpenKalman::interface
23 {
24 
25 #ifdef __cpp_concepts
26  template<typename T> requires OpenKalman::internal::diagonal_expr<T> or OpenKalman::internal::triangular_expr<T> or OpenKalman::internal::hermitian_expr<T>
27  struct library_interface<T>
28 #else
29  template<typename T>
30  struct library_interface<T, std::enable_if_t<OpenKalman::internal::diagonal_expr<T> or OpenKalman::internal::triangular_expr<T> or OpenKalman::internal::hermitian_expr<T>>>
31 #endif
32  : library_interface<std::decay_t<nested_object_of_t<T>>>
33  {
34  private:
35 
36  using Nested = std::decay_t<nested_object_of_t<T>>;
38 
39  public:
40 
41  template<typename Derived>
42  using library_base = internal::library_base_t<Derived, Nested>;
43 
44 
45 #ifdef __cpp_lib_ranges
46  template<indexible Arg, std::ranges::input_range Indices> requires
47  std::convertible_to<std::ranges::range_value_t<Indices>, const typename std::decay_t<Arg>::Index>
48 #else
49  template<typename Arg, typename Indices>
50 #endif
51  static constexpr scalar_type_of_t<Arg>
52  get_component(Arg&& arg, const Indices& indices)
53  {
54  using Scalar = scalar_type_of_t<Arg>;
55  auto it = stdex::ranges::begin(indices);
56  std::size_t i {*it};
57  std::size_t j {++it == stdex::ranges::end(indices) ? 1 : *it};
58 
59  if constexpr (OpenKalman::internal::diagonal_expr<Arg>)
60  {
61  if (i == j)
62  return OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), indices);
63  else
64  return static_cast<Scalar>(0);
65  }
66  else if constexpr (OpenKalman::internal::triangular_expr<Arg>)
67  {
68  if (triangular_matrix<Arg, triangle_type::lower> ? i >= j : j >= i)
69  return OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), indices);
70  else
71  return static_cast<Scalar>(0);
72  }
73  else
74  {
75  static_assert(OpenKalman::internal::hermitian_expr<Arg>);
76 
77  if constexpr (values::complex<Scalar>)
78  {
79  if (i == j)
80  return values::real(OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), indices));
81  else if (hermitian_adapter<Arg, HermitianAdapterType::lower> ? i > j : j > i)
82  return OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), indices);
83  else
84  return values::conj(OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), j, i));
85  }
86  else
87  {
88  if (hermitian_adapter<Arg, HermitianAdapterType::lower> ? i >= j : j >= i)
89  return OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), indices);
90  else
91  return OpenKalman::get_component(OpenKalman::nested_object(std::forward<Arg>(arg)), j, i);
92  }
93  }
94  }
95 
96 
97 #ifdef __cpp_lib_ranges
98  template<indexible Arg, std::ranges::input_range Indices> requires (not std::is_const_v<Arg>) and
99  std::convertible_to<std::ranges::range_value_t<Indices>, const typename Arg::Index>
100 #else
101  template<typename Arg, typename Indices, std::enable_if_t<(not std::is_const_v<Arg>), int> = 0>
102 #endif
103  static void
104  set_component(Arg& arg, const scalar_type_of_t<Arg>& s, const Indices& indices)
105  {
106  using Scalar = scalar_type_of_t<Arg>;
107  auto it = stdex::ranges::begin(indices);
108  std::size_t i {*it};
109  std::size_t j {++it == stdex::ranges::end(indices) ? 1 : *it};
110 
111  if constexpr (OpenKalman::internal::diagonal_expr<Arg>)
112  {
113  if (i == j)
114  OpenKalman::set_component(arg, s, indices);
115  else
116  if (s != static_cast<Scalar>(0)) throw std::out_of_range("Cannot set non-diagonal element of a diagonal matrix to a non-zero value.");
117  }
118  else if constexpr (OpenKalman::internal::triangular_expr<Arg>)
119  {
120  if (triangular_matrix<Arg, triangle_type::lower> ? i >= j : j >= i)
121  OpenKalman::set_component(arg, s, indices);
122  else
123  if (s != static_cast<Scalar>(0)) throw std::out_of_range("Cannot set elements of a triangular matrix to non-zero values outside the triangle.");
124  }
125  else
126  {
127  static_assert(OpenKalman::internal::hermitian_expr<Arg>);
128 
129  if (hermitian_adapter<Arg, HermitianAdapterType::lower> ? i >= j : j >= i)
130  OpenKalman::set_component(arg, s, indices);
131  else
132  OpenKalman::set_component(arg, values::conj(s), j, i);
133  }
134  }
135 
136 
137  // to_native_matrix inherited
138 
139  // make_default inherited
140 
141  // fill_components inherited
142 
143  // make_constant inherited
144 
145  // make_identity_matrix inherited
146 
147  // make_triangular_matrix inherited
148 
149  // make_hermitian_adapter inherited
150 
151 
152  template<typename Arg, typename...Begin, typename...Size>
153  static auto
154  get_slice(Arg&& arg, std::tuple<Begin...> begin, std::tuple<Size...> size)
155  {
156  auto dense = to_dense_object<scalar_type_of_t<Arg>>(std::forward<Arg>(arg));
157  static_assert(not OpenKalman::internal::diagonal_expr<decltype(dense)> and not OpenKalman::internal::triangular_expr<decltype(dense)> and
158  not OpenKalman::internal::hermitian_expr<decltype(dense)>);
159  return OpenKalman::get_slice(std::move(dense), begin, size);
160  }
161 
162 
163  template<typename Arg, typename Block, typename...Begin>
164  static constexpr Arg&
165  set_slice(Arg& arg, Block&& block, Begin...begin) = delete;
166 
167 
168  // set_triangle not defined because it is handled by global set_triangle function
169 
170 
171  template<typename Arg>
172  static decltype(auto)
173  to_diagonal(Arg&& arg)
174  {
175  // Note: the interface only needs to handle constant and dynamic-sized zero matrices.
176  return diagonal_adapter {std::forward<Arg>(arg)};
177  }
178 
179 
180  template<typename Arg>
181  static decltype(auto)
182  diagonal_of(Arg&& arg)
183  {
184  // Note: the global diagonal_of function already handles all zero and constant cases.
185  if constexpr (OpenKalman::internal::diagonal_expr<Arg>) return nested_object(std::forward<Arg>(arg));
186  else return OpenKalman::diagonal_of(nested_object(std::forward<Arg>(arg)));
187  }
188 
189 
190  // broadcast
191 
192 
193  template<typename...Ds, typename Operation, typename...Args>
194  static constexpr decltype(auto)
195  n_ary_operation(const std::tuple<Ds...>& tup, Operation&& op, Args&&...args)
196  {
198  return Traits::n_ary_operation(tup, std::forward<Operation>(op), std::forward<Args>(args)...);
199  }
200 
201 
202  template<std::size_t...indices, typename BinaryFunction, typename Arg>
203  static constexpr decltype(auto)
204  reduce(BinaryFunction&& b, Arg&& arg)
205  {
207  return Traits::template reduce<indices...>(std::forward<BinaryFunction>(b), std::forward<Arg>(arg));
208  }
209 
210  // to_euclidean not defined
211 
212  // from_euclidean not defined
213 
214  // wrap_angles not defined
215 
216  template<typename Arg>
217  static constexpr decltype(auto)
218  conjugate(Arg&& arg)
219  {
220  if constexpr (OpenKalman::internal::hermitian_expr<Arg>)
221  {
222  constexpr auto t = hermitian_adapter_type_of_v<Arg>;
223  return make_hermitian_matrix<t>(OpenKalman::conjugate(nested_object(std::forward<Arg>(arg))));
224  }
225  else
226  {
227  static_assert(OpenKalman::internal::triangular_expr<Arg>);
228  constexpr auto t = triangle_type_of_v<Arg>;
229  return make_triangular_matrix<t>(OpenKalman::conjugate(nested_object(std::forward<Arg>(arg))));
230  }
231  // Global conjugate function already handles diagonal_adapter
232  }
233 
234 
235  template<typename Arg>
236  static constexpr decltype(auto)
237  transpose(Arg&& arg)
238  {
239  if constexpr (OpenKalman::internal::hermitian_expr<Arg>)
240  {
241  if constexpr (hermitian_matrix<nested_object_of_t<Arg>>)
242  {
243  return OpenKalman::transpose(nested_object(std::forward<Arg>(arg)));
244  }
245  else
246  {
247  constexpr auto t = (hermitian_adapter<Arg, HermitianAdapterType::lower> ? triangle_type::upper : triangle_type::lower);
248  return make_hermitian_matrix<t>(OpenKalman::transpose(nested_object(std::forward<Arg>(arg))));
249  }
250  }
251  else
252  {
253  static_assert(OpenKalman::internal::triangular_expr<Arg>);
254  if constexpr (triangular_matrix<nested_object_of_t<Arg>>)
255  {
256  return OpenKalman::transpose(nested_object(std::forward<Arg>(arg)));
257  }
258  else
259  {
260  constexpr auto t = triangular_matrix<Arg, triangle_type::lower> ? triangle_type::upper : triangle_type::lower;
261  return make_triangular_matrix<t>(OpenKalman::transpose(nested_object(std::forward<Arg>(arg))));
262  }
263  }
264  // Global transpose function already handles diagonal_adapter
265  }
266 
267 
268  template<typename Arg>
269  static constexpr decltype(auto)
270  conjugate_transpose(Arg&& arg)
271  {
272  // Global conjugate function already handles HermitianAdapter and diagonal_adapter
273  static_assert(OpenKalman::internal::triangular_expr<Arg>);
274 
275  constexpr auto t = triangular_matrix<Arg, triangle_type::lower> ? triangle_type::upper : triangle_type::lower;
276  return make_triangular_matrix<t>(OpenKalman::conjugate_transpose(nested_object(std::forward<Arg>(arg))));
277  }
278 
279 
280  template<typename Arg>
281  static constexpr auto
282  determinant(Arg&& arg)
283  {
284  // The general determinant function already handles TriangularAdapter and diagonal_adapter.
285  static_assert(OpenKalman::internal::hermitian_expr<Arg>);
286  return OpenKalman::determinant(to_dense_object(std::forward<Arg>(arg)));
287  }
288 
289 
290  template<typename Arg, typename...Args>
291  static constexpr auto
292  sum(Arg&& arg, Args&&...args)
293  {
294  return library_interface<Nested>::sum(std::forward<Arg>(arg), std::forward<Args>(args)...);
295  }
296 
297 
298  template<typename A, typename B>
299  static constexpr auto
300  contract(A&& a, B&& b)
301  {
302  return library_interface<std::decay_t<nested_object_of_t<T>>>::contract(std::forward<A>(a), std::forward<B>(b));
303  }
304 
305 
306  // contract_in_place
307 
308 
309  template<triangle_type tri, typename A>
310  static constexpr auto
311  cholesky_factor(A&& a)
312  {
313  static_assert(not OpenKalman::internal::diagonal_expr<A>); // diagonal_adapter case should be handled by cholesky_factor function
314 
315  if constexpr (OpenKalman::internal::hermitian_expr<A>)
316  {
318  if constexpr (hermitian_adapter<A, h>)
319  return cholesky_factor<tri>(nested_object(std::forward<A>(a)));
320  else
321  return cholesky_factor<tri>(conjugate_transpose(nested_object(std::forward<A>(a))));
322  }
323  else
324  {
325  static_assert(OpenKalman::internal::triangular_expr<A>);
326  if constexpr (triangular_matrix<A, tri>)
327  return OpenKalman::cholesky_factor<tri>(nested_object(std::forward<A>(a)));
328  else
329  return OpenKalman::cholesky_factor<tri>(to_diagonal(diagonal_of(std::forward<A>(a))));
330  }
331  }
332 
333 
334  template<HermitianAdapterType significant_triangle, typename A, typename U, typename Alpha>
335  static decltype(auto)
336  rank_update_hermitian(A&& a, U&& u, const Alpha alpha)
337  {
338  auto&& n = nested_object(std::forward<A>(a));
340  if constexpr (OpenKalman::internal::hermitian_expr<A>)
341  {
342  auto&& m = Trait::template rank_update_hermitian<significant_triangle>(std::forward<decltype(n)>(n), std::forward<U>(u), alpha);
343  return make_hermitian_matrix<significant_triangle>(std::forward<decltype(m)>(m));
344  }
345  else
346  {
347  static_assert(OpenKalman::internal::diagonal_expr<A>);
348  return Trait::template rank_update_hermitian<significant_triangle>(to_native_matrix(std::forward<A>(a)), std::forward<U>(u), alpha);
349  }
350  }
351 
352 
353  template<triangle_type triangle, typename A, typename U, typename Alpha>
354  static decltype(auto)
355  rank_update_triangular(A&& a, U&& u, const Alpha alpha)
356  {
357  static_assert(OpenKalman::internal::diagonal_expr<A>);
358  using N = std::decay_t<nested_object_of_t<T>>;
359  return library_interface<N>::template rank_update_triangular<triangle>(std::forward<A>(a), std::forward<U>(u), alpha);
360  }
361 
362 
363  template<bool must_be_unique, bool must_be_exact, typename A, typename B>
364  static constexpr decltype(auto)
365  solve(A&& a, B&& b)
366  {
367  using N = std::decay_t<nested_object_of_t<T>>;
368  return library_interface<N>::template solve<must_be_unique, must_be_exact>(to_native_matrix(std::forward<A>(a)), std::forward<B>(b));
369  }
370 
371 
372  template<typename A>
373  static constexpr decltype(auto)
374  LQ_decomposition(A&& a)
375  {
376  using N = std::decay_t<nested_object_of_t<T>>;
377  return library_interface<N>::LQ_decomposition(to_native_matrix(std::forward<A>(a)));
378  }
379 
380 
381  template<typename A>
382  static constexpr decltype(auto)
383  QR_decomposition(A&& a)
384  {
385  using N = std::decay_t<nested_object_of_t<T>>;
386  return library_interface<N>::QR_decomposition(to_native_matrix(std::forward<A>(a)));
387  }
388 
389  };
390 
391 
392 }
393 
394 
395 #endif
constexpr auto n_ary_operation(const std::tuple< Ds... > &d_tup, Operation &&operation, Args &&...args)
Perform a component-wise n-ary operation, using broadcasting to match the size of a pattern matrix...
Definition: n_ary_operation.hpp:325
decltype(auto) constexpr contract(A &&a, B &&b)
Matrix multiplication of A * B.
Definition: contract.hpp:54
Definition: basics.hpp:41
decltype(auto) rank_update_hermitian(A &&a, U &&u, scalar_type_of_t< A > alpha=1)
Do a rank update on a hermitian matrix.
Definition: rank_update_hermitian.hpp:45
A lower-left triangular matrix.
decltype(auto) constexpr conjugate(Arg &&arg)
Take the complex conjugate of an indexible object.
Definition: conjugate.hpp:44
decltype(auto) constexpr get_slice(Arg &&arg, const std::tuple< Offset... > &offsets, const std::tuple< Extent... > &extents)
Extract a slice from a matrix or tensor.
Definition: get_slice.hpp:103
decltype(auto) constexpr QR_decomposition(A &&a)
Perform a QR decomposition of matrix A=Q[U,0], U is a upper-triangular matrix, and Q is orthogonal...
Definition: QR_decomposition.hpp:33
decltype(auto) constexpr conjugate_transpose(Arg &&arg)
Take the conjugate-transpose of an indexible_object.
Definition: conjugate_transpose.hpp:35
HermitianAdapterType
The type of a hermitian adapter, indicating which triangle of the nested matrix is used...
Definition: enumerations.hpp:79
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
decltype(auto) constexpr reduce(BinaryFunction &&b, Arg &&arg)
Perform a partial reduction based on an associative binary function, across one or more indices...
Definition: reduce.hpp:143
constexpr bool triangular_matrix
Specifies that an argument is an indexible object having a given triangle_type (e.g., upper, lower, or diagonal).
Definition: triangular_matrix.hpp:36
constexpr bool hermitian_matrix
Specifies that a type is a hermitian matrix.
Definition: hermitian_matrix.hpp:59
An adapter for creating a diagonal matrix or tensor.
Definition: diagonal_adapter.hpp:27
An interface to various routines from the linear algebra library associated with indexible object T...
Definition: library_interface.hpp:42
decltype(auto) constexpr transpose(Arg &&arg)
Swap any two indices of an indexible_object.
Definition: transpose.hpp:49
decltype(auto) constexpr diagonal_of(Arg &&arg)
Extract a column vector (or column slice for rank>2 tensors) comprising the diagonal elements...
Definition: diagonal_of.hpp:44
constexpr auto solve(A &&a, B &&b)
Solve the equation AX = B for X, which may or may not be a unique solution.
Definition: solve.hpp:87
constexpr auto conj(const Arg &arg)
A constexpr function for the complex conjugate of a (complex) number.
Definition: conj.hpp:39
constexpr auto determinant(Arg &&arg)
Take the determinant of a matrix.
Definition: determinant.hpp:44
constexpr bool size
T is either an index representing a size, or unbounded_size_t, which indicates that the size is unbou...
Definition: size.hpp:71
constexpr auto real(const Arg &arg)
A constexpr function to obtain the real part of a (complex) number.
Definition: real.hpp:40
An upper-right triangular matrix.
decltype(auto) constexpr sum(Ts &&...ts)
Element-by-element sum of one or more objects.
Definition: sum.hpp:112
decltype(auto) constexpr LQ_decomposition(A &&a)
Perform an LQ decomposition of matrix A=[L,0]Q, L is a lower-triangular matrix, and Q is orthogonal...
Definition: LQ_decomposition.hpp:33
decltype(auto) rank_update_triangular(A &&a, U &&u, scalar_type_of_t< A > alpha=1)
Do a rank update on triangular matrix.
Definition: rank_update_triangular.hpp:48
decltype(auto) constexpr to_diagonal(Arg &&arg, P &&p)
Convert a column vector (or any other array with a 1D second index) into a diagonal_matrix.
Definition: to_diagonal.hpp:46
A matrix with typed rows and columns.
Definition: forward-class-declarations.hpp:292
decltype(auto) constexpr cholesky_factor(A &&a)
Take the Cholesky factor of a matrix.
Definition: cholesky_factor.hpp:38
constexpr Arg && set_slice(Arg &&arg, Block &&block, const Begin &...begin)
Assign an object to a particular slice of a matrix or tensor.
Definition: set_slice.hpp:56