从参数列表生成C++函数
Generating a C++ function from a list of argument
我正在编写一个函数f,用于龙格-库塔积分器。
output RungeKutta(function f, initial conditions IC, etc.)
由于函数会被多次调用,我正在寻找一种在编译时生成函数f的方法。
在这种情况下,函数f取决于参数向量p的固定列表,其中p是稀疏的,并且在编译代码之前是固定的。具体来说,
double function f(vector<double> x) {
return x dot p;
}
由于p
是稀疏的,所以在f
中取点积不是最有效的。硬编码x dot p
似乎是可行的,但p可能很长(1000)。
我有什么选择?
我唯一的选择是编写另一个程序(以p
作为输入)来生成.cpp文件吗?
谢谢你的评论。下面是微分方程的一个更具体的例子。
dy/dx=f_p(x)
f_p(x)的一个例子:
p=[0,1,0];x=[x1,x2,x3]
double f_p(vector<double> x) {
return x2; // This is what I meant by hard-coding
}
而不是:
double f(vector<double> p, vector<double> x) {
double r = 0;
for (i=0; i < p.length(); i++) {
r += p[i]*x[i];
}
return r;
}
您试图解决的关键问题是,在给定问题域的情况下,计算中会被多次调用的"leaf"函数通常也不会起作用。希望冗余工作(即将一个值与编译时已知为零的数组元素相乘)可以作为编译时步骤的一部分进行折叠。
C++有处理这一问题的语言设施,即模板元编程。C++模板非常强大(即图灵完备),允许基于编译时间常数进行递归计算。
下面是一个如何使用模板和模板专业化实现示例的示例(您也可以在这里找到我创建的一个可运行的示例http://ideone.com/BDtBt7)。代码背后的基本思想是生成一个带有静态函数的类型,该函数返回值的输入向量和编译时常数数组的点积。静态函数递归地调用自身的实例,在元素的输入/常量数组中移动时传递较低的索引值。它还以正在计算的编译时常数数组p中的值是否为零为模板。如果是,我们可以跳过计算,然后转到递归中的下一个值。最后,有一个基本情况,一旦我们到达数组中的第一个元素,就会停止递归。
#include <array>
#include <iostream>
#include <vector>
constexpr std::array<double, 5> p = { 1.0, 0.0, 3.0, 5.0, 0.0 };
template<size_t index, bool isZero>
struct DotProductCalculator
{
static double Calculate(const std::vector<double>& xArg)
{
return (xArg[index] * p[index])
+ DotProductCalculator<index - 1, p[index - 1] == 0.0>::Calculate(xArg);
}
};
template<>
struct DotProductCalculator<0, true>
{
static double Calculate(const std::vector<double>& xArg)
{
return 0.0;
}
};
template<>
struct DotProductCalculator<0, false>
{
static double Calculate(const std::vector<double>& xArg)
{
return xArg[0] * p[0];
}
};
template<size_t index>
struct DotProductCalculator<index, true>
{
static double Calculate(const std::vector<double>& xArg)
{
return 0.0 + DotProductCalculator<index - 1, p[index - 1] == 0.0>::Calculate(xArg);
}
};
template<typename ArrayType>
double f_p_driver(const std::vector<double>& xArg, const ArrayType& pAsArgument)
{
return DotProductCalculator<std::tuple_size<ArrayType>::value - 1,
p[std::tuple_size<ArrayType>::value -1] == 0.0>::Calculate(xArg);
}
int main()
{
std::vector<double> x = { 1.0, 2.0, 3.0, 4.0, 5.0 };
double result = f_p_driver(x, p);
std::cout << "Result: " << result;
return 0;
}
你在评论中说,p实际上是矩阵的一行或一列,并且矩阵是稀疏的。我不熟悉你正在解决的具体物理问题,但通常情况下,稀疏矩阵有某种固定的对角线"带状"结构,例如:
| a1 b1 0 0 0 0 0 d1 |
| c1 a2 b2 0 0 0 0 0 |
| 0 c2 a3 b3 0 0 0 0 |
| 0 0 c3 a4 b4 0 0 0 |
| 0 0 0 c4 a5 b5 0 0 |
| 0 0 0 0 c5 a6 b6 0 |
| 0 0 0 0 0 c6 a7 b7 |
| e1 0 0 0 0 0 c7 a8 |
存储此类矩阵的最有效方法往往是将对角线存储为数组/向量,因此:
A = [a1, a2, a3, a4, a5, a6, a7, a8]
B = [b1, b2, b3, b4, b5, b6, b7]
C = [c1, c2, c3, c4, c5, c6, c7]
D = [d1]
E = [e1]
将行矢量X = [x1, x2, x3, x4, x5, x6, x7, x8]
乘以上述矩阵,就变成:
Y = X . M
Y[0] = X[0] * A[0] + X[1] * C[0] + X[7] * E[0]
Y[1] = X[0] * B[0] + X[1] * A[1] + X[2] * C[1]
等等。
或者更一般地说:
Y[i] = X[i-7] * D[i] + X[i-1] * B[i] + X[i] * A[i] + X[i+1] * C[i] + X[i+7] * E[i]
其中超出范围的数组访问(< 0
或>= 8
应被视为求值为0。为了避免在任何地方都测试越界,实际上可以将每个对角线和向量本身存储在超大数组中,这些数组的前导元素和尾部元素都填充了零。
请注意,由于所有阵列访问都是线性的,因此这也将具有很高的缓存效率。
在给定约束的情况下,我将创建一个自定义函数对象,该对象存储矩阵p
,并在其函数调用运算符中计算运算。我将实现该函数的两个版本:一个版本在构造时对矩阵进行预处理,以"知道"非零元素在哪里,另一个版本只执行所述操作,接受许多计算只产生0
。引用的10%非零元素的数量听起来可能过于密集,不利于利用稀疏性来获得回报。
忽略p
是一个矩阵,并将其用作向量,没有预处理的版本将是这样的:
class dotProduct {
std::vector<double> p;
public:
dotProduct(std::vector<double> const& p): p(p) {}
double operator()(std::vector<double> const& x) const {
return std::inner_product(p.begin(), p.end(), x.begin());
}
};
// ...
... RungeKutta(dotProduct(p), initial conditions IC, etc.);
当使用C++11时,可以使用lambda函数:
... RungeKutta([=](std::vector<double> const& x) {
return std::inner_product(p.begin(), p.end(), x.begin());
}, intitial conditions IC, etc.);
对于预处理版本,您将存储一个std::vector<std::pair<double, std::size_t>>
,指示哪些索引实际上需要相乘:
class sparseDotProduct {
typedef std::vector<std::pair<double, std::size_t>> Vector;
Vector p;
public:
sparsedotProduct(std::vector<double> const& op) {
for (std::size_t i(0), s(op.size()); i != s; ++i) {
if (op[i]) {
p.push_back(std::make_pair(op[i], i));
}
}
}
double operator()(std::vector<double> const& x) {
double result(0);
for (Vector::const_iterator it(p.begin()), end(p.end()); it != end; ++it) {
result += it->first * x[it->second];
}
return result;
}
};
这个函数对象的使用是一样的,尽管如果p
不变,保留这个对象可能是合理的。
我个人认为,如果有10%的非零值,非稀疏版本实际上会优于稀疏版本。然而,有了这两个版本,衡量不同方法的性能应该相对简单。我不认为自定义创建的代码会更好,尽管它可以改进计算。如果是这样的话,使用元编程技术来创建代码可能会,但我怀疑这太实用了。
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 继承函数的重载解析
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- C++模板来检查友元函数的存在
- 递归函数计算序列中的平方和(并输出过程)
- 对RValue对象调用的LValue ref限定成员函数
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 为什么使用 "this" 指针调用派生成员函数?
- 将对象数组的引用传递给函数
- 函数调用中参数的顺序重要吗
- 函数向量_指针有不同的原型,我可以构建一个吗
- 使用不带参数的函数访问结构元素
- 代码在main()中运行,但在函数中出现错误
- 内置函数可查看CPP中的成员变量
- 如何获取std::result_of函数的返回类型
- 如何在c++中为模板函数实例创建快捷方式
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗