Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <array>
7 : #include <cmath>
8 : #include <cstddef>
9 : #include <utility>
10 : #include <vector>
11 :
12 : #include "DataStructures/DataVector.hpp"
13 : #include "DataStructures/DynamicBuffer.hpp"
14 : #include "DataStructures/Tensor/TypeAliases.hpp"
15 : #include "NumericalAlgorithms/SphericalHarmonics/SpherepackHelper.hpp"
16 : #include "Utilities/Blas.hpp"
17 : #include "Utilities/ForceInline.hpp"
18 : #include "Utilities/Gsl.hpp"
19 :
20 : /// Items related to spherical harmonics
21 : namespace ylm {
22 :
23 : /*!
24 : * \ingroup SpectralGroup
25 : *
26 : * \brief Defines the C++ interface to SPHEREPACK.
27 : *
28 : * \details The class `Spherepack` defines the C++ interface to the fortran
29 : * library SPHEREPACK used for computations on the surface of a sphere.
30 : *
31 : * Given a real-valued, scalar function \f$g(\theta, \phi)\f$, SPHEREPACK
32 : * expands it as:
33 : *
34 : * \f{align}
35 : * g(\theta, \phi)
36 : * &=\frac{1}{2}\sum_{l=0}^{l_{\max}}\bar P_l^0(\cos\theta) a_{l0}
37 : * +\sum_{l=1}^{l_{\max}}\sum_{m=1}^{\min(l, m_{\max})}\bar P_l^m(\cos\theta)\{
38 : * a_{lm}\cos m\phi -b_{lm}\sin m\phi\}\label{eq:spherepack_expansion}
39 : * \f}
40 : *
41 : * where \f$a_{lm}\f$ and \f$b_{lm}\f$ are real-valued
42 : * spectral coefficient arrays used by
43 : * SPHEREPACK, \f$P_l^m(x)\f$ are defined as
44 : *
45 : * \f{align}
46 : * \bar P_l^m(x)&=\sqrt{\frac{(2l+1)(l-m)!}{2(l+m)!}}\;P_{lm}(x)
47 : * \f}
48 : *
49 : * and \f$P_{nm}(x)\f$ are the associated Legendre polynomials as defined,
50 : * for example, in Jackson's "Classical Electrodynamics".
51 : *
52 : * #### Relationship to standard spherical harmonics
53 : *
54 : * The standard expansion of \f$g(\theta, \phi)\f$ in terms of scalar
55 : * spherical harmonics is
56 : * \f{align}
57 : * g(\theta, \phi)
58 : * &=
59 : * \sum_{l=0}^{l_{\max}}\sum_{m=-\min(l, m_{\max})}^{\min(l, m_{\max})}
60 : * A_{lm} Y_{lm}(\theta,\phi),
61 : * \f}
62 : * where \f$Y_{lm}(\theta,\phi)\f$ are the usual complex-valued scalar
63 : * spherical harmonics (as defined, for example, in
64 : * Jackson's "Classical Electrodynamics")
65 : * and \f$A_{lm}\f$ are complex coefficients.
66 : *
67 : * The relationship between the complex coefficients \f$A_{lm}\f$ and
68 : * SPHEREPACK's real-valued \f$a_{lm}\f$ and \f$b_{lm}\f$ is
69 : * \f{align}
70 : * a_{l0} & = \sqrt{\frac{2}{\pi}}A_{l0}&\qquad l\geq 0,\\
71 : * a_{lm} & = (-1)^m\sqrt{\frac{2}{\pi}} \mathrm{Re}(A_{lm})
72 : * &\qquad l\geq 1, m\geq 1, \\
73 : * b_{lm} & = (-1)^m\sqrt{\frac{2}{\pi}} \mathrm{Im}(A_{lm})
74 : * &\qquad l\geq 1, m\geq 1.
75 : * \f}
76 : *
77 : * \note If \f$g\f$ is real,
78 : * \f$A_{lm} = (-1)^m A^\star_{l -m}\f$ (where \f${}^\star\f$ means
79 : * a complex conjugate); this is why we don't need to consider \f$m<0\f$
80 : * in the previous formulas or in SPHEREPACK's expansion.
81 : *
82 : * #### Relationship to real-valued spherical harmonics
83 : *
84 : * Sometimes it is useful to expand a real-valued function in the form
85 : * \f{align}
86 : * g(\theta, \phi)
87 : * &= \sum_{l=0}^\infty\sum_{m=0}^l
88 : * \left[
89 : * c_{lm}\mathrm{Re}(Y_{lm}(\theta, \phi))+
90 : * d_{nm}\mathrm{Im}(Y_{lm}(\theta, \phi))
91 : * \right].
92 : * \f}
93 : * The coefficients here are therefore
94 : * \f{align}
95 : * c_{l0} &= A_{l0},\\
96 : * c_{lm} &= 2\mathrm{Re}(A_{lm}) \qquad m\geq 1,\\
97 : * d_{lm} &=-2\mathrm{Im}(A_{lm}).
98 : * \f}
99 : *
100 : * #### Modal and nodal representations
101 : *
102 : * Internally, SPHEREPACK can represent its expansion in two ways which we
103 : * will refer to as modal and nodal representations:
104 : *
105 : * -# modal: The spectral coefficient arrays \f$a_{lm}\f$ and \f$b_{lm}\f$,
106 : * referred to as `spectral_coefs` in the methods below. For this C++ interface,
107 : * they are saved in a single `DataVector`. To help you index the coefficients
108 : * as expected by this interface, use the class `SpherepackIterator`.
109 : *
110 : * -# nodal: The values at certain collocation points, referred to as
111 : * `collocation_values` in the methods below. This is an array of the expanded
112 : * function \f$g(\theta,\phi)\f$ evaluated at collocation values
113 : * \f$(\theta_i,\phi_j)\f$, where \f$\theta_i\f$ are Gauss-Legendre quadrature
114 : * nodes in the interval \f$(0, \pi)\f$ with \f$i = 0, ..., l_{\max}\f$, and
115 : * \f$\phi_j\f$ is distributed uniformly in \f$(0, 2\pi)\f$ with \f$i = 0, ...,
116 : * 2m_{\max}\f$. The angles of the collocation points can be computed with the
117 : * method `theta_phi_points`.
118 : *
119 : * To convert between the two representations the methods `spec_to_phys` and
120 : * `phys_to_spec` can be used. For internal calculations SPHEREPACK will usually
121 : * convert to spectral coefficients first, so it is in general more efficient to
122 : * use these directly.
123 : *
124 : * Most methods of SPHEREPACK will compute the requested values of e.g.
125 : * `gradient` or `scalar_laplacian` at the collocation points, effectively
126 : * returning an expansion in nodal form as defined above. To evaluate the
127 : * function at arbitrary angles \f$\theta\f$, \f$\phi\f$, these values have to
128 : * be "interpolated" (i.e. the new expansion evaluated) using `interpolate`.
129 : *
130 : * Spherepack stores two types of quantities:
131 : * 1. storage_, which is filled in the constructor and is always const.
132 : * 2. memory_pool_, which is dynamic and thread_local, and is overwritten
133 : * by various member functions that need temporary storage.
134 : */
135 1 : class Spherepack {
136 : public:
137 : /// Type returned by gradient function.
138 1 : using FirstDeriv = tnsr::i<DataVector, 2, Frame::ElementLogical>;
139 : /// Type returned by second derivative function.
140 1 : using SecondDeriv = tnsr::ij<DataVector, 2, Frame::ElementLogical>;
141 :
142 : /// Struct to hold cached information at a set of target interpolation
143 : /// points.
144 : template <typename T>
145 1 : struct InterpolationInfo {
146 0 : InterpolationInfo(size_t l_max, size_t m_max, const gsl::span<double> pmm,
147 : const std::array<T, 2>& target_points);
148 0 : T cos_theta;
149 : // cos(m*phi)
150 0 : DynamicBuffer<T> cos_m_phi;
151 : // sin(m*phi)
152 0 : DynamicBuffer<T> sin_m_phi;
153 : // pbar_factor[m] = Pbar(m,m)*sin(theta)^m
154 0 : DynamicBuffer<T> pbar_factor;
155 :
156 0 : size_t size() const { return num_points_; }
157 0 : size_t m_max() const { return m_max_; }
158 0 : size_t l_max() const { return l_max_; }
159 :
160 : private:
161 0 : size_t l_max_;
162 0 : size_t m_max_;
163 0 : size_t num_points_;
164 : };
165 :
166 : /// Here l_max and m_max are the largest fully-represented l and m in
167 : /// the Ylm expansion.
168 1 : Spherepack(size_t l_max, size_t m_max);
169 :
170 : /// The number of theta collocation points (Gauss-Legendre in
171 : /// \f$\cos\theta\f$) needed to represent all modes up to
172 : /// \f$l_{\max}\f$ exactly.
173 : ///
174 : /// Use to get the correct sizes of vectors of collocation points and
175 : /// spectral coefficients for a given l_max and m_max. Useful for allocating
176 : /// space without having to create a Spherepack.
177 1 : SPECTRE_ALWAYS_INLINE static constexpr size_t n_theta_points(
178 : const size_t l_max) {
179 : return l_max + 1;
180 : }
181 : /// The number of phi collocation points (uniform in \f$\phi\f$) needed
182 : /// to represent all modes up to \f$m_{\max}\f$ exactly.
183 : ///
184 : /// Use to get the correct sizes of vectors of collocation points and
185 : /// spectral coefficients for a given l_max and m_max. Useful for allocating
186 : /// space without having to create a Spherepack.
187 1 : SPECTRE_ALWAYS_INLINE static constexpr size_t n_phi_points(
188 : const size_t m_max) {
189 : return 2 * m_max + 1;
190 : }
191 : /// \brief The number of grid points used in the nodal (physical)
192 : /// representation.
193 : ///
194 : /// Use to get the correct sizes of vectors of collocation points and
195 : /// spectral coefficients for a given l_max and m_max. Useful for allocating
196 : /// space without having to create a Spherepack.
197 1 : SPECTRE_ALWAYS_INLINE static constexpr size_t physical_size(
198 : const size_t l_max, const size_t m_max) {
199 : return n_theta_points(l_max) * n_phi_points(m_max);
200 : }
201 : /// \brief The number of grid points used in the nodal (physical)
202 : /// representation.
203 : ///
204 : /// \note `spectral_size` is the size of the buffer that holds the
205 : /// coefficients; it is not the number of coefficients (which is
206 : /// \f$m_{\max}^2+(l_{\max}-m_{\max})(2m_{\max}+1)\f$).
207 : /// To simplify its internal indexing, SPHEREPACK uses a buffer with
208 : /// more space than necessary. See SpherepackIterator for
209 : /// how to index the coefficients in the buffer.
210 1 : SPECTRE_ALWAYS_INLINE static constexpr size_t spectral_size(
211 : const size_t l_max, const size_t m_max) {
212 : return 2 * (l_max + 1) * (m_max + 1);
213 : }
214 :
215 : /// @{
216 : /// Sizes in physical and spectral space for this instance.
217 1 : size_t l_max() const { return l_max_; }
218 1 : size_t m_max() const { return m_max_; }
219 1 : size_t physical_size() const { return n_theta_ * n_phi_; }
220 1 : size_t spectral_size() const { return spectral_size_; }
221 : /// @}
222 :
223 0 : std::array<size_t, 2> physical_extents() const {
224 : return {{n_theta_, n_phi_}};
225 : }
226 :
227 : /// @{
228 : /// Collocation points theta and phi.
229 : ///
230 : /// The phi points are uniform in phi, with the first point
231 : /// at phi=0.
232 : ///
233 : /// The theta points are Gauss-Legendre in \f$\cos(\theta)\f$,
234 : /// so there are no points at the poles.
235 1 : SPECTRE_ALWAYS_INLINE const std::vector<double>& theta_points() const {
236 : return storage_.theta;
237 : }
238 1 : SPECTRE_ALWAYS_INLINE const std::vector<double>& phi_points() const {
239 : return storage_.phi;
240 : }
241 1 : std::array<DataVector, 2> theta_phi_points() const;
242 : /// @}
243 :
244 : /// @{
245 : /// Spectral transformations.
246 : /// To act on a slice of the input and output arrays, specify strides
247 : /// and offsets.
248 1 : void phys_to_spec(gsl::not_null<double*> spectral_coefs,
249 : gsl::not_null<const double*> collocation_values,
250 : size_t physical_stride = 1, size_t physical_offset = 0,
251 : size_t spectral_stride = 1,
252 : size_t spectral_offset = 0) const {
253 : phys_to_spec_impl(spectral_coefs, collocation_values, physical_stride,
254 : physical_offset, spectral_stride, spectral_offset, false);
255 : }
256 1 : void spec_to_phys(gsl::not_null<double*> collocation_values,
257 : gsl::not_null<const double*> spectral_coefs,
258 : size_t spectral_stride = 1, size_t spectral_offset = 0,
259 : size_t physical_stride = 1,
260 : size_t physical_offset = 0) const {
261 : spec_to_phys_impl(collocation_values, spectral_coefs, spectral_stride,
262 : spectral_offset, physical_stride, physical_offset, false);
263 : };
264 : /// @}
265 :
266 : /// @{
267 : /// Spectral transformations where `collocation_values` and
268 : /// `spectral_coefs` are assumed to point to 3-dimensional arrays
269 : /// (I1 x S2 topology), and the transformations are done for all
270 : /// 'radial' points at once by internally looping over all values of
271 : /// the offset from zero to `stride`-1 (the physical and spectral
272 : /// strides are equal and are called `stride`).
273 1 : void phys_to_spec_all_offsets(gsl::not_null<double*> spectral_coefs,
274 : gsl::not_null<const double*> collocation_values,
275 : size_t stride) const {
276 : phys_to_spec_impl(spectral_coefs, collocation_values, stride, 0, stride, 0,
277 : true);
278 : }
279 1 : void spec_to_phys_all_offsets(gsl::not_null<double*> collocation_values,
280 : gsl::not_null<const double*> spectral_coefs,
281 : size_t stride) const {
282 : spec_to_phys_impl(collocation_values, spectral_coefs, stride, 0, stride, 0,
283 : true);
284 : };
285 : /// @}
286 :
287 : /// @{
288 : /// Simpler, less general interfaces to `phys_to_spec` and `spec_to_phys`.
289 : /// Acts on a slice of the input and returns a unit-stride result.
290 1 : DataVector phys_to_spec(const DataVector& collocation_values,
291 : size_t physical_stride = 1,
292 : size_t physical_offset = 0) const;
293 1 : DataVector spec_to_phys(const DataVector& spectral_coefs,
294 : size_t spectral_stride = 1,
295 : size_t spectral_offset = 0) const;
296 : /// @}
297 :
298 : /// @{
299 : /// Simpler, less general interfaces to `phys_to_spec_all_offsets`
300 : /// and `spec_to_phys_all_offsets`. Result has the same stride as
301 : /// the input.
302 1 : DataVector phys_to_spec_all_offsets(const DataVector& collocation_values,
303 : size_t stride) const;
304 1 : DataVector spec_to_phys_all_offsets(const DataVector& spectral_coefs,
305 : size_t stride) const;
306 : /// @}
307 :
308 : /// Computes Pfaffian derivative (df/dtheta, csc(theta) df/dphi) at
309 : /// the collocation values.
310 : /// To act on a slice of the input and output arrays, specify stride
311 : /// and offset (assumed to be the same for input and output).
312 1 : void gradient(const std::array<double*, 2>& df,
313 : gsl::not_null<const double*> collocation_values,
314 : size_t physical_stride = 1, size_t physical_offset = 0) const;
315 :
316 : /// Same as `gradient`, but takes the spectral coefficients (rather
317 : /// than collocation values) of the function. This is more
318 : /// efficient if one happens to already have the spectral
319 : /// coefficients.
320 : /// To act on a slice of the input and output arrays, specify strides
321 : /// and offsets.
322 1 : void gradient_from_coefs(const std::array<double*, 2>& df,
323 : gsl::not_null<const double*> spectral_coefs,
324 : size_t spectral_stride = 1,
325 : size_t spectral_offset = 0,
326 : size_t physical_stride = 1,
327 : size_t physical_offset = 0) const {
328 : gradient_from_coefs_impl(df, spectral_coefs, spectral_stride,
329 : spectral_offset, physical_stride, physical_offset,
330 : false);
331 : }
332 :
333 : /// @{
334 : /// Same as `gradient` but pointers are assumed to point to
335 : /// 3-dimensional arrays (I1 x S2 topology), and the gradient is
336 : /// done for all 'radial' points at once by internally looping
337 : /// over all values of the offset from zero to `stride`-1.
338 1 : void gradient_all_offsets(const std::array<double*, 2>& df,
339 : gsl::not_null<const double*> collocation_values,
340 : size_t stride = 1) const;
341 :
342 1 : SPECTRE_ALWAYS_INLINE void gradient_from_coefs_all_offsets(
343 : const std::array<double*, 2>& df,
344 : gsl::not_null<const double*> spectral_coefs, size_t stride = 1) const {
345 : gradient_from_coefs_impl(df, spectral_coefs, stride, 0, stride, 0, true);
346 : }
347 : /// @}
348 :
349 : /// @{
350 : /// Simpler, less general interfaces to `gradient`.
351 : /// Acts on a slice of the input and returns a unit-stride result.
352 1 : FirstDeriv gradient(const DataVector& collocation_values,
353 : size_t physical_stride = 1,
354 : size_t physical_offset = 0) const;
355 1 : FirstDeriv gradient_from_coefs(const DataVector& spectral_coefs,
356 : size_t spectral_stride = 1,
357 : size_t spectral_offset = 0) const;
358 : /// @}
359 :
360 : /// @{
361 : /// Simpler, less general interfaces to `gradient_all_offsets`.
362 : /// Result has the same stride as the input.
363 1 : FirstDeriv gradient_all_offsets(const DataVector& collocation_values,
364 : size_t stride = 1) const;
365 1 : FirstDeriv gradient_from_coefs_all_offsets(const DataVector& spectral_coefs,
366 : size_t stride = 1) const;
367 : /// @}
368 :
369 : /// Computes Laplacian in physical space.
370 : /// To act on a slice of the input and output arrays, specify stride
371 : /// and offset (assumed to be the same for input and output).
372 1 : void scalar_laplacian(gsl::not_null<double*> scalar_laplacian,
373 : gsl::not_null<const double*> collocation_values,
374 : size_t physical_stride = 1,
375 : size_t physical_offset = 0) const;
376 :
377 : /// Same as `scalar_laplacian` above, but the input is the spectral
378 : /// coefficients (rather than collocation values) of the function.
379 : /// This is more efficient if one happens to already have the
380 : /// spectral coefficients.
381 : /// To act on a slice of the input and output arrays, specify strides
382 : /// and offsets.
383 1 : void scalar_laplacian_from_coefs(gsl::not_null<double*> scalar_laplacian,
384 : gsl::not_null<const double*> spectral_coefs,
385 : size_t spectral_stride = 1,
386 : size_t spectral_offset = 0,
387 : size_t physical_stride = 1,
388 : size_t physical_offset = 0) const;
389 :
390 : /// @{
391 : /// Simpler, less general interfaces to `scalar_laplacian`.
392 : /// Acts on a slice of the input and returns a unit-stride result.
393 1 : DataVector scalar_laplacian(const DataVector& collocation_values,
394 : size_t physical_stride = 1,
395 : size_t physical_offset = 0) const;
396 1 : DataVector scalar_laplacian_from_coefs(const DataVector& spectral_coefs,
397 : size_t spectral_stride = 1,
398 : size_t spectral_offset = 0) const;
399 : /// @}
400 :
401 : /// Computes Pfaffian first and second derivative in physical space.
402 : /// The first derivative is \f$df(i) = d_i f\f$, and the
403 : /// second derivative is \f$ddf(i,j) = d_i (d_j f)\f$,
404 : /// where \f$d_0 = d/d\theta\f$ and \f$d_1 = csc(\theta) d/d\phi\f$.
405 : /// ddf is not symmetric.
406 : /// To act on a slice of the input and output arrays, specify stride
407 : /// and offset (assumed to be the same for input and output).
408 1 : void second_derivative(const std::array<double*, 2>& df,
409 : gsl::not_null<SecondDeriv*> ddf,
410 : gsl::not_null<const double*> collocation_values,
411 : size_t physical_stride = 1,
412 : size_t physical_offset = 0) const;
413 :
414 : /// Simpler, less general interface to second_derivative
415 1 : std::pair<FirstDeriv, SecondDeriv> first_and_second_derivative(
416 : const DataVector& collocation_values) const;
417 :
418 : /// Computes the integral over the sphere.
419 1 : SPECTRE_ALWAYS_INLINE double definite_integral(
420 : gsl::not_null<const double*> collocation_values,
421 : size_t physical_stride = 1, size_t physical_offset = 0) const {
422 : // clang-tidy: 'do not use pointer arithmetic'
423 : return ddot_(n_theta_ * n_phi_, storage_.quadrature_weights.data(), 1,
424 : collocation_values.get() + physical_offset, // NOLINT
425 : physical_stride);
426 : }
427 :
428 : /// Returns weights \f$w_i\f$ such that \f$sum_i (c_i w_i)\f$
429 : /// is the definite integral, where \f$c_i\f$ are collocation values
430 : /// at point i.
431 1 : SPECTRE_ALWAYS_INLINE const std::vector<double>& integration_weights() const {
432 : return storage_.quadrature_weights;
433 : }
434 :
435 : /// Adds a constant (i.e. \f$f(\theta,\phi)\f$ += \f$c\f$) to the function
436 : /// given by the spectral coefficients, by modifying the coefficients.
437 1 : SPECTRE_ALWAYS_INLINE static void add_constant(
438 : const gsl::not_null<DataVector*> spectral_coefs, const double c) {
439 : // The factor of sqrt(8) is because of the normalization of
440 : // SPHEREPACK's coefficients.
441 : (*spectral_coefs)[0] += sqrt(8.0) * c;
442 : }
443 :
444 : /// Returns the average of \f$f(\theta,\phi)\f$ over \f$(\theta,\phi)\f$.
445 1 : SPECTRE_ALWAYS_INLINE static double average(
446 : const DataVector& spectral_coefs) {
447 : // The factor of sqrt(8) is because of the normalization of
448 : // SPHEREPACK's coefficients. All other coefficients average to zero.
449 : return spectral_coefs[0] / sqrt(8.0);
450 : }
451 :
452 : /// Sets up the `InterpolationInfo` structure for interpolating onto
453 : /// a set of target \f$(\theta,\phi)\f$ points. Does not depend on
454 : /// the function being interpolated.
455 : template <typename T>
456 1 : InterpolationInfo<T> set_up_interpolation_info(
457 : const std::array<T, 2>& target_points) const;
458 :
459 : /// Interpolates from `collocation_values` onto the points that have
460 : /// been passed into the `set_up_interpolation_info` function.
461 : /// To interpolate a different function on the same spectral grid, there
462 : /// is no need to recompute `interpolation_info`.
463 : /// If you specify stride and offset, acts on a slice of the input values.
464 : /// The output has unit stride.
465 : template <typename T>
466 1 : void interpolate(gsl::not_null<T*> result,
467 : gsl::not_null<const double*> collocation_values,
468 : const InterpolationInfo<T>& interpolation_info,
469 : size_t physical_stride = 1,
470 : size_t physical_offset = 0) const;
471 :
472 : /// Same as `interpolate`, but assumes you have spectral coefficients.
473 : /// This is more efficient if you already have the spectral coefficients
474 : /// available.
475 : /// If you specify stride and offset, acts on a slice of the input coefs.
476 : /// The output has unit stride.
477 : template <typename T, typename R>
478 1 : void interpolate_from_coefs(gsl::not_null<T*> result, const R& spectral_coefs,
479 : const InterpolationInfo<T>& interpolation_info,
480 : size_t spectral_stride = 1,
481 : size_t spectral_offset = 0) const;
482 :
483 : /// Simpler interface to `interpolate`. If you need to call this
484 : /// repeatedly on different `spectral_coefs` or `collocation_values`
485 : /// for the same target points, this is inefficient; instead use
486 : /// `set_up_interpolation_info` and the functions that use
487 : /// `InterpolationInfo`.
488 : template <typename T>
489 1 : T interpolate(const DataVector& collocation_values,
490 : const std::array<T, 2>& target_points) const;
491 : template <typename T>
492 0 : T interpolate_from_coefs(const DataVector& spectral_coefs,
493 : const std::array<T, 2>& target_points) const;
494 :
495 : /// Takes spectral coefficients compatible with a resolution given by
496 : /// `l_max_coefs` and `m_max_coefs` and either prolongs them or restricts
497 : /// them to be compatible with a resolution given by `l_max_target` and
498 : /// `m_max_target`. This is done by truncation (restriction) or padding
499 : /// with zeros (prolongation).
500 : ///
501 : /// With `stride > 1`, `spectral_coefs` (and the result) hold `stride`
502 : /// independent sets of coefficients interleaved with the given stride, i.e.
503 : /// coefficient `c` of set `s` is at index `c * stride + s`. This is the
504 : /// layout produced by `phys_to_spec_all_offsets`, so several sets (e.g.
505 : /// radial points) can be prolonged or restricted in a single call.
506 1 : static DataVector prolong_or_restrict(const DataVector& spectral_coefs,
507 : size_t l_max_coefs, size_t m_max_coefs,
508 : size_t l_max_target,
509 : size_t m_max_target, size_t stride = 1);
510 :
511 : /// Same as the overload above, but writes into the pre-allocated `result`
512 : /// buffer (resizing it only if necessary) to avoid allocating. Useful to
513 : /// reuse a single buffer across repeated calls.
514 1 : static void prolong_or_restrict(gsl::not_null<DataVector*> result,
515 : const DataVector& spectral_coefs,
516 : size_t l_max_coefs, size_t m_max_coefs,
517 : size_t l_max_target, size_t m_max_target,
518 : size_t stride = 1);
519 :
520 : /// Takes spectral coefficients compatible with `*this`, and either
521 : /// prolongs them or restricts them to be compatible with `target`.
522 : /// This is done by truncation (restriction) or padding with zeros
523 : /// (prolongation).
524 1 : DataVector prolong_or_restrict(const DataVector& spectral_coefs,
525 : const Spherepack& target) const;
526 :
527 : private:
528 : // Spectral transformations and gradient.
529 : // If `loop_over_offset` is true, then `collocation_values` and
530 : // `spectral_coefs` are assumed to point to 3-dimensional
531 : // arrays (I1 x S2 topology), and the transformations are done for
532 : // all 'radial' points at once by looping over all values of the
533 : // offset from zero to stride-1. If `loop_over_offset` is true,
534 : // `physical_stride` must equal `spectral_stride`.
535 0 : void phys_to_spec_impl(gsl::not_null<double*> spectral_coefs,
536 : gsl::not_null<const double*> collocation_values,
537 : size_t physical_stride = 1, size_t physical_offset = 0,
538 : size_t spectral_stride = 1, size_t spectral_offset = 0,
539 : bool loop_over_offset = false) const;
540 0 : void spec_to_phys_impl(gsl::not_null<double*> collocation_values,
541 : gsl::not_null<const double*> spectral_coefs,
542 : size_t spectral_stride = 1, size_t spectral_offset = 0,
543 : size_t physical_stride = 1, size_t physical_offset = 0,
544 : bool loop_over_offset = false) const;
545 0 : void gradient_from_coefs_impl(const std::array<double*, 2>& df,
546 : gsl::not_null<const double*> spectral_coefs,
547 : size_t spectral_stride = 1,
548 : size_t spectral_offset = 0,
549 : size_t physical_stride = 1,
550 : size_t physical_offset = 0,
551 : bool loop_over_offset = false) const;
552 0 : void calculate_collocation_points();
553 0 : void calculate_interpolation_data();
554 0 : void fill_scalar_work_arrays();
555 0 : void fill_vector_work_arrays();
556 0 : size_t l_max_, m_max_, n_theta_, n_phi_;
557 0 : size_t spectral_size_;
558 : // memory_pool_ will be shared by multiple instances of
559 : // Spherepack on the same thread. Because these instances are on
560 : // the same thread, member functions of two or more of these
561 : // instances cannot be called simultaneously. Note that member
562 : // functions do not make any assumptions about the contents of
563 : // memory_pool_ on entry, so between calls to member functions it is
564 : // safe to resize objects in memory_pool_ or to overwrite them with
565 : // arbitrary data.
566 0 : static thread_local Spherepack_detail::MemoryPool memory_pool_;
567 0 : Spherepack_detail::ConstStorage storage_;
568 : }; // class Spherepack
569 :
570 0 : bool operator==(const Spherepack& lhs, const Spherepack& rhs);
571 0 : bool operator!=(const Spherepack& lhs, const Spherepack& rhs);
572 :
573 : } // namespace ylm
|