OpenKalman
n_ary_operation.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) 2022 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_N_ARY_OPERATION_HPP
17 #define OPENKALMAN_N_ARY_OPERATION_HPP
18 
19 namespace OpenKalman
20 {
21  // ----------------- //
22  // n_ary_operation //
23  // ----------------- //
24 
25  namespace detail
26  {
27 
28  // Check that dimensions of arguments Args are compatible with \ref coordinates::pattern Ds.
29  template<std::size_t...ixs, typename DTup, typename...Args>
30  constexpr void check_n_ary_dims(std::index_sequence<ixs...>, const DTup& d_tup, const Args&...args)
31  {
32  return ([](const DTup& d_tup, const Args&...args){
33  constexpr auto ix = ixs;
34  return ([](const DTup& d_tup, const auto& arg){
35  using Arg = decltype(arg);
36  if constexpr (dynamic_dimension<Arg, ix> or dynamic_pattern<std::tuple_element_t<ix, DTup>>)
37  {
38  auto arg_d = get_vector_space_descriptor<ix>(arg);
39  auto tup_d = std::get<ix>(d_tup);
40  auto dim_arg_d = get_dimension(arg_d);
41  auto dim_tup_d = get_dimension(tup_d);
42  if (not (arg_d == tup_d) and not internal::is_uniform_component_of(arg_d, tup_d))
43  throw std::logic_error {"In an argument to n_ary_operation, the dimension of index " +
44  std::to_string(ix) + " is " + std::to_string(dim_arg_d) + ", but should be 1 " +
45  (dim_tup_d == 1 ? "" : "or " + std::to_string(dim_tup_d)) +
46  "(the dimension of Ds number " + std::to_string(ix)};
47  }
48  else
49  {
50  using D_Arg = vector_space_descriptor_of_t<Arg, ix>;
51  using D = std::tuple_element_t<ix, DTup>;
52  static_assert(compares_with<D_Arg, D> or equivalent_to_uniform_static_vector_space_descriptor_component_of<D_Arg, D>or
53  (ix >= index_count_v<Arg> and uniform_static_vector_space_descriptor<D>),
54  "In argument to n_ary_operation, the dimension of each index must be either 1 or that of Ds.");
55  }
56  }(d_tup, args),...);
57  }(d_tup, args...),...);
58  }
59 
60 
61  template<typename Op, typename...Args, std::size_t...I>
62  constexpr bool is_invocable_with_indices(std::index_sequence<I...>)
63  {
64  return std::is_invocable_v<Op, Args..., decltype(I)...>;
65  }
66 
67 
68  template<typename Op, std::size_t...I, typename...Args>
69  constexpr decltype(auto) n_ary_invoke_op(const Op& op, std::index_sequence<I...> seq, Args&&...args)
70  {
71  if constexpr (is_invocable_with_indices<const Op&, Args&&...>(seq))
72  return op(std::forward<Args>(args)..., static_cast<decltype(I)>(0)...);
73  else
74  return op(std::forward<Args>(args)...);
75  }
76 
77 
78 #ifdef __cpp_concepts
79  template<typename Operation, std::size_t indices, typename...Args>
80 #else
81  template<typename Operation, std::size_t indices, typename = void, typename...Args>
82 #endif
84 
85 
86 #ifdef __cpp_concepts
87  template<typename Op, std::size_t indices, typename...Args>
88  requires (is_invocable_with_indices<Op, Args...>(std::make_index_sequence<indices> {})) or
89  std::is_invocable_v<Op, Args...>
90  struct n_ary_operator_traits_impl<Op, indices, Args...>
91 #else
92  template<typename Op, std::size_t indices, typename...Args>
93  struct n_ary_operator_traits_impl<Op, indices, std::enable_if_t<
94  is_invocable_with_indices<Op, Args...>(std::make_index_sequence<indices> {}) or
95  std::is_invocable_v<Op, Args...>>, Args...>
96 #endif
97  {
98  using type = decltype(n_ary_invoke_op(std::declval<Op>(), std::make_index_sequence<indices> {}, std::declval<Args>()...));
99  };
100 
101 
102  template<typename Op, std::size_t indices, typename...Args>
104 #ifdef __cpp_concepts
105  : n_ary_operator_traits_impl<Op, indices, Args...> {};
106 #else
107  : n_ary_operator_traits_impl<Op, indices, void, Args...> {};
108 #endif
109 
110 
111 #ifndef __cpp_concepts
112  template<typename Op, std::size_t Indices, typename = void, typename...Args>
113  struct n_ary_operator_impl : std::false_type {};
114 
115  template<typename Op, std::size_t Indices, typename...Args>
116  struct n_ary_operator_impl<Op, Indices, std::enable_if_t<
117  std::is_invocable<Op, typename std::add_lvalue_reference<typename scalar_type_of<Args>::type>::type...>::value or
118  is_invocable_with_indices<Op, typename std::add_lvalue_reference<typename scalar_type_of<Args>::type>::type...>(
119  std::make_index_sequence<Indices> {})>, Args...>
120  : std::true_type {};
121 #endif
122 
123 
124  template<typename Op, std::size_t Indices, typename...Args>
125 #ifdef __cpp_concepts
126  concept n_ary_operator = std::is_invocable_v<Op, std::add_lvalue_reference_t<scalar_type_of_t<Args>>...> or
127  is_invocable_with_indices<Op, std::add_lvalue_reference_t<scalar_type_of_t<Args>>...>(std::make_index_sequence<Indices> {});
128 #else
129  constexpr bool n_ary_operator = n_ary_operator_impl<Op, Indices, void, Args...>::value;
130 #endif
131 
132 
133  template<typename Arg, std::size_t...I, typename...J>
134  inline auto n_ary_operation_get_component_impl(Arg&& arg, std::index_sequence<I...>, J...j)
135  {
136  if constexpr (sizeof...(I) == sizeof...(J))
137  return get_component(std::forward<Arg>(arg), (j < get_index_dimension_of<I>(arg) ? j : 0)...);
138  else
139  return get_component(std::forward<Arg>(arg), [](auto dim, const auto& j_tup){
140  auto j = std::get<I>(j_tup);
141  if (j < dim) return j;
142  else return 0_uz;
143  }(get_index_dimension_of<I>(arg), std::tuple {j...})...);
144  }
145 
146 
147  template<typename Op, typename ArgsTup, std::size_t...ArgI, typename...J>
148  inline auto n_ary_operation_get_component(const Op& op, ArgsTup&& args_tup, std::index_sequence<ArgI...>, J...j)
149  {
150  if constexpr (std::is_invocable_v<const Op&, scalar_type_of_t<std::tuple_element_t<ArgI, std::decay_t<ArgsTup>>>..., J...>)
151  return op(n_ary_operation_get_component_impl(
152  std::get<ArgI>(std::forward<ArgsTup>(args_tup)),
153  std::make_index_sequence<index_count_v<std::tuple_element_t<ArgI, std::decay_t<ArgsTup>>>> {},
154  j...)..., j...);
155  else
156  return op(n_ary_operation_get_component_impl(
157  std::get<ArgI>(std::forward<ArgsTup>(args_tup)),
158  std::make_index_sequence<index_count_v<std::tuple_element_t<ArgI, std::decay_t<ArgsTup>>>> {},
159  j...)...);
160  }
161 
162 
163  template<typename M, typename Op, typename ArgsTup, typename...J>
164  inline void n_ary_operation_iterate(M& m, const Op& op, ArgsTup&& args_tup, std::index_sequence<>, J...j)
165  {
166  std::make_index_sequence<std::tuple_size_v<ArgsTup>> seq;
167  set_component(m, n_ary_operation_get_component(op, std::forward<ArgsTup>(args_tup), seq, j...), j...);
168  }
169 
170 
171  template<typename M, typename Op, typename ArgsTup, std::size_t I, std::size_t...Is, typename...J>
172  inline void n_ary_operation_iterate(M& m, const Op& op, ArgsTup&& args_tup, std::index_sequence<I, Is...>, J...j)
173  {
174  for (std::size_t i = 0; i < get_index_dimension_of<I>(m); i++)
175  n_ary_operation_iterate(m, op, std::forward<ArgsTup>(args_tup), std::index_sequence<Is...> {}, j..., i);
176  }
177 
178 
180  template<typename...Ds, typename Arg, std::size_t...Ix_Ds>
181  static decltype(auto)
182  replicate_arg(const std::tuple<Ds...>& d_tup, Arg&& arg, std::index_sequence<Ix_Ds...>)
183  {
184  OpenKalman::broadcast(std::forward<Arg>(arg),
186  std::divides<scalar_type_of_t<Arg>>{},
187  get_dimension(std::get<Ix_Ds>(d_tup)),
188  get_index_dimension_of<Ix_Ds>(arg)}...);
189  }
190 
191 
192  template<typename PatternMatrix, typename...Ds, typename Op, typename...Args>
193  static constexpr auto
194  n_ary_operation_impl(const std::tuple<Ds...>& d_tup, Op&& op, Args&&...args)
195  {
196  constexpr std::index_sequence_for<Ds...> seq;
197 
198  // constant_matrix:
199  if constexpr (sizeof...(Args) > 0 and (constant_matrix<Args> and ...) and not is_invocable_with_indices<Op, scalar_type_of_t<Args>...>(seq))
200  {
201  values::operation c {op, constant_coefficient {std::forward<Args>(args)}...};
202  return std::apply(
203  [](auto&&...as){ return make_constant<PatternMatrix>(std::forward<decltype(as)>(as)...); },
204  std::tuple_cat(std::tuple{std::move(c)}, d_tup));
205  }
206  // Library handles n-ary operation.
207  else if constexpr (is_invocable_with_indices<Op, std::add_lvalue_reference_t<scalar_type_of_t<Args>>...>(seq) and
208  interface::n_ary_operation_defined_for<PatternMatrix, const std::tuple<Ds...>&, Op&&, Args&&...>)
209  {
211  auto ret {Interface::n_ary_operation(d_tup, std::forward<Op>(op), replicate_arg(d_tup, std::forward<Args>(args), seq)...)};
212  return std::apply([](auto&& a, auto&&...vs){
213  return make_vector_space_adapter(std::forward<decltype(a)>(a), std::forward<decltype(vs)>(vs)...);
214  }, std::tuple_cat(std::forward_as_tuple(std::move(ret)), d_tup));
215  }
216  else // Catch-all: library does not provide for this n-ary operation.
217  {
218  using Scalar = std::decay_t<typename n_ary_operator_traits<Op, sizeof...(Ds),
219  std::add_lvalue_reference_t<scalar_type_of_t<Args>>...>::type>;
220 
221  if constexpr (((coordinates::dimension_of_v<Ds> == 1) and ...))
222  {
223  // one-by-one matrix
224  auto e = op(get_component(std::forward<Args>(args))...);
225  return make_dense_object_from<PatternMatrix, Layout::none, Scalar>(d_tup, e);
226  }
227  else
228  {
229  auto m = std::apply([](auto&&...ds){
230  return make_dense_object<PatternMatrix, Layout::none, Scalar>(std::forward<decltype(ds)>(ds)...);
231  }, d_tup);
232  n_ary_operation_iterate(m, op, std::forward_as_tuple(std::forward<Args>(args)...), seq);
233  return m;
234  }
235  }
236  }
237 
238  } // namespace detail
239 
240 
309 #ifdef __cpp_concepts
310  template<coordinates::pattern...Ds, typename Operation, indexible...Args> requires (sizeof...(Args) > 0) and
311  detail::n_ary_operator<Operation, sizeof...(Ds), Args...> and (... and (coordinates::dimension_of_v<Ds> != 0))
312  constexpr compatible_with_vector_space_descriptor_collection<std::tuple<Ds...>> auto
313 #else
314  template<typename...Ds, typename Operation, typename...Args, std::enable_if_t<
315  (coordinates::pattern<Ds> and ...) and (indexible<Args> and ...) and (sizeof...(Args) > 0) and
316  detail::n_ary_operator<Operation, sizeof...(Ds), Args...> and (... and (coordinates::dimension_of_v<Ds> != 0)), int> = 0>
317  constexpr auto
318 #endif
319  n_ary_operation(const std::tuple<Ds...>& d_tup, Operation&& operation, Args&&...args)
320  {
321  detail::check_n_ary_dims(std::index_sequence_for<Ds...> {}, d_tup, args...);
322  using Arg0 = std::decay_t<std::tuple_element_t<0, std::tuple<Args...>>>; // \todo Pick the first appropriate pattern matrix, even if not the first one.
323  return detail::n_ary_operation_impl<Arg0>(d_tup, std::forward<Operation>(operation), std::forward<Args>(args)...);
324  }
325 
326 
327  namespace detail
328  {
329  template<std::size_t ix, typename Arg, typename...Args>
330  constexpr auto find_max_dim(const Arg& arg, const Args&...args)
331  {
332  if constexpr (sizeof...(Args) == 0)
333  {
334  auto ret = get_vector_space_descriptor<ix>(arg);
335  if constexpr (dynamic_pattern<decltype(ret)>)
336  {
337  if (get_dimension(ret) == 0) throw std::invalid_argument {"A dimension of an arguments "
338  "to n_ary_operation is zero for at least index " + std::to_string(ix) + "."};
339  }
340  else static_assert(index_dimension_of_v<Arg, ix> != 0, "Arguments to n_ary_operation cannot have zero dimensions");
341  return ret;
342  }
343  else
344  {
345  auto max_d = find_max_dim<ix>(args...);
347  using Max_D = decltype(max_d);
348 
349  if constexpr (fixed_pattern<Arg_D> and fixed_pattern<Max_D>)
350  {
351  constexpr auto dim_arg_d = coordinates::dimension_of_v<Arg_D>;
352  if constexpr (compares_with<Arg_D, Max_D>or (dim_arg_d == 1 and equivalent_to_uniform_static_vector_space_descriptor_component_of<Arg_D, Max_D>))
353  {
354  return max_d;
355  }
356  else
357  {
358  constexpr auto dim_max_d = coordinates::dimension_of_v<Max_D>;
359  static_assert(dim_max_d != 1 or not equivalent_to_uniform_static_vector_space_descriptor_component_of<Max_D, Arg_D>,
360  "The dimension of arguments to n_ary_operation are not compatible with each other for at least one index.");
361  return get_vector_space_descriptor<ix>(arg);
362  }
363  }
364  else if constexpr (coordinates::euclidean_pattern<Arg_D> and coordinates::euclidean_pattern<Max_D>)
365  {
366  if constexpr (fixed_pattern<Arg_D>)
367  {
368  constexpr std::size_t a = coordinates::dimension_of_v<Arg_D>;
369  std::size_t m = get_dimension(max_d);
370  if (a != m and a != 1 and m != 1) throw std::invalid_argument {"The dimension of arguments to n_ary_operation "
371  "are not compatible with each other for at least index " + std::to_string(ix) + "."};
372 
373  if constexpr (a == 1) return max_d;
374  else return get_vector_space_descriptor<ix>(arg);
375  }
376  else if constexpr (fixed_pattern<Max_D>)
377  {
378  auto arg_d = get_vector_space_descriptor<ix>(arg);
379  std::size_t a = get_dimension(arg_d);
380  constexpr std::size_t m = coordinates::dimension_of_v<Max_D>;
381  if (a != m and a != 1 and m != 1) throw std::invalid_argument {"The dimension of arguments to n_ary_operation "
382  "are not compatible with each other for at least index " + std::to_string(ix) + "."};
383 
384  if constexpr (m == 1) return arg_d;
385  else return max_d;
386  }
387  else
388  {
389  std::size_t a = get_index_dimension_of<ix>(arg);
390  std::size_t m = get_dimension(max_d);
391  if (a == m or a == 1) return m;
392  else if (m == 1 and m <= a) return a;
393  else throw std::invalid_argument {"The dimension of arguments to n_ary_operation are not compatible with "
394  "each other for at least index " + std::to_string(ix) + "."};
395  }
396  }
397  else
398  {
399  auto arg_d = get_vector_space_descriptor<ix>(arg);
400  //using Scalar = scalar_type_of_t<Arg>;
401  if (internal::is_uniform_component_of(arg_d, max_d))
402  {
403  //if constexpr (internal::is_DynamicDescriptor<Max_D>::value) return DynamicDescriptor {max_d};
404  //else return DynamicDescriptor<Scalar> {max_d};
405  return max_d;
406  }
407  else if (internal::is_uniform_component_of(max_d, arg_d))
408  {
409  //if constexpr (internal::is_DynamicDescriptor<Max_D>::value) return DynamicDescriptor {arg_d};
410  //else return DynamicDescriptor<Scalar> {arg_d};
411  return arg_d;
412  }
413  else throw std::invalid_argument {"The dimension of arguments to n_ary_operation are not compatible with "
414  "each other for at least index " + std::to_string(ix) + "."};
415  }
416  }
417  }
418 
419 
420  template<std::size_t...ixs, typename...Args>
421  constexpr auto find_max_dims(std::index_sequence<ixs...>, const Args&...args)
422  {
423  return std::tuple {find_max_dim<ixs>(args...)...};
424  }
425 
426  } // namespace detail
427 
428 
477 #ifdef __cpp_concepts
478  template<typename Operation, indexible...Args> requires (sizeof...(Args) > 0) and
479  detail::n_ary_operator<Operation, std::max({index_count_v<Args>...}), Args...>
480 #else
481  template<typename Operation, typename...Args, std::enable_if_t<(indexible<Args> and ...) and
482  (sizeof...(Args) > 0) and detail::n_ary_operator<Operation, std::max({index_count<Args>::value...}), Args...>, int> = 0>
483 #endif
484  constexpr auto
485  n_ary_operation(Operation&& operation, Args&&...args)
486  {
487  auto d_tup = detail::find_max_dims(std::make_index_sequence<std::max({index_count_v<Args>...})> {}, args...);
488  using Arg0 = std::decay_t<std::tuple_element_t<0, std::tuple<Args...>>>;
489  return detail::n_ary_operation_impl<Arg0>(std::move(d_tup), std::forward<Operation>(operation), std::forward<Args>(args)...);
490  }
491 
492 
493  namespace detail
494  {
495  template<typename M, typename Operation, typename Vs_tuple, typename Index_seq, typename K_seq, typename...Is>
496  void nullary_set_components(M& m, const Operation& op, const Vs_tuple&, Index_seq, K_seq, Is...is)
497  {
498  constexpr auto seq = std::make_index_sequence<sizeof...(Is)> {};
499  if constexpr (detail::is_invocable_with_indices<Operation>(seq))
500  set_component(m, op(is...), is...); //< Operation takes a full set of indices.
501  else
502  set_component(m, op(), is...); //< Operation takes no indices.
503  }
504 
505 
506  template<std::size_t DsIndex, std::size_t...DsIndices, typename M, typename Operation,
507  typename Vs_tuple, std::size_t...indices, std::size_t...Ks, typename...Is>
508  void nullary_set_components(M& m, const Operation& op, const Vs_tuple& ds_tup,
509  std::index_sequence<indices...> index_seq, std::index_sequence<Ks...> k_seq, Is...is)
510  {
511  if constexpr (((DsIndex == indices) or ...))
512  {
513  constexpr std::integral_constant<size_t, ((DsIndex == indices ? Ks : 0) + ...)> i;
514  nullary_set_components<DsIndices...>(m, op, ds_tup, index_seq, k_seq, is..., i);
515  }
516  else
517  {
518  // Iterate through the dimensions of the current DsIndex and add set elements for each dimension iteratively.
519  for (std::size_t i = 0; i < get_dimension(std::get<DsIndex>(ds_tup)); ++i)
520  {
521  nullary_set_components<DsIndices...>(m, op, ds_tup, index_seq, k_seq, is..., i);
522  }
523  }
524  }
525 
526 
527  template<std::size_t CurrentOpIndex, std::size_t factor, typename M, typename Operations_tuple,
528  typename Vs_tuple, typename UniqueIndicesSeq, std::size_t...AllDsIndices, typename K_seq>
529  void nullary_iterate(M& m, const Operations_tuple& op_tup, const Vs_tuple& ds_tup,
530  UniqueIndicesSeq unique_indices_seq, std::index_sequence<AllDsIndices...>, K_seq k_seq)
531  {
532  nullary_set_components<AllDsIndices...>(m, std::get<CurrentOpIndex>(op_tup), ds_tup, unique_indices_seq, k_seq);
533  }
534 
535 
536  template<std::size_t CurrentOpIndex, std::size_t factor, std::size_t index, std::size_t...indices,
537  typename M, typename Operations_tuple, typename Vs_tuple, typename UniqueIndicesSeq, typename AllDsSeq,
538  std::size_t...Ks, std::size_t...Js, typename...J_seqs>
539  void nullary_iterate(M& m, const Operations_tuple& op_tup, const Vs_tuple& ds_tup,
540  UniqueIndicesSeq unique_indices_seq, AllDsSeq all_ds_seq, std::index_sequence<Ks...>, std::index_sequence<Js...>,
541  J_seqs...j_seqs)
542  {
543  constexpr std::size_t new_factor = factor / coordinates::dimension_of_v<std::tuple_element_t<index, Vs_tuple>>;
544 
545  (nullary_iterate<CurrentOpIndex + new_factor * Js, new_factor, indices...>(
546  m, op_tup, ds_tup, unique_indices_seq, all_ds_seq, std::index_sequence<Ks..., Js>{}, j_seqs...),...);
547  }
548 
549  } // namespace detail
550 
551 
626  #ifdef __cpp_concepts
627  template<indexible PatternMatrix, std::size_t...indices, coordinates::pattern...Ds, typename...Operations>
628  requires ((fixed_pattern<std::tuple_element_t<indices, std::tuple<Ds...>>>) and ...) and
629  (sizeof...(Operations) == (1 * ... * coordinates::dimension_of_v<std::tuple_element_t<indices, std::tuple<Ds...>>>)) and
630  (detail::n_ary_operator<Operations, sizeof...(Ds)> and ...)
631  #else
632  template<typename PatternMatrix, std::size_t...indices, typename...Ds, typename...Operations, std::enable_if_t<
633  indexible<PatternMatrix> and (coordinates::pattern<Ds> and ...) and
634  ((fixed_pattern<std::tuple_element_t<indices, std::tuple<Ds...>>>) and ...) and
635  (sizeof...(Operations) == (1 * ... * coordinates::dimension_of<std::tuple_element_t<indices, std::tuple<Ds...>>>::value)) and
636  (detail::n_ary_operator<Operations, sizeof...(Ds)> and ...), int> = 0>
637  #endif
638  constexpr auto
639  n_ary_operation(const std::tuple<Ds...>& d_tup, const Operations&...operations)
640  {
641  using Scalar = std::common_type_t<std::decay_t<typename detail::n_ary_operator_traits<Operations, sizeof...(Ds)>::type>...>;
642 
643  // One operation for all elements combined:
644  if constexpr (sizeof...(Operations) == 1)
645  {
646  return detail::n_ary_operation_impl<std::decay_t<PatternMatrix>>(d_tup, operations...);
647  }
648  // One operation for each element, and the operations are not invocable with indices:
649  else if constexpr (((not dynamic_pattern<Ds>) and ...) and
650  sizeof...(operations) == (coordinates::dimension_of_v<Ds> * ...) and
651  not (detail::is_invocable_with_indices<const Operations&>(std::make_index_sequence<sizeof...(Ds)> {}) or ...))
652  {
653  return make_dense_object_from<PatternMatrix, Layout::none, Scalar>(d_tup, operations()...);
654  }
655  // All other cases:
656  else
657  {
658  auto m = make_dense_object<PatternMatrix, Layout::none, Scalar>(d_tup);
659  detail::nullary_iterate<0, sizeof...(Operations), indices...>(
660  m,
661  std::forward_as_tuple(operations...),
662  d_tup,
663  std::index_sequence<indices...> {},
664  std::index_sequence_for<Ds...> {},
665  std::index_sequence<> {},
666  std::make_index_sequence<coordinates::dimension_of_v<std::tuple_element_t<indices, std::tuple<Ds...>>>> {}...);
667  return m;
668  }
669  }
670 
671 
722 #ifdef __cpp_concepts
723  template<indexible PatternMatrix, std::size_t...indices, coordinates::pattern...Ds, typename...Operations>
724  requires ((fixed_pattern<vector_space_descriptor_of_t<PatternMatrix, indices>>) and ...) and
725  (sizeof...(Operations) == (1 * ... * index_dimension_of_v<PatternMatrix, indices>))
726 #else
727  template<typename PatternMatrix, std::size_t...indices, typename...Ds, typename...Operations, std::enable_if_t<
728  indexible<PatternMatrix> and (coordinates::pattern<Ds> and ...) and
729  ((fixed_pattern<typename vector_space_descriptor_of<PatternMatrix, indices>::type>) and ...) and
730  (sizeof...(Operations) == (1 * ... * index_dimension_of<PatternMatrix, indices>::value)), int> = 0>
731 #endif
732  constexpr auto
733  n_ary_operation(const Operations&...operations)
734  {
735  auto d_tup = all_vector_space_descriptors<PatternMatrix>();
736  return n_ary_operation<PatternMatrix, indices...>(d_tup, operations...);
737  }
738 
739 
740  // -------------------------- //
741  // n_ary_operation_in_place //
742  // -------------------------- //
743 
744  namespace detail
745  {
746  template<typename Operation, typename Elem, typename...J>
747  inline void do_elem_operation_in_place_impl(const Operation& operation, Elem& elem, J...j)
748  {
749  if constexpr (std::is_invocable_v<const Operation&, Elem&, J...>)
750  operation(elem, j...);
751  else
752  operation(elem);
753  }
754 
755 
756  template<typename Operation, typename Arg, typename...J>
757  inline void do_elem_operation_in_place(const Operation& operation, Arg& arg, J...j)
758  {
759  auto&& elem = get_component(arg, j...);
760  if constexpr (std::is_assignable_v<decltype((elem)), std::decay_t<decltype(elem)>>)
761  {
762  do_elem_operation_in_place_impl(operation, elem, j...);
763  }
764  else
765  {
766  auto e {std::forward<decltype(elem)>(elem)}; // copy elem
767  static_assert(std::is_assignable_v<decltype((e)), std::decay_t<decltype(elem)>>);
768  do_elem_operation_in_place_impl(operation, e, j...);
769  set_component(arg, std::move(e), j...);
770  }
771  }
772 
773 
774  template<typename Operation, typename Arg, typename Count, typename...J>
775  inline void unary_operation_in_place_impl(const Operation& operation, Arg& arg, const Count& count, J...j)
776  {
777  constexpr auto n = sizeof...(J);
778  if constexpr (n < Count::value)
779  {
780  for (std::size_t i = 0; i < get_index_dimension_of<n>(arg); ++i)
781  unary_operation_in_place_impl(operation, arg, count, j..., i);
782  }
783  else
784  {
785  do_elem_operation_in_place(operation, arg, j...);
786  }
787  }
788 
789  } // namespace detail
790 
791 
826  #ifdef __cpp_concepts
827  template<typename Operation, writable Arg> requires detail::n_ary_operator<Operation, index_count_v<Arg>, Arg>
828  #else
829  template<typename Operation, typename Arg, std::enable_if_t<writable<Arg> and
830  detail::n_ary_operator<Operation, index_count_v<Arg>, Arg>, int> = 0>
831  #endif
832  constexpr decltype(auto)
833  unary_operation_in_place(const Operation& operation, Arg&& arg)
834  {
835  // \todo If the native library has its own facilities for doing this, use them.
836 
837  detail::unary_operation_in_place_impl(operation, arg, count_indices(arg));
838  return std::forward<Arg>(arg);
839  }
840 
841 
842 } // namespace OpenKalman
843 
844 #endif //OPENKALMAN_N_ARY_OPERATION_HPP
constexpr auto count_indices(const T &t)
Get the number of indices available to address the components of an indexible object.
Definition: count_indices.hpp:33
constexpr auto n_ary_operation(const Operations &...operations)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: n_ary_operation.hpp:733
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:319
Definition: n_ary_operation.hpp:113
An operation involving some number of values.
Definition: operation.hpp:69
Arg && set_component(Arg &&arg, const scalar_type_of_t< Arg > &s, const Indices &indices)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: set_component.hpp:51
auto make_vector_space_adapter(Arg &&arg, Descriptors &&descriptors)
If necessary, wrap an object in a wrapper that adds vector space descriptors for each index...
Definition: make_vector_space_adapter.hpp:37
typename scalar_type_of< T >::type scalar_type_of_t
helper template for scalar_type_of.
Definition: scalar_type_of.hpp:54
decltype(auto) constexpr unary_operation_in_place(const Operation &operation, Arg &&arg)
Perform a component-wise, in-place unary operation.
Definition: n_ary_operation.hpp:833
The coordinates::pattern for index N of object T.
Definition: vector_space_descriptor_of.hpp:34
constexpr bool indexible
T is a generalized tensor type.
Definition: indexible.hpp:32
Definition: tuple_reverse.hpp:103
constexpr bool value
T is numerical value or is reducible to a numerical value.
Definition: value.hpp:31
The size of a coordinates::pattern.
Definition: dimension_of.hpp:37
decltype(auto) constexpr broadcast(Arg &&arg, const Factors &...factors)
Broadcast an object by replicating it by factors specified for each index.
Definition: broadcast.hpp:49
The constant associated with T, assuming T is a constant_matrix.
Definition: constant_coefficient.hpp:36
constexpr auto get_dimension(const Arg &arg)
Get the vector dimension of coordinates::pattern Arg.
Definition: get_dimension.hpp:55
The root namespace for OpenKalman.
Definition: basics.hpp:34
An interface to various routines from the linear algebra library associated with indexible object T...
Definition: library_interface.hpp:37
Definition: n_ary_operation.hpp:103
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
The dimension of an index for a matrix, expression, or array.
Definition: index_dimension_of.hpp:34
decltype(auto) constexpr get_component(Arg &&arg, const Indices &indices)
Get a component of an object at a particular set of indices.
Definition: get_component.hpp:54
constexpr bool compatible_with_vector_space_descriptor_collection
indexible T is compatible with pattern_collection D.
Definition: compatible_with_vector_space_descriptor_collection.hpp:74
The minimum number of indices need to access all the components of an object.
Definition: index_count.hpp:33
operation(const Operation &, const Args &...) -> operation< Operation, Args... >
Deduction guide.
Definition: n_ary_operation.hpp:83
constexpr bool index
An object describing a collection of /ref values::index objects.
Definition: index.hpp:75