在复杂的 class 中使用 boost::numeric::odeint 步进器

Using a boost::numeric::odeint stepper inside a complicated class

我有一个极其复杂的数值速率方程组,将由 class ElectronSolver 求解。电子态由单独的 class state_type 处理,此处未显示。 问题 class 的简化模板显示为

ElectronSolver.h

class ElectronSolver {
public:
    ElectronSolver(const char* filename, ofstream& log);
    void solve();
    void print(const std::string& fname);
    std::vector<double> T; // Times, in fs
    std::vector<state_type> Y; // stores the state_t's
private:
    //                       steps, State,   value,  Derivative, Time,   Algebra
    adams_bashforth_moulton< 5, state_type, double, state_type, double, vector_space_algebra > abm;

    void set_initial_conditions();
    // Model parameters
    PhotonFlux pf;

    void sys(const state_type& s, state_type& sdot, const double t);
};

ElectronSolver.cpp

void ElectronSolver::set_initial_conditions(){
    // Set the initial T such that pulse peak occurs at T=0
    T[0] = -timespan/2;
    Y[0] = state_type(Store, num_elec_points);
    abm.initialize( sys, Y[0], T[0], dt ); // This line produces an error
}

void ElectronSolver::sys(const state_type& s, state_type& sdot, const double t){
    // complicated system modifying sdot
    sdot.flux += pf(t)*s.flux;
}

但是,一些研究揭示了为什么标记的行会引发编译错误。 据我所知,此处声明的 sys 必须称为 "on a class",因此不能简单地作为引用传递。 This question 通过将 sys 声明为静态来解决这个问题,但这在这里不起作用,因为我依赖于在 sys.[=27 中调用 ElectronSolver 的其他成员=]

我需要多个 ElectronSolver 实例的理由很少,但我想保留该选项以防任何代码维护者想要两个不同的电子模型。

据我所知,我有四个选择:

  1. 满足 sys 需要的一切 static(由于 ElectronSolver 从其他 class 继承,因此不太理想,但可行)
  2. sys 函数构造某种包装器(可能会影响性能,更重要的是,我不知道该怎么做)
  3. 自己实现 ODE 步进器以避免使用 boost 的麻烦。
  4. ????

哪种解决方案最划算 - 性能(虽然最大的性能瓶颈是 可能 执行所花费的时间 sys - 代码优雅 - 模块化 ?

您是否要继续使用 boost 是您唯一可以做出的决定,但是创建一个不影响性能的包装器很容易。

您需要将 sys 包装在捕获 this 的 lambda 中。这应该不会影响性能,因为当打开优化时,lambda 将被内联。

你可以这样称呼它:

abm.initialize(
    [this](const state_type& s, state_type& sdot, const double t) {
        this->sys(s, sdot, t);
    },
    Y[0],
    T[0],
    dt
);

lambda 基本上是一个隐式包装结构,它包含对 this 的引用并定义 operator()(const state_type& s, state_type& sdot, const double t).

我创建了一个 example in godbolt 来展示这一点,在示例需要的地方简化并填写您的代码。如果您更改 -O0-O3 之间的优化,您可以看到 lambda 代码被剥离并且内部方法被完全内联。

另一种选择是使用 std::bind 从成员函数中创建一个裸函数:

abm.initialize(
    std::bind(&ElectronSolver::sys, std::ref(*this), _1, _2, _3),
    Y[0],
    T[0],
    dt
);

连同其他地方的这个:

#include <functional>
using namespace std::placeholders;

将所有符号放入范围。 std::bind 创建一个新函数,其中一些参数已经填充。在这种情况下,隐式第一个参数,即对象本身,用对 this 的引用填充。其余参数被赋予特殊的占位符,以指示新函数将用其参数填充它们。 std::ref 强制通过引用而不是复制来获取 this。此方法也将具有零开销。