2
0
mirror of https://github.com/boostorg/math.git synced 2026-01-19 04:22:09 +00:00
Files
math/test/fourier_transform_daubechies_test.cpp
Nick 7887d43f83 Numerical evaluation of Fourier transform of Daubechies scaling funct… (#921)
* Numerical evaluation of Fourier transform of Daubechies scaling functions.

* Update example/calculate_fourier_transform_daubechies_constants.cpp

Co-authored-by: Matt Borland <matt@mattborland.com>

* Update example/fourier_transform_daubechies_ulp_plot.cpp

Co-authored-by: Matt Borland <matt@mattborland.com>

* Update include/boost/math/special_functions/fourier_transform_daubechies_scaling.hpp

Co-authored-by: Matt Borland <matt@mattborland.com>

* Update include/boost/math/special_functions/fourier_transform_daubechies_scaling.hpp

Co-authored-by: Matt Borland <matt@mattborland.com>

* Rename include file to reflect it implements both the scaling and wavelet.

* Add performance to docs.

* Update test/math_unit_test.hpp

Co-authored-by: Matt Borland <matt@mattborland.com>

* Add boost-no-inspect to files with non-ASCII characters.

---------

Co-authored-by: Matt Borland <matt@mattborland.com>
2023-06-13 08:05:00 -07:00

221 lines
9.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// boost-no-inspect
/*
* Copyright Nick Thompson, 2023
* Use, modification and distribution are subject to the
* Boost Software License, Version 1.0. (See accompanying file
* LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#include "math_unit_test.hpp"
#include <numeric>
#include <utility>
#include <iomanip>
#include <iostream>
#include <random>
#include <boost/math/tools/condition_numbers.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/quadrature/trapezoidal.hpp>
#include <boost/math/special_functions/daubechies_scaling.hpp>
#include <boost/math/special_functions/daubechies_wavelet.hpp>
#include <boost/math/special_functions/fourier_transform_daubechies.hpp>
#ifdef BOOST_HAS_FLOAT128
#include <boost/multiprecision/float128.hpp>
using boost::multiprecision::float128;
#endif
using boost::math::fourier_transform_daubechies_scaling;
using boost::math::fourier_transform_daubechies_wavelet;
using boost::math::tools::summation_condition_number;
using boost::math::constants::two_pi;
using boost::math::constants::pi;
using boost::math::constants::one_div_root_two_pi;
using boost::math::quadrature::trapezoidal;
// 𝓕[φ](-ω) = 𝓕[φ](ω)*
template<typename Real, int p>
void test_evaluation_symmetry() {
std::cout << "Testing evaluation symmetry on the " << p << " vanishing moment scaling function.\n";
auto phi = fourier_transform_daubechies_scaling<Real, p>(0.0);
CHECK_ULP_CLOSE(one_div_root_two_pi<Real>(), phi.real(), 3);
CHECK_ULP_CLOSE(static_cast<Real>(0), phi.imag(), 3);
Real domega = Real(1)/128;
for (Real omega = domega; omega < 10; omega += domega) {
auto phi1 = fourier_transform_daubechies_scaling<Real, p>(-omega);
auto phi2 = fourier_transform_daubechies_scaling<Real, p>(omega);
CHECK_ULP_CLOSE(phi1.real(), phi2.real(), 3);
CHECK_ULP_CLOSE(phi1.imag(), -phi2.imag(), 3);
auto psi1 = fourier_transform_daubechies_wavelet<Real, p>(-omega);
auto psi2 = fourier_transform_daubechies_wavelet<Real, p>(omega);
CHECK_ULP_CLOSE(psi1.real(), psi2.real(), 3);
CHECK_ULP_CLOSE(psi1.imag(), -psi2.imag(), 3);
}
for (Real omega = 10; omega < std::cbrt(std::numeric_limits<Real>::max()); omega *= 10) {
auto phi1 = fourier_transform_daubechies_scaling<Real, p>(-omega);
auto phi2 = fourier_transform_daubechies_scaling<Real, p>(omega);
CHECK_ULP_CLOSE(phi1.real(), phi2.real(), 3);
CHECK_ULP_CLOSE(phi1.imag(), -phi2.imag(), 3);
}
return;
}
template<typename Real, int p>
void test_roots() {
std::cout << "Testing roots on the " << p << " vanishing moment scaling function.\n";
for (long n = 1; n < 100; ++n) {
// All arguments of the form 2πn are roots of the complex function:
// See Daubechies, 10 Lectures on Wavelets, Section 6.2:
Real omega = n*two_pi<Real>();
Real residual = std::norm(fourier_transform_daubechies_scaling<Real, p>(omega));
CHECK_LE(residual, std::numeric_limits<Real>::epsilon()*std::numeric_limits<Real>::epsilon());
}
std::cout << "Testing roots on the " << p << " vanishing moment wavelet.\n";
// If ωₙ is a root of the 𝓕[𝜙], then 2ωₙ is a root of 𝓕[ψ].
// In addition, m₀(π) = 0, and m₀ is 2π periodic.
// Recalling 𝓕[ψ](ω) = exp(iω/2)m₀(ω/2 + π)^{*}𝓕[𝜙](ω/2)*phase,
// ω=4nπ are also roots:
for (long n = 0; n < 100; ++n) {
Real omega = 4*n*pi<Real>();
Real residual = std::norm(fourier_transform_daubechies_wavelet<Real, p>(omega));
CHECK_LE(residual, std::numeric_limits<Real>::epsilon()*std::numeric_limits<Real>::epsilon());
}
}
template<int p>
void test_scaling_quadrature() {
std::cout << "Testing numerical quadrature of the scaling function with " << p << " vanishing moments matches numerical evaluation.\n";
auto phi = boost::math::daubechies_scaling<double, p>();
auto [tmin, tmax] = phi.support();
double domega = 1/8.0;
for (double omega = domega; omega < 10; omega += domega) {
// I suspect the quadrature is less accurate than special function evaluation, so this is just a sanity check:
auto f = [&](double t) {
return phi(t)*std::exp(std::complex<double>(0, -omega*t))*one_div_root_two_pi<double>();
};
auto expected = trapezoidal(f, tmin, tmax, 2*std::numeric_limits<double>::epsilon());
auto computed = fourier_transform_daubechies_scaling<float, p>(static_cast<float>(omega));
CHECK_MOLLIFIED_CLOSE(static_cast<float>(expected.real()), computed.real(), 1e-4);
CHECK_MOLLIFIED_CLOSE(static_cast<float>(expected.imag()), computed.imag(), 1e-4);
}
}
template<int p>
void test_wavelet_quadrature() {
std::cout << "Testing numerical quadrature of the wavelet with " << p << " vanishing moments matches numerical evaluation.\n";
auto psi = boost::math::daubechies_wavelet<double, p>();
auto [tmin, tmax] = psi.support();
double domega = 1/8.0;
// There is a root at at ω=0, so skip this one because we can't recover the phase of a root.
for (double omega = 2*domega; omega < 10; omega += domega) {
// I suspect the quadrature is less accurate than special function evaluation, so this is just a sanity check:
auto f = [&](double t) {
return psi(t)*std::exp(std::complex<double>(0, -omega*t))*one_div_root_two_pi<double>();
};
auto expected = trapezoidal(f, tmin, tmax, std::numeric_limits<double>::epsilon());
auto computed = fourier_transform_daubechies_wavelet<double, p>(omega);
if(!CHECK_ABSOLUTE_ERROR(std::abs(expected), std::abs(computed), 1e-9)) {
std::cerr << " |𝓕[ψ](" << omega << ")| is incorrect.\n";
}
// Again, lots of evidence that the quadrature is less accurate than what we've implemented.
// Graph of the quadrature phase is super janky; graph of the implementation phase is pretty good.
if(!CHECK_ABSOLUTE_ERROR(std::arg(expected), std::arg(computed), 1e-2)) {
std::cerr << " arg(𝓕[ψ](" << omega << ")) is incorrect.\n";
}
}
}
// Tests Daubechies "Ten Lectures on Wavelets", equation 5.1.19:
template<typename Real, int p>
void test_ten_lectures_eq_5_1_19() {
std::cout << "Testing Ten Lectures equation 5.1.19 on " << p << " vanishing moments.\n";
Real domega = Real(1)/Real(16);
for (Real omega = 0; omega < 1; omega += domega) {
Real term = std::norm(fourier_transform_daubechies_scaling<Real, p>(omega));
auto sum = summation_condition_number<Real>(term);
int64_t l = 1;
while (l < 10000 && term > 2*std::numeric_limits<Real>::epsilon()) {
Real tpl = std::norm(fourier_transform_daubechies_scaling<Real, p>(omega + two_pi<Real>()*l));
Real tml = std::norm(fourier_transform_daubechies_scaling<Real, p>(omega - two_pi<Real>()*l));
sum += tpl;
sum += tml;
Real term = tpl + tml;
++l;
}
// With arg promotion, I can get this to 13 ULPS:
if (!CHECK_ULP_CLOSE(1/two_pi<Real>(), sum.sum(), 125)) {
std::cerr << " Failure with occurs on " << p << " vanishing moments.\n";
}
}
}
// Tests Daubechies "Ten Lectures on Wavelets", equation 5.1.38:
template<typename Real, int p>
void test_ten_lectures_eq_5_1_38() {
std::cout << "Testing Ten Lectures equation 5.1.38 on " << p << " vanishing moments.\n";
Real domega = Real(1)/Real(16);
for (Real omega = 0; omega < 1; omega += domega) {
Real phi_omega_sq = std::norm(fourier_transform_daubechies_scaling<Real, p>(omega));
Real psi_omega_sq = std::norm(fourier_transform_daubechies_wavelet<Real, p>(omega));
Real phi_half_omega_sq = std::norm(fourier_transform_daubechies_scaling<Real, p>(omega/2));
if (!CHECK_ULP_CLOSE(phi_half_omega_sq, phi_omega_sq + psi_omega_sq, 125)) {
std::cerr << " Failure with occurs on " << p << " vanishing moments at omega = " << omega << "\n";
}
}
}
int main()
{
test_roots<float, 1>();
test_roots<float, 2>();
test_roots<float, 3>();
test_roots<float, 4>();
test_roots<float, 5>();
test_roots<float, 6>();
test_roots<float, 7>();
test_roots<float, 8>();
test_roots<float, 9>();
test_roots<float, 10>();
test_evaluation_symmetry<float, 1>();
test_evaluation_symmetry<float, 2>();
test_evaluation_symmetry<float, 3>();
test_evaluation_symmetry<float, 4>();
test_evaluation_symmetry<float, 5>();
test_evaluation_symmetry<float, 6>();
test_evaluation_symmetry<float, 7>();
test_evaluation_symmetry<float, 8>();
test_evaluation_symmetry<float, 9>();
test_evaluation_symmetry<float, 10>();
// Slow tests:
test_scaling_quadrature<9>();
test_scaling_quadrature<10>();
// This one converges really slowly:
//test_ten_lectures_eq_5_1_19<float, 1>();
test_ten_lectures_eq_5_1_19<float, 2>();
test_ten_lectures_eq_5_1_19<float, 3>();
test_ten_lectures_eq_5_1_19<float, 4>();
test_ten_lectures_eq_5_1_19<float, 5>();
test_ten_lectures_eq_5_1_19<float, 6>();
test_ten_lectures_eq_5_1_19<float, 7>();
test_ten_lectures_eq_5_1_19<float, 8>();
test_ten_lectures_eq_5_1_19<float, 9>();
test_ten_lectures_eq_5_1_19<float, 10>();
test_ten_lectures_eq_5_1_38<float, 3>();
test_ten_lectures_eq_5_1_38<float, 4>();
test_ten_lectures_eq_5_1_38<float, 5>();
test_ten_lectures_eq_5_1_38<float, 6>();
test_wavelet_quadrature<9>();
return boost::math::test::report_errors();
}