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 <boost/math/interpolators/cardinal_cubic_b_spline.hpp> 8 : #include <cstddef> 9 : #include <optional> 10 : #include <utility> 11 : #include <vector> 12 : 13 : /// \cond 14 : namespace PUP { 15 : class er; 16 : } // namespace PUP 17 : /// \endcond 18 : 19 : namespace intrp { 20 : 21 : /*! 22 : * \ingroup NumericalAlgorithmsGroup 23 : * \brief A cubic B-spline interpolant of uniformly spaced samples 24 : * 25 : * Interpolates samples \f$f_i = f(t_0 + i \Delta t)\f$, \f$i = 0, \ldots, 26 : * N-1\f$, with a cubic B-spline. The interpolation error decreases as 27 : * \f$\mathcal{O}(\Delta t^4)\f$ for smooth data. This class wraps 28 : * `boost::math::interpolators::cardinal_cubic_b_spline` and adds: 29 : * 30 : * - Serialization: the samples, start time, and time step fully determine the 31 : * interpolant, so it can be sent in a `pup` and rebuilt on the receiving 32 : * side. 33 : * - Clamped evaluation: evaluation is clamped to the bounds of the sampled 34 : * interval, so times that fall outside the interval by roundoff evaluate 35 : * to the boundary values instead of extrapolating the spline. Times 36 : * outside the bounds beyond roundoff trigger an `ASSERT` in debug builds. 37 : * 38 : * Here is an example how to use this class: 39 : * 40 : * \snippet Test_UniformCardinalBSpline.cpp uniform_cardinal_b_spline_example 41 : * 42 : * \note Requires Boost 1.81 or newer at runtime because of an accuracy fix 43 : * for the estimate of the derivative at the right endpoint in the Boost 44 : * implementation, see 45 : * https://github.com/boostorg/math/commit/4809e714d4806c07da3a3def0c4550daa0529b8d. 46 : * The constructor raises an error for older Boost versions. 47 : */ 48 1 : class UniformCardinalBSpline { 49 : public: 50 : /*! 51 : * \brief Construct from uniformly spaced samples. 52 : * 53 : * \param values The sampled function values \f$f(t_0 + i \Delta t)\f$. At 54 : * least 5 samples are required. 55 : * \param start_time The time \f$t_0\f$ of the first sample. 56 : * \param time_step The (positive) spacing \f$\Delta t\f$ between samples. 57 : */ 58 1 : UniformCardinalBSpline(std::vector<double> values, double start_time, 59 : double time_step); 60 : 61 0 : UniformCardinalBSpline() = default; 62 : 63 : /// Evaluate the interpolant at `time`, clamped to the `bounds()` 64 1 : double operator()(double time) const; 65 : 66 : /// The sampled function values 67 1 : const std::vector<double>& values() const { return values_; } 68 : 69 : /// The time of the first sample 70 1 : double start_time() const { return start_time_; } 71 : 72 : /// The spacing between samples 73 1 : double time_step() const { return time_step_; } 74 : 75 : /// The first and last sample time 76 1 : std::array<double, 2> bounds() const; 77 : 78 : // NOLINTNEXTLINE(google-runtime-references) 79 0 : void pup(PUP::er& p); 80 : 81 : private: 82 0 : void initialize_interpolant(); 83 : 84 0 : std::vector<double> values_{}; 85 0 : double start_time_{}; 86 0 : double time_step_{}; 87 : std::optional<boost::math::interpolators::cardinal_cubic_b_spline<double>> 88 0 : interpolant_{}; 89 : }; 90 : 91 0 : bool operator==(const UniformCardinalBSpline& lhs, 92 : const UniformCardinalBSpline& rhs); 93 : 94 0 : bool operator!=(const UniformCardinalBSpline& lhs, 95 : const UniformCardinalBSpline& rhs); 96 : 97 : /*! 98 : * \ingroup NumericalAlgorithmsGroup 99 : * \brief Estimate the maximum interpolation error of a 100 : * `intrp::UniformCardinalBSpline` through the given samples. 101 : * 102 : * Measures the deviation of an interpolant through every other sample from 103 : * the sampled values at the sample times, where the error of that coarser 104 : * interpolant peaks. For smooth data the interpolation error of a cubic 105 : * spline decreases by a factor of 16 when the step size is halved, so the 106 : * error of the interpolant through all samples is about 1/16 of the measured 107 : * deviation for smooth data in the convergent regime. The deviation is divided 108 : * by only 8 to obtain a conservative estimate, accounting for nonsmooth data 109 : * that converges slower. 110 : * 111 : * \param values The sampled function values. At least 9 samples are required 112 : * so the coarser interpolant has at least 5. 113 : * \param start_time The time of the first sample. 114 : * \param time_step The (positive) spacing between samples. 115 : */ 116 1 : double estimate_interpolation_error(const std::vector<double>& values, 117 : double start_time, double time_step); 118 : 119 : /*! 120 : * \ingroup NumericalAlgorithmsGroup 121 : * \brief Compress uniformly spaced samples into a 122 : * `intrp::UniformCardinalBSpline` with fewer points that reproduces the 123 : * samples to the given tolerance. 124 : * 125 : * Builds a reference interpolant through all samples and resamples it on 126 : * coarser uniform grids over the same time interval, starting with 6 points 127 : * and doubling until the resampled interpolant reproduces the input samples 128 : * to within `absolute_tolerance` at the sample times. This reduces memory 129 : * for smooth, densely sampled data, e.g. time series of spectral modes in a 130 : * simulation. 131 : * 132 : * \returns The compressed interpolant and its maximum absolute deviation 133 : * from the input samples at the sample times. 134 : * 135 : * If the tolerance cannot be met with fewer points than the input, the 136 : * returned interpolant holds the original samples. Its deviation from the 137 : * samples at the sample times vanishes by construction, so the returned 138 : * error is the error of the last (coarser) candidate in the doubling search 139 : * as a conservative estimate, and may exceed the tolerance. Inputs with 6 or 140 : * fewer samples are returned unchanged with zero error. 141 : * 142 : * \param values The sampled function values. At least 5 samples are required. 143 : * \param start_time The time of the first sample. 144 : * \param time_step The (positive) spacing between samples. 145 : * \param absolute_tolerance Maximum allowed absolute deviation of the 146 : * compressed interpolant from the input samples. 147 : */ 148 1 : std::pair<UniformCardinalBSpline, double> compress_to_tolerance( 149 : const std::vector<double>& values, double start_time, double time_step, 150 : double absolute_tolerance); 151 : 152 : } // namespace intrp