编写接受通用 n 维函数的模板化递归积分方法
Write templated recursive integration method that accepts generic n-dimensional functions
我正在开发一个大型 C++ 框架来进行一些粒子物理计算,如果有一种方法可以对具有 N 个变量的通用函数进行 N 维积分,那就太好了。积分发生在 N 立方体 [0:1]^N 上。
不幸的是,被积函数是一个成员函数,所以我将一个 std::bind
对象传递给一个模板函数(lambdas 也能很好地工作)。当N > 1
时,集成方法再次绑定固定一个变量的函数,并将新函数传递给N-1
的方法。
我觉得这可以通过将 N
作为模板参数来递归完成,但这就是我卡住的地方:有没有办法“模板化”绑定部分,以便它添加适当数量的占位符与函数的参数或 N
?然后将新的绑定方法传递给 integral<N-1>
,等等。我认为问题在于 std::bind
对象没有签名。也许使用 lambdas 可以利用可变包扩展,但模板必须以某种方式解析签名。
有没有办法使这项工作?如果可能,在 c++11 中并且没有 boost 库。
这是我现在没有递归的非常简化的版本,而是对每个维度(现在最多 3 个)的积分方法的不同定义
#include <iostream>
#include <functional>
#include <cmath>
// integrate between [0:1]
template <typename Functor>
double integral1D(Functor fn, size_t steps = 100) {
double sum = 0.;
double step = 1./steps;
for (size_t s = 0; s < steps; ++s)
sum += fn(s * step);
return sum * step;
}
template <typename Functor>
double integral2D(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) ->double {
auto subint = std::bind(fn, v, std::placeholders::_1);
return integral1D(subint, steps);
};
return integral1D(subfn, steps);
}
template <typename Functor>
double integral3D(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) ->double {
auto subint = std::bind(fn, v, std::placeholders::_1, std::placeholders::_2);
return integral2D(subint, steps);
};
return integral1D(subfn, steps);
}
struct A
{
double gaus1(double x, double range) { // computes jacobian on [-range:range]
x = range * (2. * x - 1.);
return 2 * range * std::exp(- x*x);
}
double gaus2(double x, double y, double range) {
return gaus1(x, range) * gaus1(y, range);
}
double gaus3(double x, double y, double z, double range) {
return gaus2(x, y, range) * gaus1(z, range);
}
double gaus1_integrate(double range) {
auto func = std::bind(&A::gaus1, this, std::placeholders::_1, range);
return integral1D(func);
}
double gaus2_integrate(double range) {
auto func = std::bind(&A::gaus2, this, std::placeholders::_1,
std::placeholders::_2, range);
return integral2D(func);
}
double gaus3_integrate(double range) {
auto func = std::bind(&A::gaus3, this, std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3, range);
return integral3D(func);
}
};
int main() {
A a;
std::cout << "1D integral is " << a.gaus1_integrate(50) << "\n";
std::cout << "2D integral is " << a.gaus2_integrate(50) << "\n";
std::cout << "3D integral is " << a.gaus3_integrate(50) << "\n";
return 0;
}
以上示例有效并给出了预期结果。我知道不建议对具有更多维度的更复杂的函数进行黎曼求和(或等效),但很高兴看看上面描述的方法是否可行。
回答
根据 Guillaume 的有用建议,这里是 c++11 工作示例。需要进行少量修改。
#include <iostream>
#include <functional>
#include <cmath>
template<int n, typename Functor>
struct integralNDsubint {
double v;
const Functor &fn;
template<typename... Args>
double operator()(Args... rest) const {
return fn(v, rest...);
}
};
template <int n, typename Functor, typename std::enable_if<(n > 1), bool>::type = true>
double integralND(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) -> double {
auto subint = integralNDsubint<n, Functor> {v, fn};
// following block can be used in c++14 without
// need of helper struct integralNDsubint
//auto subint = [v, &fn](auto... rest) {
//return fn(v, rest...);
//};
return integralND<n - 1>(subint, steps);
};
return integralND<1>(subfn, steps);
}
template <int n, typename Functor, typename std::enable_if<(n == 1), bool>::type = true>
double integralND(Functor fn, size_t steps = 100) {
double sum = 0.;
double step = 1./steps;
for (size_t s = 0; s < steps; ++s) {
sum += fn(s * step);
}
return sum * step;
}
struct A
{
double gaus1(double x, double range) {
x = range * (2. * x - 1.);
return 2 * range * std::exp(- x*x);
}
double gaus2(double x, double y, double range) {
return gaus1(x, range) * gaus1(y, range);
}
double gaus3(double x, double y, double z, double range) {
return gaus2(x, y, range) * gaus1(z, range);
}
double gaus1_integrate(double range) {
auto func = [=](double x) { return gaus1(x, range); };
return integralND<1>(func);
}
double gaus2_integrate(double range) {
auto func = [=](double x, double y) { return gaus2(x, y, range); } ;
return integralND<2>(func);
}
double gaus3_integrate(double range) {
auto func = [=](double x, double y, double z) { return gaus3(x, y, z, range); } ;
return integralND<3>(func);
}
};
int main() {
A a;
std::cout << "1D integral is " << a.gaus1_integrate(50) << "\n";
std::cout << "2D integral is " << a.gaus2_integrate(50) << "\n";
std::cout << "3D integral is " << a.gaus3_integrate(50) << "\n";
return 0;
}
您的函数可以推广到任意数量的维度。
template <int n, typename Functor, std::enable_if_t<(n > 1), int> = 0>
double integralND(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) -> double {
auto subint = [v, &fn](auto... rest) {
return fn(v, rest...);
};
return integralND<n - 1>(subint, steps);
};
return integralND<1>(subfn, steps);
}
template <int n, typename Functor, std::enable_if_t<(n == 1), int> = 0>
double integralND(Functor fn, size_t steps = 100) {
double sum = 0.;
double step = 1./steps;
for (size_t s = 0; s < steps; ++s) {
sum += fn(s * step);
}
return sum * step;
}
如您所见,它使用了通用的 lambda。为了与 C++11 保持兼容,您可以手动编写函数对象:
template<int n, typename F>
struct integralNDsubint {
double v;
F const& fn;
template<typename... Args>
auto operator()(Args... rest) const -> double {
return fn(v, rest...);
}
};
然后像那样使用它:
auto subint = integralNDsubint<n, Functor>{v, fn};
您还需要更改 sfinae:
template <int n, typename Functor, typename std::enable_if<(n > 1), int>::type = 0>
double integralND(Functor fn, size_t steps = 100) {
// ...
}
template <int n, typename Functor, typename std::enable_if<(n == 1), int>::type = 0>
double integralND(Functor fn, size_t steps = 100) {
// ...
}
我正在开发一个大型 C++ 框架来进行一些粒子物理计算,如果有一种方法可以对具有 N 个变量的通用函数进行 N 维积分,那就太好了。积分发生在 N 立方体 [0:1]^N 上。
不幸的是,被积函数是一个成员函数,所以我将一个 std::bind
对象传递给一个模板函数(lambdas 也能很好地工作)。当N > 1
时,集成方法再次绑定固定一个变量的函数,并将新函数传递给N-1
的方法。
我觉得这可以通过将 N
作为模板参数来递归完成,但这就是我卡住的地方:有没有办法“模板化”绑定部分,以便它添加适当数量的占位符与函数的参数或 N
?然后将新的绑定方法传递给 integral<N-1>
,等等。我认为问题在于 std::bind
对象没有签名。也许使用 lambdas 可以利用可变包扩展,但模板必须以某种方式解析签名。
有没有办法使这项工作?如果可能,在 c++11 中并且没有 boost 库。
这是我现在没有递归的非常简化的版本,而是对每个维度(现在最多 3 个)的积分方法的不同定义
#include <iostream>
#include <functional>
#include <cmath>
// integrate between [0:1]
template <typename Functor>
double integral1D(Functor fn, size_t steps = 100) {
double sum = 0.;
double step = 1./steps;
for (size_t s = 0; s < steps; ++s)
sum += fn(s * step);
return sum * step;
}
template <typename Functor>
double integral2D(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) ->double {
auto subint = std::bind(fn, v, std::placeholders::_1);
return integral1D(subint, steps);
};
return integral1D(subfn, steps);
}
template <typename Functor>
double integral3D(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) ->double {
auto subint = std::bind(fn, v, std::placeholders::_1, std::placeholders::_2);
return integral2D(subint, steps);
};
return integral1D(subfn, steps);
}
struct A
{
double gaus1(double x, double range) { // computes jacobian on [-range:range]
x = range * (2. * x - 1.);
return 2 * range * std::exp(- x*x);
}
double gaus2(double x, double y, double range) {
return gaus1(x, range) * gaus1(y, range);
}
double gaus3(double x, double y, double z, double range) {
return gaus2(x, y, range) * gaus1(z, range);
}
double gaus1_integrate(double range) {
auto func = std::bind(&A::gaus1, this, std::placeholders::_1, range);
return integral1D(func);
}
double gaus2_integrate(double range) {
auto func = std::bind(&A::gaus2, this, std::placeholders::_1,
std::placeholders::_2, range);
return integral2D(func);
}
double gaus3_integrate(double range) {
auto func = std::bind(&A::gaus3, this, std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3, range);
return integral3D(func);
}
};
int main() {
A a;
std::cout << "1D integral is " << a.gaus1_integrate(50) << "\n";
std::cout << "2D integral is " << a.gaus2_integrate(50) << "\n";
std::cout << "3D integral is " << a.gaus3_integrate(50) << "\n";
return 0;
}
以上示例有效并给出了预期结果。我知道不建议对具有更多维度的更复杂的函数进行黎曼求和(或等效),但很高兴看看上面描述的方法是否可行。
回答
根据 Guillaume 的有用建议,这里是 c++11 工作示例。需要进行少量修改。
#include <iostream>
#include <functional>
#include <cmath>
template<int n, typename Functor>
struct integralNDsubint {
double v;
const Functor &fn;
template<typename... Args>
double operator()(Args... rest) const {
return fn(v, rest...);
}
};
template <int n, typename Functor, typename std::enable_if<(n > 1), bool>::type = true>
double integralND(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) -> double {
auto subint = integralNDsubint<n, Functor> {v, fn};
// following block can be used in c++14 without
// need of helper struct integralNDsubint
//auto subint = [v, &fn](auto... rest) {
//return fn(v, rest...);
//};
return integralND<n - 1>(subint, steps);
};
return integralND<1>(subfn, steps);
}
template <int n, typename Functor, typename std::enable_if<(n == 1), bool>::type = true>
double integralND(Functor fn, size_t steps = 100) {
double sum = 0.;
double step = 1./steps;
for (size_t s = 0; s < steps; ++s) {
sum += fn(s * step);
}
return sum * step;
}
struct A
{
double gaus1(double x, double range) {
x = range * (2. * x - 1.);
return 2 * range * std::exp(- x*x);
}
double gaus2(double x, double y, double range) {
return gaus1(x, range) * gaus1(y, range);
}
double gaus3(double x, double y, double z, double range) {
return gaus2(x, y, range) * gaus1(z, range);
}
double gaus1_integrate(double range) {
auto func = [=](double x) { return gaus1(x, range); };
return integralND<1>(func);
}
double gaus2_integrate(double range) {
auto func = [=](double x, double y) { return gaus2(x, y, range); } ;
return integralND<2>(func);
}
double gaus3_integrate(double range) {
auto func = [=](double x, double y, double z) { return gaus3(x, y, z, range); } ;
return integralND<3>(func);
}
};
int main() {
A a;
std::cout << "1D integral is " << a.gaus1_integrate(50) << "\n";
std::cout << "2D integral is " << a.gaus2_integrate(50) << "\n";
std::cout << "3D integral is " << a.gaus3_integrate(50) << "\n";
return 0;
}
您的函数可以推广到任意数量的维度。
template <int n, typename Functor, std::enable_if_t<(n > 1), int> = 0>
double integralND(Functor fn, size_t steps = 100) {
auto subfn = [&](double v) -> double {
auto subint = [v, &fn](auto... rest) {
return fn(v, rest...);
};
return integralND<n - 1>(subint, steps);
};
return integralND<1>(subfn, steps);
}
template <int n, typename Functor, std::enable_if_t<(n == 1), int> = 0>
double integralND(Functor fn, size_t steps = 100) {
double sum = 0.;
double step = 1./steps;
for (size_t s = 0; s < steps; ++s) {
sum += fn(s * step);
}
return sum * step;
}
如您所见,它使用了通用的 lambda。为了与 C++11 保持兼容,您可以手动编写函数对象:
template<int n, typename F>
struct integralNDsubint {
double v;
F const& fn;
template<typename... Args>
auto operator()(Args... rest) const -> double {
return fn(v, rest...);
}
};
然后像那样使用它:
auto subint = integralNDsubint<n, Functor>{v, fn};
您还需要更改 sfinae:
template <int n, typename Functor, typename std::enable_if<(n > 1), int>::type = 0>
double integralND(Functor fn, size_t steps = 100) {
// ...
}
template <int n, typename Functor, typename std::enable_if<(n == 1), int>::type = 0>
double integralND(Functor fn, size_t steps = 100) {
// ...
}