SwshTransformJob.hpp
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 #pragma once
5 
6 #include <cmath>
7 #include <complex>
8 #include <cstddef>
9 #include <sharp_cxx.h>
10 #include <utility>
11 #include <vector>
12 
14 #include "DataStructures/Variables.hpp" // IWYU pragma: keep
15 #include "NumericalAlgorithms/Spectral/ComplexDataView.hpp"
16 #include "NumericalAlgorithms/Spectral/SwshCoefficients.hpp" // IWYU pragma: keep
17 #include "NumericalAlgorithms/Spectral/SwshCollocation.hpp"
18 #include "NumericalAlgorithms/Spectral/SwshTags.hpp"
19 #include "Utilities/Gsl.hpp"
20 #include "Utilities/TMPL.hpp"
21 
22 // IWYU pragma: no_forward_declare Variables
23 // IWYU pragma: no_forward_declare Coefficients
24 
25 namespace Spectral {
26 namespace Swsh {
27 
28 /*!
29  * \ingroup SwshGroup
30  * \brief A class which gathers all necessary shared structure among several
31  * spin-weighted spherical harmonic transforms and dispatches to libsharp. Each
32  * `TransformJob` represents exactly one spin-weighted transform libsharp
33  * execution call, and one inverse transform libsharp execution call.
34  *
35  *
36  * \details
37  * Template Parameters:
38  * - `Spin`: The spin weight for all of the fields to transform with this job.
39  * - `Representation`: The element of the `ComplexRepresentation` enum which
40  * parameterizes how the data passed to libsharp will be represented. Two
41  * options are available:
42  * - `ComplexRepresentation:Interleaved`: indicates that the real and imaginary
43  * parts of the collocation values will be passed to libsharp as pointers
44  * into existing complex data structures, minimizing copies, but maintaining
45  * a stride of 2 for 'adjacent' real or imaginary values.
46  * - `ComplexRepresentation::RealsThenImags`: indicates that the real and
47  * imaginary parts of the collocation values will be passed to libsharp as
48  * separate contiguous blocks. At current, this introduces both allocations
49  * and copies. **optimization note** In the future most of the allocations
50  * can be aggregated to calling code, which would pass in buffers by
51  * `not_null` pointers.
52  *
53  * For performance-sensitive code, both options should be tested, as each
54  * strategy has trade-offs.
55  * - `TagList`: A `tmpl::list` of Tags to be forward and/or backward transformed
56  * with the `TransformJob`.
57  *
58  * \note The signs obtained from libsharp transformations must be handled
59  * carefully. (In particular, it does not use the sign convention you will find
60  * in [Wikipedia]
61  * (https://en.wikipedia.org/wiki/Spin-weighted_spherical_harmonics)).
62  * - libsharp deals only with the transformation of real values, so
63  * transformation of complex values must be done for real and imaginary parts
64  * separately.
65  * - due to only transforming real components, it stores only the positive
66  * \f$m\f$ modes (as the rest would be redundant). Therefore, the negative
67  * \f$m\f$ modes must be inferred from conjugation and further sign changes.
68  * - libsharp only has the capability of transforming positive spin weighted
69  * quantities. Therefore, additional steps are taken (involving further
70  * conjugation of the provided data) in order to use those utilities on
71  * negative spin-weighted quantities. The resulting modes have yet more sign
72  * changes that must be taken into account.
73  *
74  * The decomposition resulting from the libsharp transform for spin \f$s\f$ and
75  * complex spin-weighted \f${}_s f\f$ can be represented mathematically as:
76  *
77  * \f{align*}{
78  * {}_s f(\theta, \phi) = \sum_{\ell = 0}^{\ell_\mathrm{max}} \Bigg\{&
79  * \left(\sum_{m = 0}^{\ell} a^\mathrm{sharp, real}_{l m} {}_s Y_{\ell
80  * m}^\mathrm{sharp, real}\right) + \left(\sum_{m=1}^{\ell}
81  * \left(a^\mathrm{sharp, real}_{l m}{}\right)^*
82  * {}_s Y_{\ell -m}^\mathrm{sharp, real}\right) \notag\\
83  * &+ i \left[\left(\sum_{m = 0}^{\ell} a^\mathrm{sharp, imag}_{l m} {}_s
84  * Y_{\ell m}^\mathrm{sharp, imag}\right) + \left(\sum_{m=1}^{\ell}
85  * \left(a^\mathrm{sharp, imag}_{l m}{}\right)^* {}_s Y_{\ell -m}^\mathrm{sharp,
86  * imag} \right)\right] \Bigg\},
87  * \f}
88  *
89  * where
90  *
91  * \f{align*}{
92  * {}_s Y_{\ell m}^\mathrm{sharp, real} &=
93  * \begin{cases}
94  * (-1)^{s + 1} {}_s Y_{\ell m}, & \mathrm{for}\; s < 0, m \ge 0 \\
95  * {}_s Y_{\ell m}, & \mathrm{for}\; s = 0, m \ge 0 \\
96  * - {}_s Y_{\ell m}, & \mathrm{for}\; s > 0, m \ge 0 \\
97  * (-1)^{s + m + 1} {}_s Y_{\ell m}, & \mathrm{for}\; s < 0, m < 0 \\
98  * (-1)^{m} {}_s Y_{\ell m}, & \mathrm{for}\; s = 0, m < 0 \\
99  * (-1)^{m + 1} {}_s Y_{\ell m}, & \mathrm{for}\; s > 0, m < 0
100  * \end{cases} \\
101  * &\equiv {}_s S_{\ell m}^{\mathrm{real}} {}_s Y_{\ell m}\\
102  * {}_s Y_{\ell m}^\mathrm{sharp, imag} &=
103  * \begin{cases}
104  * (-1)^{s + 1} {}_s Y_{\ell m}, & \mathrm{for}\; s < 0, m \ge 0 \\
105  * -{}_s Y_{\ell m}, & \mathrm{for}\; s = 0, m \ge 0 \\
106  * {}_s Y_{\ell m}, & \mathrm{for}\; s > 0, m \ge 0 \\
107  * (-1)^{s + m} {}_s Y_{\ell m}, & \mathrm{for}\; s < 0, m < 0 \\
108  * (-1)^{m} {}_s Y_{\ell m}, & \mathrm{for}\; s = 0, m < 0 \\
109  * (-1)^{m + 1} {}_s Y_{\ell m}, & \mathrm{for}\; s > 0, m < 0
110  * \end{cases} \\
111  * &\equiv {}_s S_{\ell m}^{\mathrm{real}} {}_s Y_{\ell m},
112  * \f}
113  *
114  * where the unadorned \f${}_s Y_{\ell m}\f$ on the right-hand-sides follow the
115  * (older) convention of \cite Goldberg1966uu. Note the phase in these
116  * expressions is not completely standardized, so should be checked carefully
117  * whenever coefficient data is directly manipulated.
118  *
119  * For reference, we can compute the relation between Goldberg spin-weighted
120  * moments \f${}_s f_{\ell m}\f$, defined as
121  *
122  * \f[ {}_s f(\theta, \phi) = \sum_{\ell = 0}^{\ell_\mathrm{max}} \sum_{m =
123  * -\ell}^{\ell} {}_s f_{\ell m} {}_s Y_{\ell m}
124  * \f]
125  *
126  * so,
127  * \f[
128  * {}_s f_{\ell m} =
129  * \begin{cases}
130  * a_{\ell m}^{\mathrm{sharp}, \mathrm{real}} {}_s S_{\ell m}^{\mathrm{real}} +
131  * a_{\ell m}^{\mathrm{sharp}, \mathrm{imag}} {}_s S_{\ell m}^{\mathrm{imag}} &
132  * \mathrm{for} \; m \ge 0 \\
133  * \left(a_{\ell -m}^{\mathrm{sharp}, \mathrm{real}}\right)^* {}_s S_{\ell
134  * m}^{\mathrm{real}} + \left(a_{\ell -m}^{\mathrm{sharp},
135  * \mathrm{imag}}\right)^* {}_s S_{\ell m}^{\mathrm{imag}} &
136  * \mathrm{for} \; m < 0 \\
137  * \end{cases} \f]
138  *
139  *
140  * The resulting coefficients \f$a_{\ell m}\f$ are stored in a triangular,
141  * \f$\ell\f$-varies-fastest configuration. So, for instance, the first
142  * \f$\ell_\mathrm{max}\f$ entries contain the coefficients for \f$m=0\f$ and
143  * all \f$\ell\f$s, and the next \f$\ell_\mathrm{max} - 1\f$ entries contain the
144  * coefficients for \f$m=1\f$ and \f$1 \le \ell \le \ell_\mathrm{max} \f$, and
145  * so on.
146  */
147 template <int Spin, ComplexRepresentation Representation, typename TagList>
149  public:
150  static_assert(cpp17::is_same_v<get_tags_with_spin<Spin, TagList>, TagList>,
151  "All Tags in TagList submitted to TransformJob must have the "
152  "same spin weight as the TransformJob Spin template parameter");
153  using CoefficientTagList = db::wrap_tags_in<Tags::SwshTransform, TagList>;
154  static constexpr int spin = Spin;
155 
156  /// \brief Constructor for transform job. Both the `l_max` and
157  /// `number_of_radial_grid_points` must be specified for the transformation to
158  /// appropriately find all of the data in memory and associate it with the
159  /// correct collocation grid.
160  TransformJob(size_t l_max, size_t number_of_radial_grid_points) noexcept;
161 
162  /// \brief Helper function for determining the size of allocation necessary to
163  /// store the coefficient data.
164  ///
165  /// \note This size is not the same as the collocation data size, as the
166  /// coefficients are stored in the efficient 'triangular' form noted in the
167  /// documentation for `TransformJob`.
168  constexpr size_t coefficient_output_size() const noexcept {
169  return number_of_swsh_coefficients(l_max_) * number_of_radial_grid_points_;
170  }
171 
172  /// \brief Execute the forward spin-weighted spherical harmonic transform
173  /// using libsharp.
174  ///
175  /// \param output A `Variables` which must contain `CoefficientTag<...>`s
176  /// for each of the tags provided via `TagList`. The coefficients will be
177  /// stored appropriately in this Variables.
178  /// \param input A `Variables` which must contain each of the tags provided
179  /// via `TagList`. The collocation points may be temporarily altered
180  /// (conjugated) during the execution of the transform if the `Spin` is
181  /// negative, but are reverted to their original state by the end of the
182  /// function execution.
183  template <typename InputVarsTagList, typename OutputVarsTagList>
184  void execute_transform(
185  gsl::not_null<Variables<OutputVarsTagList>*> output,
186  gsl::not_null<Variables<InputVarsTagList>*> input) const noexcept;
187 
188  /// \brief Execute the inverse spin-weighted spherical harmonic transform
189  /// using libsharp.
190  ///
191  /// \param input A `Variables` which must contain `CoefficientTag<...>`s
192  /// for each of the tags provided via `TagList`. The coefficients may be
193  /// temporarily altered (conjugated) during the execution of the transform if
194  /// the `Spin` is negative, but are reverted to their original state by the
195  /// end of the function execution.
196  /// \param output A `Variables` which must contain each of the tags provided
197  /// via `TagList`. The collocation data will be stored appropriately in this
198  /// Variables.
199  template <typename InputVarsTagList, typename OutputVarsTagList>
201  gsl::not_null<Variables<OutputVarsTagList>*> output,
202  gsl::not_null<Variables<InputVarsTagList>*> input) const noexcept;
203 
204  private:
205  size_t number_of_radial_grid_points_;
206  size_t l_max_;
207  sharp_alm_info* alm_info_;
208  const Collocation<Representation>* collocation_metadata_;
209 };
210 
211 template <int Spin, ComplexRepresentation Representation, typename TagList>
213  const size_t l_max, const size_t number_of_radial_grid_points) noexcept
214  : number_of_radial_grid_points_{number_of_radial_grid_points},
215  l_max_{l_max},
216  collocation_metadata_{&precomputed_collocation<Representation>(l_max_)} {
217  alm_info_ = detail::precomputed_coefficients(l_max_).get_sharp_alm_info();
218 }
219 
220 template <int Spin, ComplexRepresentation Representation, typename TagList>
221 template <typename InputVarsTagList, typename OutputVarsTagList>
223  const gsl::not_null<Variables<OutputVarsTagList>*> output,
224  const gsl::not_null<Variables<InputVarsTagList>*> input) const noexcept {
225  // assemble a list of pointers into the collocation point data. This is
226  // required because libsharp expects pointers to pointers.
228  pre_transform_views.reserve(number_of_radial_grid_points_ *
229  tmpl::size<TagList>::value);
230  std::vector<const double*> pre_transform_collocation_data;
231  pre_transform_collocation_data.reserve(2 * number_of_radial_grid_points_ *
232  tmpl::size<TagList>::value);
233 
234  // for each Tag and each slice block in the radial direction, construct a
235  // ComplexDataView at the appropriate locations, then retrieve its real
236  // and imaginary data and put it in subsequent slots in the
237  // pre_transform_collocation_data.
238  size_t num_transforms = 0;
239  tmpl::for_each<TagList>([
240  &pre_transform_collocation_data, &pre_transform_views, &num_transforms,
241  &input, this
242  ](auto x) noexcept {
243  using transform_var_tag = typename decltype(x)::type;
244  decltype(auto) input_vector = get(get<transform_var_tag>(*input)).data();
245  for (size_t i = 0; i < number_of_radial_grid_points_; ++i) {
246  pre_transform_views.push_back(detail::ComplexDataView<Representation>{
247  make_not_null(&input_vector), collocation_metadata_->size(),
248  i * collocation_metadata_->size()});
249  pre_transform_collocation_data.push_back(
250  pre_transform_views.back().real_data());
251  // alteration needed because libsharp doesn't support negative spins
252  if (spin < 0) {
253  pre_transform_views.back().conjugate();
254  }
255  pre_transform_collocation_data.push_back(
256  pre_transform_views.back().imag_data());
257  // libsharp considers two arrays per transform when spin is not zero.
258  num_transforms += (spin == 0 ? 2 : 1);
259  }
260  });
261 
262  std::vector<std::complex<double>*> post_transform_coefficient_data;
263  post_transform_coefficient_data.reserve(2 * number_of_radial_grid_points_ *
264  tmpl::size<TagList>::value);
265 
266  // assemble list of locations in the output Variables for the destinations
267  // of the coefficient data
268  tmpl::for_each<CoefficientTagList>([
269  &post_transform_coefficient_data, &output, this
270  ](auto x) noexcept {
271  using coefficient_var_tag = typename decltype(x)::type;
272  decltype(auto) output_vector =
273  get(get<coefficient_var_tag>(*output)).data();
274  for (size_t i = 0; i < number_of_radial_grid_points_; ++i) {
275  // coefficients associated with the real part
276  post_transform_coefficient_data.push_back(
277  output_vector.data() + 2 * i * number_of_swsh_coefficients(l_max_));
278  // coefficients associated with the imaginary part
279  post_transform_coefficient_data.push_back(
280  output_vector.data() +
281  (2 * i + 1) * number_of_swsh_coefficients(l_max_));
282  }
283  });
284 
285  sharp_execute(SHARP_MAP2ALM, abs(spin),
286  post_transform_coefficient_data.data(),
287  pre_transform_collocation_data.data(),
288  collocation_metadata_->get_sharp_geom_info(), alm_info_,
289  num_transforms, SHARP_DP, nullptr, nullptr);
290 
291  // libsharp only has the ability to transform positive spins, so we have to
292  // perform conjugation to deal with negative spins.
293  if (spin < 0) {
294  // fix the conjugation performed prior to transformation.
295  for (auto& view : pre_transform_views) {
296  view.conjugate();
297  }
298  }
299 }
300 
301 template <int Spin, ComplexRepresentation Representation, typename TagList>
302 template <typename InputVarsTagList, typename OutputVarsTagList>
304  const gsl::not_null<Variables<OutputVarsTagList>*> output,
305  const gsl::not_null<Variables<InputVarsTagList>*> input) const noexcept {
306  std::vector<std::complex<double>*> pre_transform_coefficient_data;
307  pre_transform_coefficient_data.reserve(2 * number_of_radial_grid_points_ *
308  tmpl::size<CoefficientTagList>::value);
309  // assemble list of locations in the input Variables for the locations
310  // of the coefficient data
311  tmpl::for_each<CoefficientTagList>([&pre_transform_coefficient_data, &input,
312  this](auto x) {
313  using coefficient_var_tag = typename decltype(x)::type;
314  decltype(auto) input_vector = get(get<coefficient_var_tag>(*input)).data();
315  for (size_t i = 0; i < number_of_radial_grid_points_; ++i) {
316  pre_transform_coefficient_data.push_back(
317  input_vector.data() + 2 * i * number_of_swsh_coefficients(l_max_));
318  pre_transform_coefficient_data.push_back(
319  input_vector.data() +
320  (2 * i + 1) * number_of_swsh_coefficients(l_max_));
321  }
322  });
323 
325  post_transform_views.reserve(number_of_radial_grid_points_ *
326  tmpl::size<TagList>::value);
327  std::vector<const double*> post_transform_collocation_data;
328  post_transform_collocation_data.reserve(2 * number_of_radial_grid_points_ *
329  tmpl::size<TagList>::value);
330 
331  // for each tag in `TagList`, construct a ComplexDataView at the
332  // appropriate locations, then retrieve its real and imaginary data and put
333  // it in subsequent slots in the pre_transform_collocation_data
334  size_t num_transforms = 0;
335  tmpl::for_each<TagList>([&post_transform_collocation_data, &num_transforms,
336  &post_transform_views, &output, this](auto x) {
337  using transform_var_tag = typename decltype(x)::type;
338  decltype(auto) output_vector = get(get<transform_var_tag>(*output)).data();
339  for (size_t i = 0; i < number_of_radial_grid_points_; ++i) {
340  post_transform_views.push_back(detail::ComplexDataView<Representation>{
341  make_not_null(&output_vector), collocation_metadata_->size(),
342  i * collocation_metadata_->size()});
343  post_transform_collocation_data.push_back(
344  post_transform_views.back().real_data());
345  post_transform_collocation_data.push_back(
346  post_transform_views.back().imag_data());
347  // libsharp considers two arrays per transform when spin is not zero.
348  num_transforms += (spin == 0 ? 2 : 1);
349  }
350  });
351 
352  sharp_execute(SHARP_ALM2MAP, abs(spin), pre_transform_coefficient_data.data(),
353  post_transform_collocation_data.data(),
354  collocation_metadata_->get_sharp_geom_info(), alm_info_,
355  num_transforms, SHARP_DP, nullptr, nullptr);
356 
357  if (spin < 0) {
358  // the conjugate is needed because libsharp doesn't handle negative spins.
359  for (auto& view : post_transform_views) {
360  view.conjugate();
361  }
362  }
363 
364  // The inverse transformed collocation data has just been placed in the
365  // memory blocks controlled by the `ComplexDataView`s. Finally, that data
366  // must be flushed back to the Variables.
367  for (auto& view : post_transform_views) {
368  view.copy_back_to_source();
369  }
370 }
371 
372 namespace detail {
373 template <typename Job>
374 struct transform_job_is_not_empty : std::true_type {};
375 
376 template <int Spin, ComplexRepresentation Representation>
377 struct transform_job_is_not_empty<
378  TransformJob<Spin, Representation, tmpl::list<>>> : std::false_type {};
379 
380 template <int MinSpin, ComplexRepresentation Representation, typename TagList,
381  typename IndexSequence>
382 struct make_swsh_transform_job_list_impl;
383 
384 template <int MinSpin, ComplexRepresentation Representation, typename TagList,
385  int... Is>
386 struct make_swsh_transform_job_list_impl<MinSpin, Representation, TagList,
387  std::integer_sequence<int, Is...>> {
388  using type = tmpl::filter<
389  tmpl::list<TransformJob<Is + MinSpin, Representation,
391  transform_job_is_not_empty<tmpl::_1>>;
392 };
393 } // namespace detail
394 
395 /// \ingroup SpectralGroup
396 /// \brief Assemble a `tmpl::list` of `TransformJob` given a list of tags
397 /// `TagList` that need to be transformed. The `Representation` is the
398 /// `ComplexRepresentation` to use for the transformations.
399 ///
400 /// \details Up to five `TransformJob` will be returned, corresponding to
401 /// the possible spin values. Any number of transformations are aggregated into
402 /// that set of `TransformJob`s. The number of transforms is up to five because
403 /// the libsharp utility only has capability to perform spin-weighted spherical
404 /// harmonic transforms for integer spin-weights from -2 to 2.
405 ///
406 /// \snippet Test_SwshTransformJob.cpp make_swsh_transform_job_list
407 template <ComplexRepresentation Representation, typename TagList>
409  typename detail::make_swsh_transform_job_list_impl<
410  -2, Representation, TagList,
412 
413 /// \ingroup SpectralGroup
414 /// \brief Assemble a `tmpl::list` of `TransformJob`s given a list of
415 /// `Derivative<Tag, Derivative>` that need to be computed. The
416 /// `TransformJob`s constructed by this type alias correspond to the
417 /// `Tag`s in the list.
418 ///
419 /// \details This is intended as a convenience utility for the first step of a
420 /// derivative routine, where one transforms the set of fields for which
421 /// derivatives are required.
422 ///
423 /// \snippet Test_SwshTransformJob.cpp make_swsh_transform_from_derivative_tags
424 template <ComplexRepresentation Representation, typename DerivativeTagList>
426  typename detail::make_swsh_transform_job_list_impl<
427  -2, Representation,
428  tmpl::transform<DerivativeTagList,
429  tmpl::bind<db::remove_tag_prefix, tmpl::_1>>,
431 } // namespace Swsh
432 } // namespace Spectral
void execute_transform(gsl::not_null< Variables< OutputVarsTagList > *> output, gsl::not_null< Variables< InputVarsTagList > *> input) const noexcept
Execute the forward spin-weighted spherical harmonic transform using libsharp.
typename detail::make_swsh_transform_job_list_impl< -2, Representation, tmpl::transform< DerivativeTagList, tmpl::bind< db::remove_tag_prefix, tmpl::_1 > >, decltype(std::make_integer_sequence< int, 5 >{})>::type make_swsh_transform_job_list_from_derivative_tags
Assemble a tmpl::list of TransformJobs given a list of Derivative<Tag, Derivative> that need to be co...
Definition: SwshTransformJob.hpp:430
void execute_inverse_transform(gsl::not_null< Variables< OutputVarsTagList > *> output, gsl::not_null< Variables< InputVarsTagList > *> input) const noexcept
Execute the inverse spin-weighted spherical harmonic transform using libsharp.
A wrapper class for the spherical harmonic library collocation data.
Definition: SwshCollocation.hpp:66
tmpl::transform< TagList, tmpl::bind< Wrapper, tmpl::_1, tmpl::pin< Args >... > > wrap_tags_in
Create a new list of Tags by wrapping each tag in TagList using the Wrapper.
Definition: DataBoxTag.hpp:418
tmpl::remove_duplicates< tmpl::filter< TagList, detail::has_spin< tmpl::_1, std::integral_constant< int, Spin > >> > get_tags_with_spin
Extract from TagList the subset of those tags that have a static int member spin equal to the templat...
Definition: SwshTags.hpp:187
Definition: Determinant.hpp:11
A class which gathers all necessary shared structure among several spin-weighted spherical harmonic t...
Definition: SwshTransformJob.hpp:148
typename detail::make_swsh_transform_job_list_impl< -2, Representation, TagList, decltype(std::make_integer_sequence< int, 5 >{})>::type make_swsh_transform_job_list
Assemble a tmpl::list of TransformJob given a list of tags TagList that need to be transformed...
Definition: SwshTransformJob.hpp:411
constexpr bool is_same_v
Variable template for is_same.
Definition: TypeTraits.hpp:221
constexpr size_t coefficient_output_size() const noexcept
Helper function for determining the size of allocation necessary to store the coefficient data...
Definition: SwshTransformJob.hpp:168
Defines class Variables.
TransformJob(size_t l_max, size_t number_of_radial_grid_points) noexcept
Constructor for transform job. Both the l_max and number_of_radial_grid_points must be specified for ...
Definition: SwshTransformJob.hpp:212
constexpr size_t number_of_swsh_coefficients(const size_t l_max) noexcept
Convenience function for determining the number of spin-weighted spherical harmonics coefficients tha...
Definition: SwshCoefficients.hpp:20
Wraps the template metaprogramming library used (brigand)
Defines functions and classes from the GSL.
gsl::not_null< T * > make_not_null(T *ptr) noexcept
Construct a not_null from a pointer. Often this will be done as an implicit conversion, but it may be necessary to perform the conversion explicitly when type deduction is desired.
Definition: Gsl.hpp:863
Functionality associated with a particular choice of basis functions and quadrature for spectral oper...
Definition: Chebyshev.cpp:19
ComplexRepresentation
A set of labels for the possible representations of complex numbers for the ComplexDataView and the c...
Definition: ComplexDataView.hpp:57
Defines classes SimpleTag, PrefixTag, ComputeTag and several functions for retrieving tag info...
Require a pointer to not be a nullptr
Definition: ConservativeFromPrimitive.hpp:12