Skip to content

Commit

Permalink
Numerical evaluation of Fourier transform of Daubechies scaling funct…
Browse files Browse the repository at this point in the history
…ions. [ci skip]
  • Loading branch information
NAThompson committed Jan 21, 2023
1 parent e2c989c commit d793bbc
Show file tree
Hide file tree
Showing 8 changed files with 474 additions and 1 deletion.
17 changes: 17 additions & 0 deletions doc/sf/daubechies.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ The 2 vanishing moment scaling function.
[$../graphs/daubechies_8_scaling.svg]
The 8 vanishing moment scaling function.

Boost.Math also provides numerical evaluation of the Fourier transform of these functions.
This is useful in sparse recovery problems where the measurements are taken in the Fourier basis.
The usage is exhibited below:

#include <boost/math/special_functions/fourier_transform_daubechies_scaling.hpp>
using boost::math::fourier_transform_daubechies_scaling;
// Evaluate the Fourier transform of the 4-vanishing moment Daubechies scaling function at ω=1.8:
std::complex<float> hat_phi = fourier_transform_daubechies_scaling<float, 4>(1.8f);

The Fourier transform convention is unitary with the sign of i being given in Daubechies Ten Lectures.
In particular, this means that `fourier_transform_daubechies_scaling<float, p>(0.0)` returns 1/sqrt(2π).

The implementation computes an infinite product of trigonometric polynomials as can be found from recursive application of the identity 𝓕[φ](ω) = m(ω/2)𝓕[φ](ω/2).
This is neither particularly fast nor accurate, but there appears to be no literature on this extremely useful topic, and hence the naive method must suffice.



[heading References]

* Daubechies, Ingrid. ['Ten Lectures on Wavelets.] Vol. 61. Siam, 1992.
Expand Down
38 changes: 38 additions & 0 deletions example/calculate_fourier_transform_daubechies_constants.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <utility>
#include <boost/math/filters/daubechies.hpp>
#include <boost/math/tools/polynomial.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/math/constants/constants.hpp>

using std::pow;
using boost::multiprecision::cpp_bin_float_100;
using boost::math::filters::daubechies_scaling_filter;
using boost::math::tools::polynomial;
using boost::math::constants::half;
using boost::math::constants::root_two;

template<typename Real, size_t N>
std::vector<Real> get_constants() {
auto h = daubechies_scaling_filter<cpp_bin_float_100, N>();
auto p = polynomial<cpp_bin_float_100>(h.begin(), h.end());

auto q = polynomial({half<cpp_bin_float_100>(), half<cpp_bin_float_100>()});
q = pow(q, N);
auto l = p/q;
return l.data();
}

template<typename Real>
void print_constants(std::vector<Real> const & l) {
std::cout << std::setprecision(std::numeric_limits<Real>::digits10 -10);
std::cout << "return std::array<Real, " << l.size() << ">{";
for (size_t i = 0; i < l.size() - 1; ++i) {
std::cout << "BOOST_MATH_BIG_CONSTANT(Real, std::numeric_limits<Real>::digits, " << l[i]/root_two<Real>() << "), ";
}
std::cout << "BOOST_MATH_BIG_CONSTANT(Real, std::numeric_limits<Real>::digits, " << l.back()/root_two<Real>() << ")};\n";
}

int main() {
auto constants = get_constants<cpp_bin_float_100, 10>();
print_constants(constants);
}
25 changes: 25 additions & 0 deletions example/fourier_transform_daubechies_ulp_plot.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <boost/math/special_functions/fourier_transform_daubechies_scaling.hpp>
#include <boost/math/tools/ulps_plot.hpp>

using boost::math::fourier_transform_daubechies_scaling;
using boost::math::tools::ulps_plot;

int main() {

auto phi_real_hi_acc = [](double omega) {
auto z = fourier_transform_daubechies_scaling<double, 6>(omega);
return z.real();
};

auto phi_real_lo_acc = [](float omega) {
auto z = fourier_transform_daubechies_scaling<float, 6>(omega);
return z.real();
};
auto plot = ulps_plot<decltype(phi_real_hi_acc), double, float>(phi_real_hi_acc, float(0.0), float(100.0), 20000);
plot.ulp_envelope(false);
plot.add_fn(phi_real_lo_acc);
plot.clip(100);
plot.title("Accuracy of Fourier transform of 6 vanishing moment Daubechies scaling function");
plot.write("ft_daub_scaling_6.svg");
return 0;
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// (C) 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 <random>
#include <array>
#include <vector>
#include <iostream>
#include <benchmark/benchmark.h>
#include <boost/math/special_functions/fourier_transform_daubechies_scaling.hpp>

using boost::math::fourier_transform_daubechies_scaling;

template<class Real>
void FourierTransformDaubechiesScaling(benchmark::State& state)
{
std::random_device rd;
auto seed = rd();
std::mt19937_64 mt(seed);
std::uniform_real_distribution<Real> unif(0, 10);

for (auto _ : state)
{
benchmark::DoNotOptimize(fourier_transform_daubechies_scaling<Real, 3>(unif(mt)));
}
}

BENCHMARK_TEMPLATE(FourierTransformDaubechiesScaling, float);
BENCHMARK_TEMPLATE(FourierTransformDaubechiesScaling, double);
//BENCHMARK_TEMPLATE(FourierTransformDaubechiesScaling, long double);

BENCHMARK_MAIN();
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ test-suite interpolators :
[ run gegenbauer_test.cpp : : : [ requires cxx11_auto_declarations cxx11_constexpr cxx11_smart_ptr cxx11_defaulted_functions ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] ]
[ run daubechies_scaling_test.cpp : : : <toolset>gcc-mingw:<cxxflags>-Wa,-mbig-obj <debug-symbols>off <toolset>msvc:<cxxflags>/bigobj [ requires cxx17_if_constexpr cxx17_std_apply ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] [ check-target-builds ../config//is_ci_sanitizer_run "Sanitizer CI run" : <build>no ] [ check-target-builds ../config//is_cygwin_run "Cygwin CI run" : <build>no ] ]
[ run daubechies_wavelet_test.cpp : : : <toolset>gcc-mingw:<cxxflags>-Wa,-mbig-obj <debug-symbols>off <toolset>msvc:<cxxflags>/bigobj [ requires cxx17_if_constexpr cxx17_std_apply ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] [ check-target-builds ../config//is_ci_sanitizer_run "Sanitizer CI run" : <build>no ] [ check-target-builds ../config//is_cygwin_run "Cygwin CI run" : <build>no ] ]
[ run test/fourier_transform_daubechies_scaling_test.cpp : : : <toolset>gcc-mingw:<cxxflags>-Wa,-mbig-obj <debug-symbols>off <toolset>msvc:<cxxflags>/bigobj [ requires cxx17_if_constexpr cxx17_std_apply ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] [ check-target-builds ../config//is_ci_sanitizer_run "Sanitizer CI run" : <build>no ] [ check-target-builds ../config//is_cygwin_run "Cygwin CI run" : <build>no ] ]
[ run wavelet_transform_test.cpp : : : <toolset>msvc:<cxxflags>/bigobj [ requires cxx17_if_constexpr cxx17_std_apply ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] [ check-target-builds ../config//is_ci_sanitizer_run "Sanitizer CI run" : <build>no ] ]
[ run agm_test.cpp : : : <toolset>msvc:<cxxflags>/bigobj [ requires cxx17_if_constexpr cxx17_std_apply ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] ]
[ run rsqrt_test.cpp : : : <toolset>msvc:<cxxflags>/bigobj [ requires cxx17_if_constexpr cxx17_std_apply ] [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : <linkflags>-lquadmath ] ]
Expand Down
124 changes: 124 additions & 0 deletions test/fourier_transform_daubechies_scaling_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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/fourier_transform_daubechies_scaling.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::tools::summation_condition_number;
using boost::math::constants::two_pi;
using boost::math::constants::one_div_root_two_pi;
using boost::math::quadrature::trapezoidal;
// 𝓕[φ](-ω) = 𝓕[φ](ω)*
template<typename Real, int p>
void test_evaluation_symmetry() {
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);
}

for (Real omega = 10; omega < std::sqrt(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<int p>
void test_quadrature() {
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);
}
}

// Tests Daubechies "Ten Lectures on Wavelets", equation 5.1.19:
template<typename Real, int p>
void test_ten_lectures_eq_5_1_19() {
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 < 50 && 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";
}
}
}

int main()
{
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_quadrature<2>();
//test_quadrature<3>();
//test_quadrature<4>();
//test_quadrature<5>();

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>();

return boost::math::test::report_errors();
}
6 changes: 5 additions & 1 deletion test/math_unit_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ bool check_ulp_close(PreciseReal expected1, Real computed, size_t ulps, std::str
using boost::math::lltrunc;
// Of course integers can be expected values, and they are exact:
if (!std::is_integral<PreciseReal>::value) {
BOOST_MATH_ASSERT_MSG(!isnan(expected1), "Expected value cannot be a nan.");
if (isnan(expected1)) {
std::ostringstream oss;
oss << "Error in CHECK_ULP_CLOSE: Expected value cannot be a nan. Callsite: " << filename << ":" << function << ":" << line << ".";
throw std::domain_error(oss.str());
}
if (sizeof(PreciseReal) < sizeof(Real)) {
std::ostringstream err;
err << "\n\tThe expected number must be computed in higher (or equal) precision than the number being tested.\n";
Expand Down

0 comments on commit d793bbc

Please sign in to comment.