// 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 #include #include #include #include #include #include #include #include #include #include #ifdef BOOST_HAS_FLOAT128 #include 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 void test_evaluation_symmetry() { std::cout << "Testing evaluation symmetry on the " << p << " vanishing moment scaling function.\n"; auto phi = fourier_transform_daubechies_scaling(0.0); CHECK_ULP_CLOSE(one_div_root_two_pi(), phi.real(), 3); CHECK_ULP_CLOSE(static_cast(0), phi.imag(), 3); Real domega = Real(1)/128; for (Real omega = domega; omega < 10; omega += domega) { auto phi1 = fourier_transform_daubechies_scaling(-omega); auto phi2 = fourier_transform_daubechies_scaling(omega); CHECK_ULP_CLOSE(phi1.real(), phi2.real(), 3); CHECK_ULP_CLOSE(phi1.imag(), -phi2.imag(), 3); auto psi1 = fourier_transform_daubechies_wavelet(-omega); auto psi2 = fourier_transform_daubechies_wavelet(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::max()); omega *= 10) { auto phi1 = fourier_transform_daubechies_scaling(-omega); auto phi2 = fourier_transform_daubechies_scaling(omega); CHECK_ULP_CLOSE(phi1.real(), phi2.real(), 3); CHECK_ULP_CLOSE(phi1.imag(), -phi2.imag(), 3); } return; } template 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 residual = std::norm(fourier_transform_daubechies_scaling(omega)); CHECK_LE(residual, std::numeric_limits::epsilon()*std::numeric_limits::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 residual = std::norm(fourier_transform_daubechies_wavelet(omega)); CHECK_LE(residual, std::numeric_limits::epsilon()*std::numeric_limits::epsilon()); } } template 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(); 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(0, -omega*t))*one_div_root_two_pi(); }; auto expected = trapezoidal(f, tmin, tmax, 2*std::numeric_limits::epsilon()); auto computed = fourier_transform_daubechies_scaling(static_cast(omega)); CHECK_MOLLIFIED_CLOSE(static_cast(expected.real()), computed.real(), 1e-4); CHECK_MOLLIFIED_CLOSE(static_cast(expected.imag()), computed.imag(), 1e-4); } } template 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(); 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(0, -omega*t))*one_div_root_two_pi(); }; auto expected = trapezoidal(f, tmin, tmax, std::numeric_limits::epsilon()); auto computed = fourier_transform_daubechies_wavelet(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 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(omega)); auto sum = summation_condition_number(term); int64_t l = 1; while (l < 10000 && term > 2*std::numeric_limits::epsilon()) { Real tpl = std::norm(fourier_transform_daubechies_scaling(omega + two_pi()*l)); Real tml = std::norm(fourier_transform_daubechies_scaling(omega - two_pi()*l)); sum += tpl; sum += tml; ++l; } // With arg promotion, I can get this to 13 ULPS: if (!CHECK_ULP_CLOSE(1/two_pi(), sum.sum(), 125)) { std::cerr << " Failure with occurs on " << p << " vanishing moments.\n"; } } } // Tests Daubechies "Ten Lectures on Wavelets", equation 5.1.38: template 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(omega)); Real psi_omega_sq = std::norm(fourier_transform_daubechies_wavelet(omega)); Real phi_half_omega_sq = std::norm(fourier_transform_daubechies_scaling(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(); test_roots(); test_roots(); test_roots(); test_roots(); test_roots(); test_roots(); test_roots(); test_roots(); test_roots(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); test_evaluation_symmetry(); // Slow tests: test_scaling_quadrature<9>(); test_scaling_quadrature<10>(); // This one converges really slowly: //test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_19(); test_ten_lectures_eq_5_1_38(); test_ten_lectures_eq_5_1_38(); test_ten_lectures_eq_5_1_38(); test_ten_lectures_eq_5_1_38(); test_wavelet_quadrature<9>(); return boost::math::test::report_errors(); }