在 C++ 中向粒子添加力列表

Adding a list of forces to a particle in c++

我对 C++ 有点陌生,如果这是一个愚蠢的问题,我深表歉意。

我有一个表示粒子系统中粒子的结构。除了位置、速度和质量等标准内容外,我还想给它一个力列表,这样每个力都是一个函数,我将粒子传递给它,并基于粒子的当前状态(或不), 这个函数 returns 一个力向量。理想情况下,我会总结每个这样的力矢量的结果以获得净力,然后我可以用它来计算下一个刻度的粒子速度。

这就是我希望粒子看起来的样子

struct particle {
    double mass;

    // position
    double x, y, z;

    // velocity
    double dx, dy, dz;


    std::list<function> forces;
};

现在我的问题是:我可以在不实现通用力基础 class 的情况下执行此操作吗?它实现了计算力的函数?有没有一种方法可以指定具有相同调用签名的函数列表?

如果你能保证所有的函数都有相同的方法签名,那么你就可以使用模板化的function[cppreference.com] class。我修改了您的示例以说明如何使用它。

#include <functional>
#include <list>
#include <cmath>

using namespace std;

struct particle 
{
    double mass;

    // position
    double x, y, z;

    // velocity
    double dx, dy, dz;

    // A list of forces that take a particle and return a double
    // The space between the two > symbols is needed in pre-c++11 compilers.
    list<function<double(const particle&)> > forces;
};

// An example function to calculate the force due to gravity.
double gravity(const particle& p)
{
    return p.mass * -9.8;
}

// Making something up for air resistance
double resistance(const particle& p)
{
    return 0.1 * sqrt(p.dx * p.dx + p.dy * p.dy + p.dz * p.dz);
}

int main()
{
    particle p;
    p.mass = 10;
    p.x = 0;
    p.y = 100;
    p.z = 0;

    p.dx = 0;
    p.dy = 0;
    p.dz = 0;
    p.forces.push_back(gravity);
    p.forces.push_back(resistance);
}

如果您要处理三维力,您可能需要 return 类型的更多信息,而不仅仅是双精度,但这应该是一个很好的起点。此外,如果您有兼容 C++11 的编译器,您可能还需要查找 lambda functions 以便可以在同一行中创建函数。

您可以使用 std::function。像这样:

// define a type for the function
typedef std::function< void(particle const&, float *fxfyfz) > forcer_function;
std::vector< forcer_function > forces;

现在,关于性能的一些话。由于这是您正在谈论的粒子,我假设您有相当多的粒子(例如至少数百个)。所以我假设您有兴趣使此代码以 运行 适度快速。

因此,首先,由于缓存属性不佳,不建议对您的容器使用 std::list。它会使您的代码 大大 变慢。因此,使用std::vector.

其次,将力列表添加为粒子结构的成员并不常见。你真的想对每个粒子使用不同的力吗?通常,有 <5 个力和 >100-1000 个粒子。如果您可以对所有粒子使用相同的力集合,那么将力从粒子中移出 class 会给您带来收益。例如,

struct particle
{
    double mass;
    // position
    double x, y, z;
    // velocity
    double dx, dy, dz;
};
struct particle_container
{
    std::vector< particle > particles;
    std::vector< forcer_function > forces;

    void update();
};

void particle_container::update()
{
    for(particle &p : particles) {
        double rx, ry, rz;
        rx = ry = rz = 0.0;
        for(forcer_function fn : forces) {
            double f[3];
            fn(p, &f[0]);
            rx += f[0];
            ry += f[1];
            rz += f[2];
        }
        // integrate resulting force, etc
        // ...
    }
}

如果你真的想使用每粒子力,你仍然可以使用我上面概述的方法,将具有相同力集合的粒子分组到不同的容器对象中。然后你就可以重复使用上面的所有东西,再加一个 class 就可以解决:

struct particle_groups
{
    std::vector< particle_container > groups;
    void update();
};
void particle_groups::update()
{
    for(auto &g : groups) {
        g.update();
    }
};

如果您真的非常不想分组,那么至少要考虑是否有一种方法可以使用粒子成员将无效力归零。那么你仍然可以使用上面的方法。例如,像这样:

struct particle
{
    double mass;
    // position
    double x, y, z;
    // velocity
    double dx, dy, dz;

    // is gravity active? either 1.0 or 0.0
    double grav;
    // is player interaction active? either 1.0 or 0.0
    double player;
    // etc... for all possible forces
};

然后只需将得到的重力乘以粒子的重力成员,然后根据 particle.grav 的值是 1.0 还是 0.0,有效地关闭或打开该粒子的重力。

最后,std::function慢了。您可以混合使用上述两种方法并使用单个函数。像这样:

struct particle
{
    double mass;
    // position
    double x, y, z;
    // velocity
    double dx, dy, dz;
};
struct force_settings
{
    double grav;
    double attractor;
    double player;
    //etc...
};
struct particle_container
{
    // no need to keep pointers to functions
    force_settings forces;
    std::vector< particle > particles;

    void update();
    void compute_forces(particle const& p, double *rf) const
    {
        // zero resulting force
        rf[0] = rf[1] = rf[2] = 0.0;
        // compute gravity, (assume y axis)
        rf[1] += forces.grav * 9.8; // will be either 9.8 or 0.0
        // compute attractor
        double ax = p.x - attractor.x;
        double ay = p.y - attractor.y;
        double az = p.z - attractor.z;
        rf[0] += forces.attraction * ax*ax;
        rf[1] += forces.attraction * ay*ay;
        rf[2] += forces.attraction * az*az;
        // etc... more forces here
   }
};

void particle_container::update()
{
    for(particle &p : particles) {
        double rf[3];
        compute_forces(p, &rf);
        // integrate, etc...
    }
}