/* * Copyright Nick Thompson, 2018 * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include using std::string; using boost::math::tools::polynomial; using boost::math::binomial_coefficient; using boost::math::tools::schroder_iterate; using boost::math::tools::halley_iterate; using boost::math::tools::newton_raphson_iterate; using boost::math::tools::complex_newton; using boost::math::constants::half; using boost::math::constants::root_two; using boost::math::constants::pi; using boost::math::quadrature::gauss_kronrod; using boost::multiprecision::cpp_bin_float_100; using boost::multiprecision::cpp_complex_100; template std::vector> find_roots(size_t p) { // Initialize the polynomial; see Mallat, A Wavelet Tour of Signal Processing, equation 7.96 BOOST_ASSERT(p>0); typedef typename Complex::value_type Real; std::vector coeffs(p); for (size_t k = 0; k < coeffs.size(); ++k) { coeffs[k] = Complex(binomial_coefficient(p-1+k, k), 0); } polynomial P(std::move(coeffs)); polynomial Pcopy = P; polynomial Pcopy_prime = P.prime(); auto orig = [&](Complex z) { return std::make_pair(Pcopy(z), Pcopy_prime(z)); }; polynomial P_prime = P.prime(); // Polynomial is of degree p-1. std::vector roots(p-1, {std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN()}); size_t i = 0; while(P.size() > 1) { Complex guess = {0.0, 1.0}; std::cout << std::setprecision(std::numeric_limits::digits10+3); auto f = [&](Complex x)->std::pair { return std::make_pair(P(x), P_prime(x)); }; Complex r = complex_newton(f, guess); using std::isnan; if(isnan(r.real())) { int i = 50; do { // Try a different guess guess *= Complex(1.0,-1.0); r = complex_newton(f, guess); std::cout << "New guess: " << guess << ", result? " << r << std::endl; } while (isnan(r.real()) && i-- > 0); if (isnan(r.real())) { std::cout << "Polynomial that killed the process: " << P << std::endl; throw std::logic_error("Newton iteration did not converge"); } } // Refine r with the original function. // We only use the polynomial division to ensure we don't get the same root over and over. // However, the division induces error which can grow quickly-or slowly! See Numerical Recipes, section 9.5.1. r = complex_newton(orig, r); if (isnan(r.real())) { throw std::logic_error("Found a root for the deflated polynomial which is not a root for the original. Indicative of catastrophic numerical error."); } // Test the root: using std::sqrt; Real tol = sqrt(sqrt(std::numeric_limits::epsilon())); if (norm(Pcopy(r)) > tol) { std::cout << "This is a bad root: P" << r << " = " << Pcopy(r) << std::endl; std::cout << "Reduced polynomial leading to bad root: " << P << std::endl; throw std::logic_error("Donezo."); } BOOST_ASSERT(i < roots.size()); roots[i] = r; ++i; polynomial q{-r, {1,0}}; // This optimization breaks at p = 11. I have no clue why. // Unfortunate, because I expect it to be considerably more stable than // repeatedly dividing by the complex root. /*polynomial q; if (r.imag() > sqrt(std::numeric_limits::epsilon())) { // Then the complex conjugate is also a root: using std::conj; using std::norm; BOOST_ASSERT(i < roots.size()); roots[i] = conj(r); ++i; q = polynomial({{norm(r), 0}, {-2*r.real(),0}, {1,0}}); } else { // The imaginary part is numerical noise: r.imag() = 0; q = polynomial({-r, {1,0}}); }*/ auto PR = quotient_remainder(P, q); // I should validate that the remainder is small, but . . . //std::cout << "Remainder = " << PR.second<< std::endl; P = PR.first; P_prime = P.prime(); } std::vector> Qroots(p-1); for (size_t i = 0; i < Qroots.size(); ++i) { Complex y = roots[i]; Complex z1 = static_cast(1) - static_cast(2)*y + static_cast(2)*sqrt(y*(y-static_cast(1))); Complex z2 = static_cast(1) - static_cast(2)*y - static_cast(2)*sqrt(y*(y-static_cast(1))); Qroots[i] = {z1, z2}; } return Qroots; } template std::vector daubechies_coefficients(std::vector> const & Qroots) { typedef typename Complex::value_type Real; size_t p = Qroots.size() + 1; // Choose the minimum abs root; see Mallat, discussion just after equation 7.98 std::vector chosen_roots(p-1); for (size_t i = 0; i < p - 1; ++i) { if(norm(Qroots[i].first) <= 1) { chosen_roots[i] = Qroots[i].first; } else { BOOST_ASSERT(norm(Qroots[i].second) <= 1); chosen_roots[i] = Qroots[i].second; } } polynomial R{1}; for (size_t i = 0; i < p-1; ++i) { Complex ak = chosen_roots[i]; R *= polynomial({-ak/(static_cast(1)-ak), static_cast(1)/(static_cast(1)-ak)}); } polynomial a{{half(), 0}, {half(),0}}; polynomial poly = root_two()*pow(a, p)*R; std::vector result = poly.data(); // If we reverse, we get the Numerical Recipes and Daubechies convention. // If we don't reverse, we get the Pywavelets and Mallat convention. // I believe this is because of the sign convention on the DFT, which differs between Daubechies and Mallat. // You implement a dot product in Daubechies/NR convention, and a convolution in PyWavelets/Mallat convention. // I won't reverse so I can spot check against Pywavelets: http://wavelets.pybytes.com/wavelet/ //std::reverse(result.begin(), result.end()); std::vector h(result.size()); for (size_t i = 0; i < result.size(); ++i) { Complex r = result[i]; BOOST_ASSERT(r.imag() < sqrt(std::numeric_limits::epsilon())); h[i] = r.real(); } // Quick sanity check: We could check all vanishing moments, but that sum is horribly ill-conditioned too! Real sum = 0; Real scale = 0; for (size_t i = 0; i < h.size(); ++i) { sum += h[i]; scale += h[i]*h[i]; } BOOST_ASSERT(abs(scale -1) < sqrt(std::numeric_limits::epsilon())); BOOST_ASSERT(abs(sum - root_two()) < sqrt(std::numeric_limits::epsilon())); return h; } int main() { typedef boost::multiprecision::cpp_complex<100> Complex; for(size_t p = 1; p < 200; ++p) { auto roots = find_roots(p); auto h = daubechies_coefficients(roots); std::cout << "h_" << p << "[] = {"; for (auto& x : h) { std::cout << x << ", "; } std::cout << "} // = h_" << p << "\n\n\n\n"; } }