OpenKalman
Spherical.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-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_SPHERICAL_HPP
17 #define OPENKALMAN_SPHERICAL_HPP
18 
19 #include <type_traits>
20 #include <typeinfo>
21 #include <cmath>
22 #include <array>
25 #include "values/math/real.hpp"
26 #include "values/functions/internal/update_real_part.hpp"
27 #include "values/math/signbit.hpp"
28 #include "values/math/sqrt.hpp"
29 #include "values/math/abs.hpp"
30 #include "values/math/sin.hpp"
31 #include "values/math/cos.hpp"
32 #include "values/math/asin.hpp"
33 #include "values/math/atan2.hpp"
34 #include "linear-algebra/coordinates/interfaces/coordinate_descriptor_traits.hpp"
36 #include "Distance.hpp"
37 #include "Angle.hpp"
38 #include "Inclination.hpp"
39 
40 
42 {
56  template<typename C1 = Distance, typename C2 = angle::Radians, typename C3 = inclination::Radians>
57 #ifdef __cpp_concepts
58  requires
59  (std::same_as<C1, Distance> and angle::angle<C2> and inclination::inclination<C3>) or
60  (std::same_as<C1, Distance> and angle::angle<C3> and inclination::inclination<C2>) or
61  (std::same_as<C2, Distance> and angle::angle<C1> and inclination::inclination<C3>) or
62  (std::same_as<C2, Distance> and angle::angle<C3> and inclination::inclination<C1>) or
63  (std::same_as<C3, Distance> and angle::angle<C1> and inclination::inclination<C2>) or
64  (std::same_as<C3, Distance> and angle::angle<C2> and inclination::inclination<C1>)
65 #endif
66  struct Spherical
67  {
68 #ifndef __cpp_concepts
69  static_assert(
70  (std::is_same_v<C1, Distance> and angle::angle<C2> and inclination::inclination<C3>) or
71  (std::is_same_v<C1, Distance> and angle::angle<C3> and inclination::inclination<C2>) or
72  (std::is_same_v<C2, Distance> and angle::angle<C1> and inclination::inclination<C3>) or
73  (std::is_same_v<C2, Distance> and angle::angle<C3> and inclination::inclination<C1>) or
74  (std::is_same_v<C3, Distance> and angle::angle<C1> and inclination::inclination<C2>) or
75  (std::is_same_v<C3, Distance> and angle::angle<C2> and inclination::inclination<C1>));
76 #endif
77  };
78 
79 } // namespace OpenKalman::coordinates
80 
81 
82 namespace OpenKalman::interface
83 {
84  namespace detail
85  {
86  // Implementation of polar coordinates.
87  template<typename T, typename Min, typename Max, typename Down, typename Up, std::size_t d_i, std::size_t a_i, std::size_t i_i>
89  {
90  private:
91 
92  static constexpr auto min = values::fixed_number_of_v<Min>;
93  static constexpr auto max = values::fixed_number_of_v<Max>;
94  static constexpr auto down = values::fixed_number_of_v<Down>;
95  static constexpr auto up = values::fixed_number_of_v<Up>;
96 
97  public:
98 
99  static constexpr bool is_specialized = true;
100 
101 
102  static constexpr auto
103  dimension(const T&) { return std::integral_constant<std::size_t, 3>{}; };
104 
105 
106  static constexpr auto
107  stat_dimension(const T&) { return std::integral_constant<std::size_t, 4>{}; };
108 
109 
110  static constexpr auto
111  is_euclidean(const T&) { return std::false_type{}; }
112 
113 
114  static constexpr std::size_t
115  hash_code(const T&)
116  {
117  constexpr auto a = coordinates::internal::get_hash_code(coordinates::Angle<Min, Max>{});
118  constexpr auto b = coordinates::internal::get_hash_code(coordinates::Inclination<Down, Up>{});
119  constexpr std::size_t f = (a_i * 2 + i_i * 3 - 2);
120  constexpr auto bits = std::numeric_limits<std::size_t>::digits;
121  if constexpr (bits < 32) return (a - 0x83B0_uz) + (b - 0x83B0_uz) + f * 0x1000_uz;
122  else if constexpr (bits < 64) return (a - 0x83B023AB_uz) + (b - 0x83B023AB_uz) + f * 0x10000000_uz;
123  else return (a - 0x83B023AB3EEFB99A_uz) + (b - 0x83B023AB3EEFB99A_uz) + f * 0x1000000000000000_uz;
124  }
125 
126  private:
127 
128  static constexpr std::size_t d2_i = 0, x_i = 1, y_i = 2, z_i = 3;
129 
130  public:
131 
140 #ifdef __cpp_concepts
141  static constexpr values::value auto
142  to_euclidean_component(const T& t, const auto& g, const values::index auto& euclidean_local_index)
143  requires requires(std::size_t i){ {g(i)} -> values::value; }
144 #else
145  template<typename Getter, typename L, std::enable_if_t<values::index<L> and
146  values::value<typename std::invoke_result<const Getter&, std::size_t>::type>, int> = 0>
147  static constexpr auto
148  to_euclidean_component(const T& t, const Getter& g, const L& euclidean_local_index)
149 #endif
150  {
151  if (euclidean_local_index == d2_i)
152  {
153  return g(d_i);
154  }
155  else
156  {
157  using Scalar = std::decay_t<decltype(g(std::declval<std::size_t>()))>;
158  using R = std::decay_t<decltype(values::real(std::declval<Scalar>()))>;
159  const Scalar cf_inc {numbers::pi_v<R> / (up - down)};
160  const Scalar horiz {R{up + down} * R{0.5}};
161 
162  Scalar phi = cf_inc * (g(i_i) - horiz);
163  if (euclidean_local_index == z_i)
164  {
165  return values::sin(phi);
166  }
167  else
168  {
169  const Scalar cf_cir {2 * numbers::pi_v<R> / (max - min)};
170  const Scalar mid {R{max + min} * R{0.5}};
171  Scalar theta = cf_cir * (g(a_i) - mid);
172 
173  if (euclidean_local_index == x_i) return values::cos(theta) * values::cos(phi);
174  else return values::sin(theta) * values::cos(phi); // euclidean_local_index == y_i
175  }
176  }
177  }
178 
179 
188 #ifdef __cpp_concepts
189  static constexpr values::value auto
190  from_euclidean_component(const T& t, const auto& g, const values::index auto& local_index)
191  requires requires(std::size_t i){ {g(i)} -> values::value; }
192 #else
193  template<typename Getter, typename L, std::enable_if_t<values::index<L> and
194  values::value<typename std::invoke_result<const Getter&, std::size_t>::type>, int> = 0>
195  static constexpr auto
196  from_euclidean_component(const T& t, const Getter& g, const L& local_index)
197 #endif
198  {
199  using Scalar = decltype(g(std::declval<std::size_t>()));
200  Scalar d = g(d2_i);
201  auto dr = values::real(d);
202 
203  if (local_index == d_i)
204  {
205  return values::internal::update_real_part(d, values::abs(dr));
206  }
207  else
208  {
209  using R = std::decay_t<decltype(values::real(std::declval<Scalar>()))>;
210  const Scalar cf_cir {2 * numbers::pi_v<R> / (max - min)};
211  const Scalar mid {R{max + min} * R{0.5}};
212 
213  Scalar x = g(x_i);
214  Scalar y = g(y_i);
215 
216  switch(local_index)
217  {
218  case a_i:
219  {
220  auto xp = values::real(g(x_i));
221  auto yp = values::real(g(y_i));
222  // If distance is negative, flip x and y axes 180 degrees:
223  Scalar x2 = values::internal::update_real_part(x, values::signbit(dr) ? -xp : xp);
224  Scalar y2 = values::internal::update_real_part(y, values::signbit(dr) ? -yp : yp);
225 
226  if constexpr (values::complex<Scalar>) return values::atan2(y2, x2) / cf_cir + mid;
227  else { return values::atan2(y2, x2) / cf_cir + mid; }
228  }
229  default: // case i_i
230  {
231  const Scalar cf_inc {numbers::pi_v<R> / (up - down)};
232  const Scalar horiz {R{up + down} * R{0.5}};
233  Scalar z {g(z_i)};
234  auto zp = values::real(z);
235  Scalar z2 {values::internal::update_real_part(z, values::signbit(dr) ? -zp : zp)};
236  Scalar r {values::sqrt(x*x + y*y + z2*z2)};
237  if constexpr (values::complex<Scalar>)
238  {
239  auto theta = (r == Scalar{0}) ? r : values::asin(z2/r);
240  return theta / cf_inc + horiz;
241  }
242  else
243  {
244  using R = std::decay_t<decltype(values::asin(z2/r))>;
245  // This is so that a zero-radius or faulty spherical coordinate has horizontal inclination:
246  auto theta = (r == 0 or r < z2 or z2 < -r) ? R{0} : values::asin(z2/r);
247  return theta / cf_inc + horiz;
248  }
249 
250  }
251  }
252  }
253 
254  }
255 
256  private:
257 
258  template<typename Scalar>
259  static constexpr auto
260  inclination_wrap_impl(const Scalar& a)
261  {
262  using Ret = std::tuple<std::decay_t<std::decay_t<decltype(values::real(a))>>, bool>;
263  auto ap = values::real(a);
264  using R = std::decay_t<decltype(ap)>;
265  if (ap >= down and ap <= up) // A shortcut, for the easy case.
266  {
267  return Ret { ap, false };
268  }
269  else
270  {
271  constexpr R period = 2 * (up - down);
272  using std::fmod;
273  R ar = fmod(ap - R{down}, period);
274  R ar2 = ar < 0 ? ar + period : ar;
275  bool b = ar2 > up - down; // Whether there is a mirror reflection about vertical axis.
276  return Ret { R{down} + (b ? period - ar2 : ar2), b };
277  }
278  }
279 
280 
281  template<typename Scalar>
282  static constexpr std::decay_t<Scalar>
283  azimuth_wrap_impl(bool reflect_azimuth, Scalar&& a)
284  {
285  using R = std::decay_t<decltype(values::real(std::declval<decltype(a)>()))>;
286  constexpr R period {max - min};
287  constexpr R half_period {(max - min) / R{2}};
288  R ap = reflect_azimuth ? values::real(a) - half_period : values::real(a);
289 
290  if (ap >= min and ap < max) // Check if angle doesn't need wrapping.
291  {
292  return values::internal::update_real_part(std::forward<decltype(a)>(a), ap);;
293  }
294  else // Wrap the angle.
295  {
296  using std::fmod;
297  auto ar = fmod(ap - R{min}, period);
298  if (ar < 0) return values::internal::update_real_part(std::forward<decltype(a)>(a), R{min} + ar + period);
299  else return values::internal::update_real_part(std::forward<decltype(a)>(a), R{min} + ar);
300  }
301  }
302 
303  public:
304 
312 #ifdef __cpp_concepts
313  static constexpr values::value auto
314  get_wrapped_component(const T& t, const auto& g, const values::index auto& local_index)
315  requires requires(std::size_t i){ {g(i)} -> values::value; }
316 #else
317  template<typename Getter, typename L, std::enable_if_t<values::index<L> and
318  values::value<typename std::invoke_result<const Getter&, std::size_t>::type>, int> = 0>
319  static constexpr auto
320  get_wrapped_component(const T& t, const Getter& g, const L& local_index)
321 #endif
322  {
323  auto d = g(d_i);
324  auto dp = values::real(d);
325 
326  switch(local_index)
327  {
328  case d_i:
329  {
330  return values::internal::update_real_part(d, values::abs(dp));
331  }
332  case a_i:
333  {
334  const bool b = std::get<1>(inclination_wrap_impl(g(i_i)));
335  return azimuth_wrap_impl(b != values::signbit(dp), g(a_i));
336  }
337  default: // case i_i
338  {
339  auto i = g(i_i);
340  auto new_i = std::get<0>(inclination_wrap_impl(i));
341  return values::internal::update_real_part(i, values::signbit(dp) ? -new_i : new_i);
342  }
343  }
344  }
345 
346 
356 #ifdef __cpp_concepts
357  static constexpr void
358  set_wrapped_component(const T& t, const auto& s, const auto& g, const values::value auto& x, const values::index auto& local_index)
359  requires requires(std::size_t i){ s(x, i); s(g(i), i); }
360 #else
361  template<typename Setter, typename Getter, typename X, typename L, std::enable_if_t<values::value<X> and values::index<L> and
362  std::is_invocable<const Setter&, const X&, std::size_t>::value and
363  std::is_invocable<const Setter&, typename std::invoke_result<const Getter&, std::size_t>::type, std::size_t>::value, int> = 0>
364  static constexpr void
365  set_wrapped_component(const T& t, const Setter& s, const Getter& g, const X& x, const L& local_index)
366 #endif
367  {
368  switch(local_index)
369  {
370  case d_i:
371  {
372  auto dp = values::real(x);
373  s(values::internal::update_real_part(x, values::abs(dp)), d_i);
374  if (values::signbit(dp)) // If new distance would have been negative
375  {
376  auto azimuth_i = a_i;
377  auto inclination = i_i;
378  s(azimuth_wrap_impl(true, g(azimuth_i)), azimuth_i); // Reflect azimuth.
379  s(-g(inclination), inclination); // Reflect inclination.
380  }
381  break;
382  }
383  case a_i:
384  {
385  s(azimuth_wrap_impl(false, x), a_i);
386  break;
387  }
388  default: // case i_i
389  {
390  const auto [ip, b] = inclination_wrap_impl(x);
391  s(values::internal::update_real_part(x, ip), i_i); // Reflect inclination.
392  const auto azimuth_i = a_i;
393  s(azimuth_wrap_impl(b, g(azimuth_i)), azimuth_i); // Maybe reflect azimuth.
394  break;
395  }
396  }
397  }
398 
399  };
400 
401  } // namespace detail
402 
403 
408  template<typename Min, typename Max, typename Down, typename Up>
409  struct coordinate_descriptor_traits<coordinates::Spherical<coordinates::Distance, coordinates::Angle<Min, Max>, coordinates::Inclination<Down, Up>>>
410  : detail::SphericalBase<coordinates::Spherical<coordinates::Distance, coordinates::Angle<Min, Max>, coordinates::Inclination<Down, Up>>, Min, Max, Down, Up, 0, 1, 2>
411  {};
412 
413 
418  template<typename Down, typename Up, typename Min, typename Max>
419  struct coordinate_descriptor_traits<coordinates::Spherical<coordinates::Distance, coordinates::Inclination<Down, Up>, coordinates::Angle<Min, Max>>>
420  : detail::SphericalBase<coordinates::Spherical<coordinates::Distance, coordinates::Inclination<Down, Up>, coordinates::Angle<Min, Max>>, Min, Max, Down, Up, 0, 2, 1>
421  {};
422 
423 
428  template<typename Min, typename Max, typename Down, typename Up>
429  struct coordinate_descriptor_traits<coordinates::Spherical<coordinates::Angle<Min, Max>, coordinates::Distance, coordinates::Inclination<Down, Up>>>
430  : detail::SphericalBase<coordinates::Spherical<coordinates::Angle<Min, Max>, coordinates::Distance, coordinates::Inclination<Down, Up>>, Min, Max, Down, Up, 1, 0, 2>
431  {};
432 
433 
438  template<typename Down, typename Up, typename Min, typename Max>
439  struct coordinate_descriptor_traits<coordinates::Spherical<coordinates::Inclination<Down, Up>, coordinates::Distance, coordinates::Angle<Min, Max>>>
440  : detail::SphericalBase<coordinates::Spherical<coordinates::Inclination<Down, Up>, coordinates::Distance, coordinates::Angle<Min, Max>>, Min, Max, Down, Up, 1, 2, 0>
441  {};
442 
443 
448  template<typename Min, typename Max, typename Down, typename Up>
449  struct coordinate_descriptor_traits<coordinates::Spherical<coordinates::Angle<Min, Max>, coordinates::Inclination<Down, Up>, coordinates::Distance>>
450  : detail::SphericalBase<coordinates::Spherical<coordinates::Angle<Min, Max>, coordinates::Inclination<Down, Up>, coordinates::Distance>, Min, Max, Down, Up, 2, 0, 1>
451  {};
452 
453 
458  template<typename Down, typename Up, typename Min, typename Max>
459  struct coordinate_descriptor_traits<coordinates::Spherical<coordinates::Inclination<Down, Up>, coordinates::Angle<Min, Max>, coordinates::Distance>>
460  : detail::SphericalBase<coordinates::Spherical<coordinates::Inclination<Down, Up>, coordinates::Angle<Min, Max>, coordinates::Distance>, Min, Max, Down, Up, 2, 1, 0>
461  {};
462 
463 
464 } // namespace OpenKalman::interface
465 
466 #endif //OPENKALMAN_SPHERICAL_HPP
Definition for get_hash_code.
Definition of the Distance class.
Definition: basics.hpp:41
A positive or negative real number φ representing an inclination or declination from the horizon...
Definition: Inclination.hpp:55
static constexpr auto to_euclidean_component(const T &t, const Getter &g, const L &euclidean_local_index)
Maps an element to coordinates in Euclidean space.
Definition: Spherical.hpp:148
constexpr bool value
T is numerical value or is reducible to a numerical value.
Definition: value.hpp:31
constexpr auto sin(const Arg &arg)
Constexpr alternative to the std::sin function.
Definition: sin.hpp:43
static constexpr auto get_wrapped_component(const T &t, const Getter &g, const L &local_index)
Perform modular wrapping of spherical coordinates.
Definition: Spherical.hpp:320
constexpr auto cos(const Arg &arg, std::enable_if_t< values::value< Arg >, int >=0)
Constexpr alternative to the std::cos function.
Definition: cos.hpp:43
Definition: compares_with.hpp:28
Definitions relating to the availability of c++ language features.
static constexpr auto from_euclidean_component(const T &t, const Getter &g, const L &local_index)
Maps a coordinate in Euclidean space to an element.
Definition: Spherical.hpp:196
constexpr auto sqrt(const Arg &arg)
A constexpr alternative to std::sqrt.
Definition: sqrt.hpp:46
constexpr auto atan2(const Y &y_arg, const X &x_arg)
Constexpr alternative to the std::atan2 function.
Definition: atan2.hpp:46
A coordinates::descriptor reflecting spherical coordinates.
Definition: Spherical.hpp:66
constexpr bool signbit(const Arg &arg)
A constexpr function analogous to std::signbit.
Definition: signbit.hpp:39
constexpr auto real(Arg arg)
A constexpr function to obtain the real part of a (complex) number.
Definition: real.hpp:40
Traits for coordinates::pattern objects.
Definition: coordinate_descriptor_traits.hpp:41
constexpr auto abs(const Arg &arg)
A constexpr alternative to std::abs.
Definition: abs.hpp:38
static constexpr void set_wrapped_component(const T &t, const Setter &s, const Getter &g, const X &x, const L &local_index)
Set an element and then perform any necessary modular wrapping.
Definition: Spherical.hpp:365
constexpr bool index
T is an index value.
Definition: index.hpp:56
Definition of the Inclination class and related limits.
Definition for values::number.
An angle or any other simple modular value.
Definition: Angle.hpp:62
Definition for values::real.
constexpr auto asin(const Arg &arg)
Constexpr alternative to the std::asin function.
Definition: asin.hpp:44
Definition of the Angle class and related limits.