Lanczos Differentiators












0














Friends,



I have been working on implementing denoising Lanczos differentiators, following this paper. The following code works well (though I could certainly improve its performance), but the design is a train wreck, and I was hoping to get some advice to improve it.



The code is provided below, but let me provide some background so the design tradeoffs are more obvious. If you use standard finite difference stencils on noisy data, you'll amplify noise to the extent that the results are totally useless. So instead of using the traditional finite difference filter, you want to create a filter which has $O(Delta t^{p})$ accuracy against the true derivative, but take the filter length $2n+1 gg p$ so that the filter can average away noise. (For $p = 2n$, you recover the standard finite difference filter and for $p > 2n$ the filter is undefined.) Then you need $n$ boundary filters, each of length $2n+1$, because the interior filter requires $n$ points on each side. (The right side boundary filters are reflect+negate the left boundary filters, so don't need to be stored.) So if I was going to precompute all the $(n,p)$-filters up to $n_{max}$, it would require ~$4n_{max}^{3}$ elements in storage, for each precision. That's not counting the fact that people will want these filters precomputed in both float and double precision, perhaps long double as well. Hence did a back-of-the-envelope which concluded that to store $n_{max} = 25$, I'd need a 40MB header file which would need to be parsed to find the desired filter. I think that'd make people pretty unhappy. In addition, the error of the computed derivative decreases as $n^3$, so there's really no point of diminishing returns where I can say "this is a sane $n_{max}$, there's no cause to ever use a longer filter!"



So I feel like I have to compute the filters at runtime. But the filter computation is at least cubic complexity in $n$, so it's pretty expensive. In addition, the filters feel like compile-time data, since they doesn't depend on any runtime data and they're immutable. So I feel like there's some design that could be done which would make it so that the filters only have to be computed once and can be used globally for any data.



Here are some other design problems I feel should be addressed:



I'm passing the data vector into the class at the same time I'm computing the filters, and storing a reference member. This should be decoupled, as the filters don't depend on the data. But then I'd lose the ability to call the class via an index, or would need to do something like lanczos.set_data(v); Real dvdt = lanczos[i]. Another option might be Real dvdt = lanczos(v, i), or even Real dvdt = lanczos(v, dt, i), as the filters don't depend on the timestep either. As I said, the API is a train wreck and it's not clear to me how to make it more elegant.



#ifndef BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP
#define BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP

namespace boost {
namespace math {
namespace differentiation {

namespace detail {
template <typename Real> class discrete_legendre {
public:
discrete_legendre(int n) : m_n{n} {
// The integer n indexes a family of discrete Legendre polynomials indexed by k <= 2*n
}

Real norm_sq(int r) {
Real prod = Real(2) / Real(2 * r + 1);
for (int k = -r; k <= r; ++k) {
prod *= Real(2 * m_n + 1 + k) / Real(2 * m_n);
}
return prod;
}

Real operator()(Real x, int k) {
BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
if (k == 0) {
return 1;
}
if (k == 1) {
return x;
}
Real qrm2 = 1;
Real qrm1 = x;
Real N = 2 * m_n + 1;
Real qr;
for (int r = 2; r <= k; ++r) {
Real num = (r - 1) * (N * N - (r - 1) * (r - 1)) * qrm2;
Real tmp = (2 * r - 1) * x * qrm1 - num / Real(4 * m_n * m_n);
qrm2 = qrm1;
qrm1 = tmp / r;
}
return qrm1;
}

Real prime(Real x, int k) {
BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
if (k == 0) {
return 0;
}
if (k == 1) {
return 1;
}
Real qrm2 = 1;
Real qrm1 = x;
Real qrm2p = 0;
Real qrm1p = 1;
Real N = 2 * m_n + 1;
Real qr;
for (int r = 2; r <= k; ++r) {
Real s =
(r - 1) * (N * N - (r - 1) * (r - 1)) / Real(4 * m_n * m_n);
Real tmp1 = ((2 * r - 1) * x * qrm1 - s * qrm2) / r;
Real tmp2 = ((2 * r - 1) * (qrm1 + x * qrm1p) - s * qrm2p) / r;
qrm2 = qrm1;
qrm1 = tmp1;
qrm2p = qrm1p;
qrm1p = tmp2;
}
return qrm1p;
}

private:
int m_n;
};

template <class Real> std::vector<Real> interior_filter(int n, int p) {
// We could make the filter length n, as f[0] = 0,
// but that'd make the indexing awkward when applying the filter.
std::vector<Real> f(n + 1, 0);
auto dlp = discrete_legendre<Real>(n);
for (int j = 1; j < f.size(); ++j) {
Real arg = Real(j) / Real(n);
for (int l = 1; l <= p; l += 2) {
f[j] += dlp.prime(0, l) * dlp(arg, l) / dlp.norm_sq(l);
}
f[j] /= (n * n);
}
return f;
}

template <class Real> std::vector<Real> boundary_filter(int n, int p, int s) {
std::vector<Real> f(2 * n + 1, 0);
auto dlp = discrete_legendre<Real>(n);
for (int k = 0; k < f.size(); ++k) {
int j = k - n;
f[k] = 0;
Real arg = Real(j) / Real(n);
Real sn = Real(s) / Real(n);
for (int l = 1; l <= p; ++l) {
f[k] += dlp.prime(sn, l) * dlp(arg, l) / dlp.norm_sq(l);
}
f[k] /= (n * n);
}
return f;
}

} // namespace detail

template <class RandomAccessContainer> class lanczos_smoothing_derivative {
public:
using Real = typename RandomAccessContainer::value_type;
lanczos_smoothing_derivative(RandomAccessContainer const &v,
Real spacing = 1, int filter_length = 18,
int approximation_order = 3)
: m_v{v}, dt{spacing} {
BOOST_ASSERT_MSG(approximation_order <= 2 * filter_length,
"The approximation order must be <= 2n");
BOOST_ASSERT_MSG(spacing > 0, "Spacing between samples must be > 0.");
using std::size;
BOOST_ASSERT_MSG(
size(v) >= filter_length,
"Vector must be at least as long as the filter length");
m_f = detail::interior_filter<Real>(filter_length, approximation_order);

boundary_filters.resize(filter_length);
for (size_t i = 0; i < filter_length; ++i) {
// s = i - n;
boundary_filters[i] = detail::boundary_filter<Real>(
filter_length, approximation_order, i - filter_length);
}
}

Real operator(size_t i) const {
using std::size;
if (i >= m_f.size() - 1 && i <= size(m_v) - m_f.size()) {
Real dv = 0;
for (size_t j = 1; j < m_f.size(); ++j) {
dv += m_f[j] * (m_v[i + j] - m_v[i - j]);
}
return dv / dt;
}

// m_f.size() = N+1
if (i < m_f.size() - 1) {
auto &f = boundary_filters[i];
Real dv = 0;
for (size_t j = 0; j < f.size(); ++j) {
dv += f[j] * m_v[j];
}
return dv / dt;
}

if (i > size(m_v) - m_f.size()) {
int k = size(m_v) - 1 - i;
auto &f = boundary_filters[k];
Real dv = 0;
for (size_t j = 0; j < f.size(); ++j) {
dv += f[j] * m_v[m_v.size() - 1 - j];
}
return -dv / dt;
}

// OOB access:
return std::numeric_limits<Real>::quiet_NaN();
}

private:
const RandomAccessContainer &m_v;
std::vector<Real> m_f;
std::vector<std::vector<Real>> boundary_filters;
Real dt;
};

} // namespace differentiation
} // namespace math
} // namespace boost
#endif


Any advice is appreciated. Note that I haven't fine-toothed this code for elegance because I think it needs to be torn up and refactored; I'll post another question once the API is stabilized.










share|improve this question





























    0














    Friends,



    I have been working on implementing denoising Lanczos differentiators, following this paper. The following code works well (though I could certainly improve its performance), but the design is a train wreck, and I was hoping to get some advice to improve it.



    The code is provided below, but let me provide some background so the design tradeoffs are more obvious. If you use standard finite difference stencils on noisy data, you'll amplify noise to the extent that the results are totally useless. So instead of using the traditional finite difference filter, you want to create a filter which has $O(Delta t^{p})$ accuracy against the true derivative, but take the filter length $2n+1 gg p$ so that the filter can average away noise. (For $p = 2n$, you recover the standard finite difference filter and for $p > 2n$ the filter is undefined.) Then you need $n$ boundary filters, each of length $2n+1$, because the interior filter requires $n$ points on each side. (The right side boundary filters are reflect+negate the left boundary filters, so don't need to be stored.) So if I was going to precompute all the $(n,p)$-filters up to $n_{max}$, it would require ~$4n_{max}^{3}$ elements in storage, for each precision. That's not counting the fact that people will want these filters precomputed in both float and double precision, perhaps long double as well. Hence did a back-of-the-envelope which concluded that to store $n_{max} = 25$, I'd need a 40MB header file which would need to be parsed to find the desired filter. I think that'd make people pretty unhappy. In addition, the error of the computed derivative decreases as $n^3$, so there's really no point of diminishing returns where I can say "this is a sane $n_{max}$, there's no cause to ever use a longer filter!"



    So I feel like I have to compute the filters at runtime. But the filter computation is at least cubic complexity in $n$, so it's pretty expensive. In addition, the filters feel like compile-time data, since they doesn't depend on any runtime data and they're immutable. So I feel like there's some design that could be done which would make it so that the filters only have to be computed once and can be used globally for any data.



    Here are some other design problems I feel should be addressed:



    I'm passing the data vector into the class at the same time I'm computing the filters, and storing a reference member. This should be decoupled, as the filters don't depend on the data. But then I'd lose the ability to call the class via an index, or would need to do something like lanczos.set_data(v); Real dvdt = lanczos[i]. Another option might be Real dvdt = lanczos(v, i), or even Real dvdt = lanczos(v, dt, i), as the filters don't depend on the timestep either. As I said, the API is a train wreck and it's not clear to me how to make it more elegant.



    #ifndef BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP
    #define BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP

    namespace boost {
    namespace math {
    namespace differentiation {

    namespace detail {
    template <typename Real> class discrete_legendre {
    public:
    discrete_legendre(int n) : m_n{n} {
    // The integer n indexes a family of discrete Legendre polynomials indexed by k <= 2*n
    }

    Real norm_sq(int r) {
    Real prod = Real(2) / Real(2 * r + 1);
    for (int k = -r; k <= r; ++k) {
    prod *= Real(2 * m_n + 1 + k) / Real(2 * m_n);
    }
    return prod;
    }

    Real operator()(Real x, int k) {
    BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
    if (k == 0) {
    return 1;
    }
    if (k == 1) {
    return x;
    }
    Real qrm2 = 1;
    Real qrm1 = x;
    Real N = 2 * m_n + 1;
    Real qr;
    for (int r = 2; r <= k; ++r) {
    Real num = (r - 1) * (N * N - (r - 1) * (r - 1)) * qrm2;
    Real tmp = (2 * r - 1) * x * qrm1 - num / Real(4 * m_n * m_n);
    qrm2 = qrm1;
    qrm1 = tmp / r;
    }
    return qrm1;
    }

    Real prime(Real x, int k) {
    BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
    if (k == 0) {
    return 0;
    }
    if (k == 1) {
    return 1;
    }
    Real qrm2 = 1;
    Real qrm1 = x;
    Real qrm2p = 0;
    Real qrm1p = 1;
    Real N = 2 * m_n + 1;
    Real qr;
    for (int r = 2; r <= k; ++r) {
    Real s =
    (r - 1) * (N * N - (r - 1) * (r - 1)) / Real(4 * m_n * m_n);
    Real tmp1 = ((2 * r - 1) * x * qrm1 - s * qrm2) / r;
    Real tmp2 = ((2 * r - 1) * (qrm1 + x * qrm1p) - s * qrm2p) / r;
    qrm2 = qrm1;
    qrm1 = tmp1;
    qrm2p = qrm1p;
    qrm1p = tmp2;
    }
    return qrm1p;
    }

    private:
    int m_n;
    };

    template <class Real> std::vector<Real> interior_filter(int n, int p) {
    // We could make the filter length n, as f[0] = 0,
    // but that'd make the indexing awkward when applying the filter.
    std::vector<Real> f(n + 1, 0);
    auto dlp = discrete_legendre<Real>(n);
    for (int j = 1; j < f.size(); ++j) {
    Real arg = Real(j) / Real(n);
    for (int l = 1; l <= p; l += 2) {
    f[j] += dlp.prime(0, l) * dlp(arg, l) / dlp.norm_sq(l);
    }
    f[j] /= (n * n);
    }
    return f;
    }

    template <class Real> std::vector<Real> boundary_filter(int n, int p, int s) {
    std::vector<Real> f(2 * n + 1, 0);
    auto dlp = discrete_legendre<Real>(n);
    for (int k = 0; k < f.size(); ++k) {
    int j = k - n;
    f[k] = 0;
    Real arg = Real(j) / Real(n);
    Real sn = Real(s) / Real(n);
    for (int l = 1; l <= p; ++l) {
    f[k] += dlp.prime(sn, l) * dlp(arg, l) / dlp.norm_sq(l);
    }
    f[k] /= (n * n);
    }
    return f;
    }

    } // namespace detail

    template <class RandomAccessContainer> class lanczos_smoothing_derivative {
    public:
    using Real = typename RandomAccessContainer::value_type;
    lanczos_smoothing_derivative(RandomAccessContainer const &v,
    Real spacing = 1, int filter_length = 18,
    int approximation_order = 3)
    : m_v{v}, dt{spacing} {
    BOOST_ASSERT_MSG(approximation_order <= 2 * filter_length,
    "The approximation order must be <= 2n");
    BOOST_ASSERT_MSG(spacing > 0, "Spacing between samples must be > 0.");
    using std::size;
    BOOST_ASSERT_MSG(
    size(v) >= filter_length,
    "Vector must be at least as long as the filter length");
    m_f = detail::interior_filter<Real>(filter_length, approximation_order);

    boundary_filters.resize(filter_length);
    for (size_t i = 0; i < filter_length; ++i) {
    // s = i - n;
    boundary_filters[i] = detail::boundary_filter<Real>(
    filter_length, approximation_order, i - filter_length);
    }
    }

    Real operator(size_t i) const {
    using std::size;
    if (i >= m_f.size() - 1 && i <= size(m_v) - m_f.size()) {
    Real dv = 0;
    for (size_t j = 1; j < m_f.size(); ++j) {
    dv += m_f[j] * (m_v[i + j] - m_v[i - j]);
    }
    return dv / dt;
    }

    // m_f.size() = N+1
    if (i < m_f.size() - 1) {
    auto &f = boundary_filters[i];
    Real dv = 0;
    for (size_t j = 0; j < f.size(); ++j) {
    dv += f[j] * m_v[j];
    }
    return dv / dt;
    }

    if (i > size(m_v) - m_f.size()) {
    int k = size(m_v) - 1 - i;
    auto &f = boundary_filters[k];
    Real dv = 0;
    for (size_t j = 0; j < f.size(); ++j) {
    dv += f[j] * m_v[m_v.size() - 1 - j];
    }
    return -dv / dt;
    }

    // OOB access:
    return std::numeric_limits<Real>::quiet_NaN();
    }

    private:
    const RandomAccessContainer &m_v;
    std::vector<Real> m_f;
    std::vector<std::vector<Real>> boundary_filters;
    Real dt;
    };

    } // namespace differentiation
    } // namespace math
    } // namespace boost
    #endif


    Any advice is appreciated. Note that I haven't fine-toothed this code for elegance because I think it needs to be torn up and refactored; I'll post another question once the API is stabilized.










    share|improve this question



























      0












      0








      0







      Friends,



      I have been working on implementing denoising Lanczos differentiators, following this paper. The following code works well (though I could certainly improve its performance), but the design is a train wreck, and I was hoping to get some advice to improve it.



      The code is provided below, but let me provide some background so the design tradeoffs are more obvious. If you use standard finite difference stencils on noisy data, you'll amplify noise to the extent that the results are totally useless. So instead of using the traditional finite difference filter, you want to create a filter which has $O(Delta t^{p})$ accuracy against the true derivative, but take the filter length $2n+1 gg p$ so that the filter can average away noise. (For $p = 2n$, you recover the standard finite difference filter and for $p > 2n$ the filter is undefined.) Then you need $n$ boundary filters, each of length $2n+1$, because the interior filter requires $n$ points on each side. (The right side boundary filters are reflect+negate the left boundary filters, so don't need to be stored.) So if I was going to precompute all the $(n,p)$-filters up to $n_{max}$, it would require ~$4n_{max}^{3}$ elements in storage, for each precision. That's not counting the fact that people will want these filters precomputed in both float and double precision, perhaps long double as well. Hence did a back-of-the-envelope which concluded that to store $n_{max} = 25$, I'd need a 40MB header file which would need to be parsed to find the desired filter. I think that'd make people pretty unhappy. In addition, the error of the computed derivative decreases as $n^3$, so there's really no point of diminishing returns where I can say "this is a sane $n_{max}$, there's no cause to ever use a longer filter!"



      So I feel like I have to compute the filters at runtime. But the filter computation is at least cubic complexity in $n$, so it's pretty expensive. In addition, the filters feel like compile-time data, since they doesn't depend on any runtime data and they're immutable. So I feel like there's some design that could be done which would make it so that the filters only have to be computed once and can be used globally for any data.



      Here are some other design problems I feel should be addressed:



      I'm passing the data vector into the class at the same time I'm computing the filters, and storing a reference member. This should be decoupled, as the filters don't depend on the data. But then I'd lose the ability to call the class via an index, or would need to do something like lanczos.set_data(v); Real dvdt = lanczos[i]. Another option might be Real dvdt = lanczos(v, i), or even Real dvdt = lanczos(v, dt, i), as the filters don't depend on the timestep either. As I said, the API is a train wreck and it's not clear to me how to make it more elegant.



      #ifndef BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP
      #define BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP

      namespace boost {
      namespace math {
      namespace differentiation {

      namespace detail {
      template <typename Real> class discrete_legendre {
      public:
      discrete_legendre(int n) : m_n{n} {
      // The integer n indexes a family of discrete Legendre polynomials indexed by k <= 2*n
      }

      Real norm_sq(int r) {
      Real prod = Real(2) / Real(2 * r + 1);
      for (int k = -r; k <= r; ++k) {
      prod *= Real(2 * m_n + 1 + k) / Real(2 * m_n);
      }
      return prod;
      }

      Real operator()(Real x, int k) {
      BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
      if (k == 0) {
      return 1;
      }
      if (k == 1) {
      return x;
      }
      Real qrm2 = 1;
      Real qrm1 = x;
      Real N = 2 * m_n + 1;
      Real qr;
      for (int r = 2; r <= k; ++r) {
      Real num = (r - 1) * (N * N - (r - 1) * (r - 1)) * qrm2;
      Real tmp = (2 * r - 1) * x * qrm1 - num / Real(4 * m_n * m_n);
      qrm2 = qrm1;
      qrm1 = tmp / r;
      }
      return qrm1;
      }

      Real prime(Real x, int k) {
      BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
      if (k == 0) {
      return 0;
      }
      if (k == 1) {
      return 1;
      }
      Real qrm2 = 1;
      Real qrm1 = x;
      Real qrm2p = 0;
      Real qrm1p = 1;
      Real N = 2 * m_n + 1;
      Real qr;
      for (int r = 2; r <= k; ++r) {
      Real s =
      (r - 1) * (N * N - (r - 1) * (r - 1)) / Real(4 * m_n * m_n);
      Real tmp1 = ((2 * r - 1) * x * qrm1 - s * qrm2) / r;
      Real tmp2 = ((2 * r - 1) * (qrm1 + x * qrm1p) - s * qrm2p) / r;
      qrm2 = qrm1;
      qrm1 = tmp1;
      qrm2p = qrm1p;
      qrm1p = tmp2;
      }
      return qrm1p;
      }

      private:
      int m_n;
      };

      template <class Real> std::vector<Real> interior_filter(int n, int p) {
      // We could make the filter length n, as f[0] = 0,
      // but that'd make the indexing awkward when applying the filter.
      std::vector<Real> f(n + 1, 0);
      auto dlp = discrete_legendre<Real>(n);
      for (int j = 1; j < f.size(); ++j) {
      Real arg = Real(j) / Real(n);
      for (int l = 1; l <= p; l += 2) {
      f[j] += dlp.prime(0, l) * dlp(arg, l) / dlp.norm_sq(l);
      }
      f[j] /= (n * n);
      }
      return f;
      }

      template <class Real> std::vector<Real> boundary_filter(int n, int p, int s) {
      std::vector<Real> f(2 * n + 1, 0);
      auto dlp = discrete_legendre<Real>(n);
      for (int k = 0; k < f.size(); ++k) {
      int j = k - n;
      f[k] = 0;
      Real arg = Real(j) / Real(n);
      Real sn = Real(s) / Real(n);
      for (int l = 1; l <= p; ++l) {
      f[k] += dlp.prime(sn, l) * dlp(arg, l) / dlp.norm_sq(l);
      }
      f[k] /= (n * n);
      }
      return f;
      }

      } // namespace detail

      template <class RandomAccessContainer> class lanczos_smoothing_derivative {
      public:
      using Real = typename RandomAccessContainer::value_type;
      lanczos_smoothing_derivative(RandomAccessContainer const &v,
      Real spacing = 1, int filter_length = 18,
      int approximation_order = 3)
      : m_v{v}, dt{spacing} {
      BOOST_ASSERT_MSG(approximation_order <= 2 * filter_length,
      "The approximation order must be <= 2n");
      BOOST_ASSERT_MSG(spacing > 0, "Spacing between samples must be > 0.");
      using std::size;
      BOOST_ASSERT_MSG(
      size(v) >= filter_length,
      "Vector must be at least as long as the filter length");
      m_f = detail::interior_filter<Real>(filter_length, approximation_order);

      boundary_filters.resize(filter_length);
      for (size_t i = 0; i < filter_length; ++i) {
      // s = i - n;
      boundary_filters[i] = detail::boundary_filter<Real>(
      filter_length, approximation_order, i - filter_length);
      }
      }

      Real operator(size_t i) const {
      using std::size;
      if (i >= m_f.size() - 1 && i <= size(m_v) - m_f.size()) {
      Real dv = 0;
      for (size_t j = 1; j < m_f.size(); ++j) {
      dv += m_f[j] * (m_v[i + j] - m_v[i - j]);
      }
      return dv / dt;
      }

      // m_f.size() = N+1
      if (i < m_f.size() - 1) {
      auto &f = boundary_filters[i];
      Real dv = 0;
      for (size_t j = 0; j < f.size(); ++j) {
      dv += f[j] * m_v[j];
      }
      return dv / dt;
      }

      if (i > size(m_v) - m_f.size()) {
      int k = size(m_v) - 1 - i;
      auto &f = boundary_filters[k];
      Real dv = 0;
      for (size_t j = 0; j < f.size(); ++j) {
      dv += f[j] * m_v[m_v.size() - 1 - j];
      }
      return -dv / dt;
      }

      // OOB access:
      return std::numeric_limits<Real>::quiet_NaN();
      }

      private:
      const RandomAccessContainer &m_v;
      std::vector<Real> m_f;
      std::vector<std::vector<Real>> boundary_filters;
      Real dt;
      };

      } // namespace differentiation
      } // namespace math
      } // namespace boost
      #endif


      Any advice is appreciated. Note that I haven't fine-toothed this code for elegance because I think it needs to be torn up and refactored; I'll post another question once the API is stabilized.










      share|improve this question















      Friends,



      I have been working on implementing denoising Lanczos differentiators, following this paper. The following code works well (though I could certainly improve its performance), but the design is a train wreck, and I was hoping to get some advice to improve it.



      The code is provided below, but let me provide some background so the design tradeoffs are more obvious. If you use standard finite difference stencils on noisy data, you'll amplify noise to the extent that the results are totally useless. So instead of using the traditional finite difference filter, you want to create a filter which has $O(Delta t^{p})$ accuracy against the true derivative, but take the filter length $2n+1 gg p$ so that the filter can average away noise. (For $p = 2n$, you recover the standard finite difference filter and for $p > 2n$ the filter is undefined.) Then you need $n$ boundary filters, each of length $2n+1$, because the interior filter requires $n$ points on each side. (The right side boundary filters are reflect+negate the left boundary filters, so don't need to be stored.) So if I was going to precompute all the $(n,p)$-filters up to $n_{max}$, it would require ~$4n_{max}^{3}$ elements in storage, for each precision. That's not counting the fact that people will want these filters precomputed in both float and double precision, perhaps long double as well. Hence did a back-of-the-envelope which concluded that to store $n_{max} = 25$, I'd need a 40MB header file which would need to be parsed to find the desired filter. I think that'd make people pretty unhappy. In addition, the error of the computed derivative decreases as $n^3$, so there's really no point of diminishing returns where I can say "this is a sane $n_{max}$, there's no cause to ever use a longer filter!"



      So I feel like I have to compute the filters at runtime. But the filter computation is at least cubic complexity in $n$, so it's pretty expensive. In addition, the filters feel like compile-time data, since they doesn't depend on any runtime data and they're immutable. So I feel like there's some design that could be done which would make it so that the filters only have to be computed once and can be used globally for any data.



      Here are some other design problems I feel should be addressed:



      I'm passing the data vector into the class at the same time I'm computing the filters, and storing a reference member. This should be decoupled, as the filters don't depend on the data. But then I'd lose the ability to call the class via an index, or would need to do something like lanczos.set_data(v); Real dvdt = lanczos[i]. Another option might be Real dvdt = lanczos(v, i), or even Real dvdt = lanczos(v, dt, i), as the filters don't depend on the timestep either. As I said, the API is a train wreck and it's not clear to me how to make it more elegant.



      #ifndef BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP
      #define BOOST_MATH_DIFFERENTIATION_LANCZOS_SMOOTHING_HPP

      namespace boost {
      namespace math {
      namespace differentiation {

      namespace detail {
      template <typename Real> class discrete_legendre {
      public:
      discrete_legendre(int n) : m_n{n} {
      // The integer n indexes a family of discrete Legendre polynomials indexed by k <= 2*n
      }

      Real norm_sq(int r) {
      Real prod = Real(2) / Real(2 * r + 1);
      for (int k = -r; k <= r; ++k) {
      prod *= Real(2 * m_n + 1 + k) / Real(2 * m_n);
      }
      return prod;
      }

      Real operator()(Real x, int k) {
      BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
      if (k == 0) {
      return 1;
      }
      if (k == 1) {
      return x;
      }
      Real qrm2 = 1;
      Real qrm1 = x;
      Real N = 2 * m_n + 1;
      Real qr;
      for (int r = 2; r <= k; ++r) {
      Real num = (r - 1) * (N * N - (r - 1) * (r - 1)) * qrm2;
      Real tmp = (2 * r - 1) * x * qrm1 - num / Real(4 * m_n * m_n);
      qrm2 = qrm1;
      qrm1 = tmp / r;
      }
      return qrm1;
      }

      Real prime(Real x, int k) {
      BOOST_ASSERT_MSG(k <= 2 * m_n, "r <= 2n is required.");
      if (k == 0) {
      return 0;
      }
      if (k == 1) {
      return 1;
      }
      Real qrm2 = 1;
      Real qrm1 = x;
      Real qrm2p = 0;
      Real qrm1p = 1;
      Real N = 2 * m_n + 1;
      Real qr;
      for (int r = 2; r <= k; ++r) {
      Real s =
      (r - 1) * (N * N - (r - 1) * (r - 1)) / Real(4 * m_n * m_n);
      Real tmp1 = ((2 * r - 1) * x * qrm1 - s * qrm2) / r;
      Real tmp2 = ((2 * r - 1) * (qrm1 + x * qrm1p) - s * qrm2p) / r;
      qrm2 = qrm1;
      qrm1 = tmp1;
      qrm2p = qrm1p;
      qrm1p = tmp2;
      }
      return qrm1p;
      }

      private:
      int m_n;
      };

      template <class Real> std::vector<Real> interior_filter(int n, int p) {
      // We could make the filter length n, as f[0] = 0,
      // but that'd make the indexing awkward when applying the filter.
      std::vector<Real> f(n + 1, 0);
      auto dlp = discrete_legendre<Real>(n);
      for (int j = 1; j < f.size(); ++j) {
      Real arg = Real(j) / Real(n);
      for (int l = 1; l <= p; l += 2) {
      f[j] += dlp.prime(0, l) * dlp(arg, l) / dlp.norm_sq(l);
      }
      f[j] /= (n * n);
      }
      return f;
      }

      template <class Real> std::vector<Real> boundary_filter(int n, int p, int s) {
      std::vector<Real> f(2 * n + 1, 0);
      auto dlp = discrete_legendre<Real>(n);
      for (int k = 0; k < f.size(); ++k) {
      int j = k - n;
      f[k] = 0;
      Real arg = Real(j) / Real(n);
      Real sn = Real(s) / Real(n);
      for (int l = 1; l <= p; ++l) {
      f[k] += dlp.prime(sn, l) * dlp(arg, l) / dlp.norm_sq(l);
      }
      f[k] /= (n * n);
      }
      return f;
      }

      } // namespace detail

      template <class RandomAccessContainer> class lanczos_smoothing_derivative {
      public:
      using Real = typename RandomAccessContainer::value_type;
      lanczos_smoothing_derivative(RandomAccessContainer const &v,
      Real spacing = 1, int filter_length = 18,
      int approximation_order = 3)
      : m_v{v}, dt{spacing} {
      BOOST_ASSERT_MSG(approximation_order <= 2 * filter_length,
      "The approximation order must be <= 2n");
      BOOST_ASSERT_MSG(spacing > 0, "Spacing between samples must be > 0.");
      using std::size;
      BOOST_ASSERT_MSG(
      size(v) >= filter_length,
      "Vector must be at least as long as the filter length");
      m_f = detail::interior_filter<Real>(filter_length, approximation_order);

      boundary_filters.resize(filter_length);
      for (size_t i = 0; i < filter_length; ++i) {
      // s = i - n;
      boundary_filters[i] = detail::boundary_filter<Real>(
      filter_length, approximation_order, i - filter_length);
      }
      }

      Real operator(size_t i) const {
      using std::size;
      if (i >= m_f.size() - 1 && i <= size(m_v) - m_f.size()) {
      Real dv = 0;
      for (size_t j = 1; j < m_f.size(); ++j) {
      dv += m_f[j] * (m_v[i + j] - m_v[i - j]);
      }
      return dv / dt;
      }

      // m_f.size() = N+1
      if (i < m_f.size() - 1) {
      auto &f = boundary_filters[i];
      Real dv = 0;
      for (size_t j = 0; j < f.size(); ++j) {
      dv += f[j] * m_v[j];
      }
      return dv / dt;
      }

      if (i > size(m_v) - m_f.size()) {
      int k = size(m_v) - 1 - i;
      auto &f = boundary_filters[k];
      Real dv = 0;
      for (size_t j = 0; j < f.size(); ++j) {
      dv += f[j] * m_v[m_v.size() - 1 - j];
      }
      return -dv / dt;
      }

      // OOB access:
      return std::numeric_limits<Real>::quiet_NaN();
      }

      private:
      const RandomAccessContainer &m_v;
      std::vector<Real> m_f;
      std::vector<std::vector<Real>> boundary_filters;
      Real dt;
      };

      } // namespace differentiation
      } // namespace math
      } // namespace boost
      #endif


      Any advice is appreciated. Note that I haven't fine-toothed this code for elegance because I think it needs to be torn up and refactored; I'll post another question once the API is stabilized.







      c++ numerical-methods signal-processing






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited 1 hour ago

























      asked 2 hours ago









      user14717

      24619




      24619



























          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210609%2flanczos-differentiators%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown






























          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210609%2flanczos-differentiators%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Morgemoulin

          Scott Moir

          Souastre