2 * Copyright Nick Thompson, John Maddock 2020
3 * Use, modification and distribution are subject to the
4 * Boost Software License, Version 1.0. (See accompanying file
5 * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 #ifndef BOOST_MATH_SPECIAL_DAUBECHIES_SCALING_HPP
9 #define BOOST_MATH_SPECIAL_DAUBECHIES_SCALING_HPP
16 #include <boost/math/special_functions/detail/daubechies_scaling_integer_grid.hpp>
17 #include <boost/math/filters/daubechies.hpp>
18 #include <boost/math/interpolators/detail/cubic_hermite_detail.hpp>
19 #include <boost/math/interpolators/detail/quintic_hermite_detail.hpp>
20 #include <boost/math/interpolators/detail/septic_hermite_detail.hpp>
22 namespace boost::math {
24 template<class Real, int p, int order>
25 std::vector<Real> daubechies_scaling_dyadic_grid(int64_t j_max)
29 auto c = boost::math::filters::daubechies_scaling_filter<Real, p>();
30 Real scale = sqrt(static_cast<Real>(2))*(1 << order);
36 auto phik = detail::daubechies_scaling_integer_grid<Real, p, order>();
38 // Maximum sensible j for 32 bit floats is j_max = 22:
39 if (std::is_same_v<Real, float>)
43 throw std::logic_error("Requested dyadic grid more dense than number of representables on the interval.");
46 std::vector<Real> v(2*p + (2*p-1)*((1<<j_max) -1), std::numeric_limits<Real>::quiet_NaN());
49 for (int64_t i = 0; i < (int64_t) phik.size(); ++i) {
50 v[i*(1uLL<<j_max)] = phik[i];
53 for (int64_t j = 1; j <= j_max; ++j)
55 int64_t k_max = v.size()/(int64_t(1) << (j_max-j));
56 for (int64_t k = 1; k < k_max; k += 2)
58 // Where this value will go:
59 int64_t delivery_idx = k*(1uLL << (j_max-j));
60 // This is a nice check, but we've tested this exhaustively, and it's an expensive check:
61 //if (delivery_idx >= (int64_t) v.size()) {
62 // std::cerr << "Delivery index out of range!\n";
66 for (int64_t l = 0; l < (int64_t) c.size(); ++l)
68 int64_t idx = k*(int64_t(1) << (j_max - j + 1)) - l*(int64_t(1) << j_max);
73 if (idx < (int64_t) v.size())
78 // Again, another nice check:
79 //if (!isnan(v[delivery_idx])) {
80 // std::cerr << "Delivery index already populated!, = " << v[delivery_idx] << "\n";
81 // std::cerr << "would overwrite with " << term << "\n";
83 v[delivery_idx] = term;
91 template<class RandomAccessContainer>
92 class matched_holder {
94 using Real = typename RandomAccessContainer::value_type;
96 matched_holder(RandomAccessContainer && y, RandomAccessContainer && dydx, int grid_refinements, Real x0) : x0_{x0}, y_{std::move(y)}, dy_{std::move(dydx)}
98 inv_h_ = (1 << grid_refinements);
100 for (auto & dy : dy_)
106 inline Real operator()(Real x) const
110 // This is the exact Holder exponent, but it's pessimistic almost everywhere!
111 // It's only exactly right at dyadic rationals.
112 //Real const alpha = 2 - log(1+sqrt(Real(3)))/log(Real(2));
113 // We're gonna use alpha = 1/2, rather than 0.5500...
114 Real s = (x-x0_)*inv_h_;
116 auto i = static_cast<decltype(y_.size())>(ii);
118 Real dphi = dy_[i+1];
119 Real diff = y_[i+1] - y_[i];
120 return y_[i] + (2*dphi - diff)*t + 2*sqrt(t)*(diff-dphi);
123 int64_t bytes() const
125 return 2*y_.size()*sizeof(Real) + sizeof(this);
131 RandomAccessContainer y_;
132 RandomAccessContainer dy_;
135 template<class RandomAccessContainer>
136 class matched_holder_aos {
138 using Point = typename RandomAccessContainer::value_type;
139 using Real = typename Point::value_type;
141 matched_holder_aos(RandomAccessContainer && data, int grid_refinements, Real x0) : x0_{x0}, data_{std::move(data)}
143 inv_h_ = Real(1uLL << grid_refinements);
145 for (auto & datum : data_)
151 inline Real operator()(Real x) const
155 Real s = (x-x0_)*inv_h_;
157 auto i = static_cast<decltype(data_.size())>(ii);
159 Real y0 = data_[i][0];
160 Real y1 = data_[i+1][0];
161 Real dphi = data_[i+1][1];
163 return y0 + (2*dphi - diff)*t + 2*sqrt(t)*(diff-dphi);
166 int64_t bytes() const
168 return data_.size()*data_[0].size()*sizeof(Real) + sizeof(this);
174 RandomAccessContainer data_;
178 template<class RandomAccessContainer>
179 class linear_interpolation {
181 using Real = typename RandomAccessContainer::value_type;
183 linear_interpolation(RandomAccessContainer && y, RandomAccessContainer && dydx, int grid_refinements) : y_{std::move(y)}, dydx_{std::move(dydx)}
185 s_ = (1 << grid_refinements);
188 inline Real operator()(Real x) const
194 int64_t kk = static_cast<int64_t>(k);
196 return (1-t)*y_[kk] + t*y_[kk+1];
199 inline Real prime(Real x) const
205 int64_t kk = static_cast<int64_t>(k);
207 return (1-t)*dydx_[kk] + t*dydx_[kk+1];
210 int64_t bytes() const
212 return (1 + y_.size() + dydx_.size())*sizeof(Real) + sizeof(y_) + sizeof(dydx_);
217 RandomAccessContainer y_;
218 RandomAccessContainer dydx_;
221 template<class RandomAccessContainer>
222 class linear_interpolation_aos {
224 using Point = typename RandomAccessContainer::value_type;
225 using Real = typename Point::value_type;
227 linear_interpolation_aos(RandomAccessContainer && data, int grid_refinements, Real x0) : x0_{x0}, data_{std::move(data)}
229 s_ = Real(1uLL << grid_refinements);
232 inline Real operator()(Real x) const
238 int64_t kk = static_cast<int64_t>(k);
240 return (t != 0) ? (1-t)*data_[kk][0] + t*data_[kk+1][0] : data_[kk][0];
243 inline Real prime(Real x) const
249 int64_t kk = static_cast<int64_t>(k);
251 return t != 0 ? (1-t)*data_[kk][1] + t*data_[kk+1][1] : data_[kk][1];
254 int64_t bytes() const
256 return sizeof(this) + data_.size()*data_[0].size()*sizeof(Real);
262 RandomAccessContainer data_;
267 struct daubechies_eval_type
271 static const std::vector<T>& vector_cast(const std::vector<T>& v) { return v; }
275 struct daubechies_eval_type<float>
279 inline static std::vector<float> vector_cast(const std::vector<double>& v)
281 std::vector<float> result(v.size());
282 for (unsigned i = 0; i < v.size(); ++i)
283 result[i] = static_cast<float>(v[i]);
288 struct daubechies_eval_type<double>
290 typedef long double type;
292 inline static std::vector<double> vector_cast(const std::vector<long double>& v)
294 std::vector<double> result(v.size());
295 for (unsigned i = 0; i < v.size(); ++i)
296 result[i] = static_cast<double>(v[i]);
301 struct null_interpolator
304 T operator()(const T&)
310 } // namespace detail
312 template<class Real, int p>
313 class daubechies_scaling {
315 // Some type manipulation so we know the type of the interpolator, and the vector type it requires:
317 typedef std::vector<std::array<Real, p < 6 ? 2 : p < 10 ? 3 : 4>> vector_type;
319 // List our interpolators:
322 detail::null_interpolator, detail::matched_holder_aos<vector_type>, detail::linear_interpolation_aos<vector_type>,
323 interpolators::detail::cardinal_cubic_hermite_detail_aos<vector_type>, interpolators::detail::cardinal_quintic_hermite_detail_aos<vector_type>,
324 interpolators::detail::cardinal_septic_hermite_detail_aos<vector_type> > interpolator_list;
326 // Select the one we need:
328 typedef std::tuple_element_t<
333 p <= 9 ? 4 : 5, interpolator_list> interpolator_type;
336 daubechies_scaling(int grid_refinements = -1)
338 static_assert(p < 20, "Daubechies scaling functions are only implemented for p < 20.");
339 static_assert(p > 0, "Daubechies scaling functions must have at least 1 vanishing moment.");
340 if constexpr (p == 1)
345 if (grid_refinements < 0)
347 if (std::is_same_v<Real, float>)
349 if (grid_refinements == -2)
351 // Control absolute error:
352 // p= 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
353 std::array<int, 20> r{ -1, -1, 18, 19, 16, 11, 8, 7, 7, 7, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3 };
354 grid_refinements = r[p];
358 // Control relative error:
359 // p= 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
360 std::array<int, 20> r{ -1, -1, 21, 21, 21, 17, 16, 15, 14, 13, 12, 11, 11, 11, 11, 11, 11, 11, 11, 11 };
361 grid_refinements = r[p];
364 else if (std::is_same_v<Real, double>)
366 // p= 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
367 std::array<int, 20> r{ -1, -1, 21, 21, 21, 21, 21, 21, 21, 21, 20, 20, 19, 19, 18, 18, 18, 18, 18, 18 };
368 grid_refinements = r[p];
372 grid_refinements = 21;
376 // Compute the refined grid:
377 // In fact for float precision I know the grid must be computed in double precision and then cast back down, or else parts of the support are systematically inaccurate.
378 std::future<std::vector<Real>> t0 = std::async(std::launch::async, [&grid_refinements]() {
379 // Computing in higher precision and downcasting is essential for 1ULP evaluation in float precision:
380 auto v = daubechies_scaling_dyadic_grid<typename detail::daubechies_eval_type<Real>::type, p, 0>(grid_refinements);
381 return detail::daubechies_eval_type<Real>::vector_cast(v);
383 // Compute the derivative of the refined grid:
384 std::future<std::vector<Real>> t1 = std::async(std::launch::async, [&grid_refinements]() {
385 auto v = daubechies_scaling_dyadic_grid<typename detail::daubechies_eval_type<Real>::type, p, 1>(grid_refinements);
386 return detail::daubechies_eval_type<Real>::vector_cast(v);
389 // if necessary, compute the second and third derivative:
390 std::vector<Real> d2ydx2;
391 std::vector<Real> d3ydx3;
392 if constexpr (p >= 6) {
393 std::future<std::vector<Real>> t3 = std::async(std::launch::async, [&grid_refinements]() {
394 auto v = daubechies_scaling_dyadic_grid<typename detail::daubechies_eval_type<Real>::type, p, 2>(grid_refinements);
395 return detail::daubechies_eval_type<Real>::vector_cast(v);
398 if constexpr (p >= 10) {
399 std::future<std::vector<Real>> t4 = std::async(std::launch::async, [&grid_refinements]() {
400 auto v = daubechies_scaling_dyadic_grid<typename detail::daubechies_eval_type<Real>::type, p, 3>(grid_refinements);
401 return detail::daubechies_eval_type<Real>::vector_cast(v);
410 auto dydx = t1.get();
412 if constexpr (p >= 2)
414 vector_type data(y.size());
415 for (size_t i = 0; i < y.size(); ++i)
418 data[i][1] = dydx[i];
419 if constexpr (p >= 6)
420 data[i][2] = d2ydx2[i];
421 if constexpr (p >= 10)
422 data[i][3] = d3ydx3[i];
424 if constexpr (p <= 3)
425 m_interpolator = std::make_shared<interpolator_type>(std::move(data), grid_refinements, Real(0));
427 m_interpolator = std::make_shared<interpolator_type>(std::move(data), Real(0), Real(1) / (1 << grid_refinements));
430 m_interpolator = std::make_shared<detail::null_interpolator>();
434 inline Real operator()(Real x) const
436 if (x <= 0 || x >= 2*p-1)
440 return (*m_interpolator)(x);
443 inline Real prime(Real x) const
445 static_assert(p > 2, "The 3-vanishing moment Daubechies scaling function is the first which is continuously differentiable.");
446 if (x <= 0 || x >= 2*p-1)
450 return m_interpolator->prime(x);
453 inline Real double_prime(Real x) const
455 static_assert(p >= 6, "Second derivatives require at least 6 vanishing moments.");
456 if (x <= 0 || x >= 2*p - 1)
460 return m_interpolator->double_prime(x);
463 std::pair<Real, Real> support() const
465 return {Real(0), Real(2*p-1)};
468 int64_t bytes() const
470 return m_interpolator->bytes() + sizeof(m_interpolator);
474 std::shared_ptr<interpolator_type> m_interpolator;