ComplexDataView.hpp
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 #pragma once
5 
6 #include <complex>
7 #include <cstddef>
8 
9 #include "DataStructures/ComplexDataVector.hpp"
10 #include "DataStructures/DataVector.hpp"
11 #include "Utilities/Gsl.hpp"
12 
13 namespace Spectral {
14 /// \ingroup SpectralGroup
15 /// Namespace for spin-weighted spherical harmonic utilities.
16 namespace Swsh {
17 
18 /// \brief A set of labels for the possible representations of complex numbers
19 /// for the `ComplexDataView` and the computations performed in the
20 /// spin-weighted spherical harmonic transform library.
21 ///
22 /// \details The representation describes one of two behaviors:
23 /// - `Interleaved`: The vectors of complex numbers will be represented by
24 /// alternating doubles in memory. This causes both the real and imaginary part
25 /// at a given gridpoint to be near one another, but successive real values
26 /// farther. This is the native representation of complex data in the C++
27 /// standard, and is the representation needed for Blaze math
28 /// operations. Therefore, using this representation type in libsharp
29 /// computations will cause operations which access only the real or imaginary
30 /// parts individually to trace over larger memory regions. However, this
31 /// representation will give rise to fewer copying operations to perform the
32 /// libsharp operations.
33 ///
34 /// - `RealsThenImags`: The vectors of complex numbers will primarily be
35 /// represented by a pair of vectors of doubles, one for the real values and
36 /// one for the imaginary values (the full computation cannot be performed
37 /// exclusively in this representation, as it must return to a vector of
38 /// `std::complex<double>` for Blaze math operations). This causes the
39 /// successive real values for different gridpoints to be closer in memory, but
40 /// the real and imaginary parts for a given gridpoint to be farther in
41 /// memory. This is not the native representation for complex data in C++, so
42 /// the data must be transformed between operations which use Blaze and the
43 /// transform operations which use `RealsThenImags`. Therefore, using this
44 /// representation in libsharp computations will cause operations which act on
45 /// real or imaginary parts individually to have better memory locality (so
46 /// likely improved cache performance, but such statements are highly
47 /// hardware-dependent). However, this representation will give rise to more
48 /// copying operations to perform the libsharp operations.
49 ///
50 /// \note The pair of representations is provided as a means to 'turn a dial' in
51 /// optimizations. It is unclear which of these representations will be
52 /// preferable, and it may well be the case that different representations are
53 /// better for different calculation types or different hardware. Therefore,
54 /// when optimizing code which uses libsharp, it is encouraged to profile the
55 /// cost of each representation for a computation and choose the one which
56 /// performs best.
57 enum class ComplexRepresentation { Interleaved, RealsThenImags };
58 
59 namespace detail {
60 // A storage container for storing sub-vector references ("views") of a
61 // ComplexDataVector.
62 //
63 // This class takes as a template argument a ComplexRepresentation to use when
64 // returning pointers to the components of the complex data. The representation
65 // is either:
66 // - `ComplexRepresentation::Interleaved`, which indicates that the complex data
67 // is represented as a vector of `std::complex<double>`, and then
68 // ComplexDataView acts as a view (a pure reference to a subvector).
69 // - `ComplexRepresentation::RealsThenImags`, which indicates that the complex
70 // data is represented as a pair of vectors of `double`. This is no longer a
71 // strict view in the typical sense, as the memory layout is different from
72 // the data which it references. In order to minimize copies, the user must
73 // specify when edits to the ComplexDataView are complete by calling the
74 // `copy_back_to_source()` member function.
75 //
76 // Warning: For optimization reasons, mutations applied via member functions or
77 // manipulations of data pointed to by pointers returned by member functions
78 // *may or may not* be immediately applied to the source vector used to
79 // construct the ComplexDataView. Correct use of this class will first perform
80 // (potentially several) manipulations of the data via the ComplexDataView, then
81 // as a final step flush the data to the source vector via the member function
82 // `copy_back_to_source()`. At that point, the vector is guaranteed to be
83 // placed in the same state as the ComplexDataView. **The process of
84 // constructing a ComplexDataView, then mutating data in both the
85 // ComplexDataView and the source vector, then calling `copy_back_to_source()`
86 // is considered undefined behavior, and depends on the details of the memory
87 // layout chosen.**
88 template <ComplexRepresentation Representation>
89 class ComplexDataView {
90  public:
91  // The Representation determines the internal data representation
92  static const ComplexRepresentation complex_representation = Representation;
93 
94  // The internal storage type
95  using value_type = std::complex<double>;
96 
97  // Construct a view which starts at index `offset` of the supplied
98  // vector, and extends for `size` elements
99  ComplexDataView(gsl::not_null<ComplexDataVector*> vector, size_t size,
100  size_t offset = 0) noexcept;
101 
102  // Construct a view which starts at pointer `start` and extends for
103  // `size` elements. Need not be a part of a ComplexDataVector.
104  ComplexDataView(std::complex<double>* start, size_t size) noexcept;
105 
106  // For the lifetime of the data view, it points to the same portion of a
107  // single vector. We disallow default move assignment, as it would exhibit
108  // behavior that would contradict that. All assignment operations permitted by
109  // the ComplexDataView are copying operations which act only on the data, not
110  // the reference.
111  ComplexDataView() = delete;
112  ComplexDataView(const ComplexDataView&) = default;
113  ComplexDataView(ComplexDataView&&) = default;
114  ComplexDataView operator=(ComplexDataView&&) = delete;
115  ~ComplexDataView() = default;
116 
117  // assign into the view the values of a same-sized ComplexDataVector
118  ComplexDataView<Representation>& operator=(
119  const ComplexDataVector& vector) noexcept;
120 
121  // Assign into the view the values from a same-sized view
122  ComplexDataView<Representation>& operator=(
123  const ComplexDataView<Representation>& view) noexcept;
124 
125  // Conjugate the data.
126  void conjugate() noexcept;
127 
128  // Assign into the real components of a view the values from a
129  // provided `DataVector`
130  ComplexDataView<Representation>& assign_real(
131  const DataVector& vector) noexcept;
132 
133  // Assign into the imaginary components of a view the values from a
134  // provided `DataVector`
135  ComplexDataView<Representation>& assign_imag(
136  const DataVector& vector) noexcept;
137 
138  // Gets the size of the view (the number of complex entries).
139  size_t size() const noexcept { return size_; }
140 
141  // Gets the stride between successive real or successive imaginary components
142  // in memory.
143  static constexpr size_t stride() noexcept { return stride_; }
144 
145  // Gets the raw pointer to the start of the real data, which are separated
146  // from one another by `stride()`.
147  double* real_data() noexcept;
148  const double* real_data() const noexcept;
149 
150  // Gets the raw pointer to the start of the imaginary data, which are
151  // separated from one another by `stride()`.
152  double* imag_data() noexcept;
153  const double* imag_data() const noexcept;
154 
155  // A function for manually flushing the data back to the source. This class
156  // minimizes copies wherever possible, so the manual control of bringing the
157  // data back to the source vector only when necessary is important for
158  // optimization.
159  // Warning: Until this function is called, mutations applied via other member
160  // functions or mutations applied via the pointers obtained from other member
161  // functions are not guaranteed to be applied to the source vector.
162  void copy_back_to_source() noexcept;
163 
164  private:
165  size_t size_;
166 
167  static const size_t stride_ =
168  Representation == ComplexRepresentation::RealsThenImags ? 1 : 2;
169 
170  // In either case, we want to avoid unnecessary copies, so we track whether
171  // the data has been copied from one representation to the other.
172  bool real_slices_up_to_date_;
173  // These two DataVectors are unused in the case of `Interleaved`
174  // representation
175  DataVector real_slice_;
176  DataVector imag_slice_;
177 
178  double* data_real_;
179  double* data_imag_;
180 
181  ComplexDataVector complex_view_;
182 };
183 
184 } // namespace detail
185 } // namespace Swsh
186 } // namespace Spectral
Definition: Determinant.hpp:11
Stores a collection of complex function values.
Definition: ComplexDataVector.hpp:47
Stores a collection of function values.
Definition: DataVector.hpp:46
Defines functions and classes from the GSL.
Functionality associated with a particular choice of basis functions and quadrature for spectral oper...
Definition: Chebyshev.cpp:16
ComplexRepresentation
A set of labels for the possible representations of complex numbers for the ComplexDataView and the c...
Definition: ComplexDataView.hpp:57
Require a pointer to not be a nullptr
Definition: ConservativeFromPrimitive.hpp:12