我正在尝试实现2维 n -body模拟的OpenMP版本。
但是存在一个问题:我假设每个粒子的初始速度和加速度都为零。当颗粒首先聚集在一起时,它们会高速散开,不再聚集。
这似乎与牛顿法一致,对吧?
有人可以解释为什么会发生这种情况以及如何解决错误吗?
以下是我的代码的一部分:
/* update one frame */
void update() {
int i, j;
omp_set_num_threads(8);
# pragma omp parallel private(j)
{
# pragma omp for schedule(static)
for ( i = 0; i < g_N; ++i ) {
g_pv[i].a_x = 0.0;
g_pv[i].a_y = 0.0;
for ( j = 0; j < g_N; ++j ) {
if (i == j)
continue;
double r_2 = pow((g_pv[i].pos_x - g_pv[j].pos_x),2) + pow((g_pv[i].pos_y - g_pv[j].pos_y),2);
g_pv[i].a_x += (-1) * G * g_pv[j].m * (g_pv[i].pos_x - g_pv[j].pos_x) / (pow(r_2 + e,1.5));
g_pv[i].a_y += (-1) * G * g_pv[j].m * (g_pv[i].pos_y - g_pv[j].pos_y) / (pow(r_2 + e,1.5));
}
g_pv[i].v_x += period * g_pv[i].a_x;
g_pv[i].v_y += period * g_pv[i].a_y;
}
# pragma omp for schedule(static)
for ( int i = 0; i < g_N; ++i ) {
g_pv[i].pos_x += g_pv[i].v_x * period;
g_pv[i].pos_y += g_pv[i].v_y * period;
}
}
}
不要担心OpenMP,只需将其视为顺序版本即可。 OpenMP不会对结果产生太大影响。
编辑:澄清一下,这是整个代码(这部分可能有一些错误,但我描述的问题应该出现在上面的代码部分)
# include <iostream>
# include <fstream>
# include <iomanip>
# include <cmath>
# include <vector>
# include <cstdlib>
# include <omp.h>
# include <GL/glew.h>
# include <GL/freeglut.h>
# include <GL/gl.h>
using namespace std;
/* the size of the opengl window */
# define WIDTH 2000
# define HEIGHT 2000
/* define the global constants */
const double G = 6.67 * pow(10, -11);
// const double G = 6.67;
const double e = 0.00001;
const double period = 1;
/* define the structure of particle */
struct particle
{
double m;
double pos_x;
double pos_y;
double v_x;
double v_y;
double a_x;
double a_y;
particle(double m = 0, double pos_x = 0, double pos_y = 0,
double v_x = 0, double v_y = 0, double a_x = 0, double a_y = 0)
{
this->m = m;
this->pos_x = pos_x;
this->pos_y = pos_y;
this->v_x = v_x;
this->v_y = v_y;
this->a_x = a_x;
this->a_y = a_y;
}
};
/* define the global data */
int g_N; // number of particles
vector<particle> g_pv; // particle vector
void setUp();
void update();
void display();
int main(int argc, char ** argv) {
/* set up the window */
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (WIDTH, HEIGHT);
glutInitWindowPosition (100, 100);
glutCreateWindow ("openmp");
/* initialize */
setUp();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
/* read the input data */
void setUp() {
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
/* Sets a 2d projection matrix
* (0,0) is the lower left corner (WIDTH, HEIGHT) is the upper right */
glOrtho (0, WIDTH, 0, HEIGHT, 0, 1);
glDisable(GL_DEPTH_TEST);
ifstream inFile;
inFile.open("input_25.txt");
inFile >> g_N;
g_pv.resize(g_N);
for ( int i = 0; i < g_N; ++i )
{
inFile >> g_pv[i].m >> g_pv[i].pos_x >> g_pv[i].pos_y
>> g_pv[i].v_x >> g_pv[i].v_y >> g_pv[i].a_x >> g_pv[i].a_y;
}
inFile.close();
}
/* display in openGL */
void display(void) {
glClear(GL_COLOR_BUFFER_BIT);
for(int i = 0; i < g_pv.size(); ++i) {
/* Get the ith particle */
particle p = g_pv[i];
/* Draw the particle as a little square. */
glBegin(GL_QUADS);
glColor3f (1.0, 1.0, 1.0);
glVertex2f(p.pos_x + 2, p.pos_y + 2);
glVertex2f(p.pos_x - 2, p.pos_y + 2);
glVertex2f(p.pos_x - 2, p.pos_y - 2);
glVertex2f(p.pos_x + 2, p.pos_y - 2);
glEnd();
}
update();
glutPostRedisplay();
glFlush ();
}
/* update one frame */
void update() {
int i, j;
omp_set_num_threads(8);
# pragma omp parallel private(j)
{
/* compute the force */
# pragma omp for schedule(static)
for ( i = 0; i < g_N; ++i ) {
g_pv[i].a_x = 0.0;
g_pv[i].a_y = 0.0;
for ( j = 0; j < g_N; ++j ) {
if (i == j)
continue;
double r_2 = pow((g_pv[i].pos_x - g_pv[j].pos_x),2) + pow((g_pv[i].pos_y - g_pv[j].pos_y),2);
g_pv[i].a_x += (-1) * G * g_pv[j].m * (g_pv[i].pos_x - g_pv[j].pos_x) / (pow(r_2 + e,1.5));
g_pv[i].a_y += (-1) * G * g_pv[j].m * (g_pv[i].pos_y - g_pv[j].pos_y) / (pow(r_2 + e,1.5));
}
g_pv[i].v_x += period * g_pv[i].a_x;
g_pv[i].v_y += period * g_pv[i].a_y;
}
/* compute the velocity */
# pragma omp for schedule(static)
for ( int i = 0; i < g_N; ++i ) {
g_pv[i].pos_x += g_pv[i].v_x * period;
g_pv[i].pos_y += g_pv[i].v_y * period;
}
}
}
答案 0 :(得分:7)
我正在将我的评论扩展到答案(正如Z boson所建议的那样),并提出了一些关于如何解决问题的建议。
这个问题确实属于Computational Science.SE,因为我认为代码本身没有任何问题,但算法似乎有问题:随着粒子越来越近,你可以获得{ {1}}。这很大。非常非常大(与您的时间步长相比)。为什么?假设你有两个行星,一个坐在(0,0),一个小坐在(0,1),后者对前者的加速度非常大。在下一步中,小行星将处于(0,-100)或其他任何位置,并且其上的力为零,这意味着它将永远不会返回,现在也具有相当大的速度。您的模拟有blown up。
如你所说,这与牛顿定律并不相符,所以这表明你的数值方案已经失败了。不用担心,有几种方法可以解决这个问题。您添加G / pow(e,1.5) ~ G * 1e7
时已经预料到了这一点。只要把它放大,说e
,你应该没事。或者,设置一个非常小的时间步长10
。如果粒子太接近或者如果行星碰撞会发生什么(可能是爆炸和消失,或者抛出异常),你也可以不计算力量。或者说具有r - 2 潜力或类似物的排斥力:
period
这类似于现象学Lennard-Jones interaction如何结合由泡利不相容原理引起的排斥。请注意,您可以增加排斥的清晰度(如果您愿意,可以将g_pv[i].a_y += (-1) * G * g_pv[j].m * (g_pv[i].pos_y - g_pv[j].pos_y) * (1 / pow(r_2 + e,1.5) - 1 / pow(r_2 + e,2.5));
更改为2.5
),这意味着排斥效果远远不够(好),但它需要是更准确地解决,导致12.5
(差)更小。理想情况下,您将从不会导致冲突的初始配置开始,但这几乎是不可能预测的。最后,您可能希望使用上面列出的方法的组合。
使用OpenMP可能会导致轻微的加速,但您应该使用算法来实现远程强制,例如Barnes-Hut simulation。有关详情,请参阅最近发展中发布的2013 Summer School on Fast Methods for Long Range Interactions in Complex Particle Systems和booklet(免费提供)。您也可能不希望每次显示步骤:科学模拟通常会保存每5000步左右。如果你想看漂亮的电影,你可以插入一些移动平均线来消除噪音(你的模拟没有温度或任何类型的东西,所以没有平均你可能没问题)。此外,我不确定您的数据结构是否针对作业进行了优化,或者您是否遇到了缓存未命中问题。我不是一个真正的专家,所以也许其他人可能想要对此进行权衡。最后,请考虑不使用period
,而是使用fast inverse square root或类似方法。
答案 1 :(得分:4)
第一个明显的问题是你使用的是简单的“泰勒整合方案”。关键是,既然你正在逼近无限小的 dt ,那么对于你period
的有限时间差Δt,你正在扩展众所周知的运动方程在一个Taylor expansion中,有无限的条款....你正在截断。
一般来说:
x t +Δt = x t + x ' t Δt+ 1/2 x “ t Δt 2 + 1/6 x ' “ t Δt 3 + O(Δt 4 )
x ' t 时间t的一阶导数,速度 v ; x “ t 二阶导数,加速度 a ,......; O(Δt 4 )是错误的顺序 - 在这个例子中,我们在3 rd 顺序截断扩展,我们有一个 local < / em> 4 th 的错误。
在您使用的情况下(Euler方法):
x t +Δt = x t + v t Δt+ O(Δt 2 )
和
v t +Δt = v t + a t Δt+ O(Δt 2 )
在这种情况下,由于您要将扩展停止到第一项,因此这是一阶近似。你将得到一个O阶的局部误差(Δt 2 ),它对应于阶数为O(Δt)的全局误差。这就是截断错误的行为 - 有关详细信息,请参阅此reference。
我非常清楚两种不同的集成方案,它们改进了Euler方法:
我知道Runge-Kutta方法(找到引用的两篇文章中的参考文献)甚至是高阶,但我从未亲自使用过它们。
我在这里说的顺序是截断的截断顺序,与截断本身执行的错误顺序严格相关。
Leapfrog方法是二阶的。
Verlet方法是三阶的,局部误差为O(Δt 4 )。这是一种三阶方法,即使您没有看到任何三阶导数,因为它们在推导中被抵消,如wikipedia reference所示。
更高阶的积分方案可让您获得更精确的轨迹,而不会缩短时间步长Δt。
然而,这些集成方法所具有的最重要的属性,在简单的Taylor正向积分上缺失,无论截断顺序如何,都是:
从参考文献中你会发现很多材料需要学习,但是一本好的N-body书可以让你以更有条理的方式学习它。
关于两种方案之间非常重要的区别的最后一点说明:Verlet方法不能自动提供速度,需要作为后续步骤进行评估(直接)。另一方面,Leapfrog方法会评估位置和速度,但在不同的时间 - 使用此方案,您无法同时评估这两个数量。
一旦你有一个好的集成方案,你需要担心你能够容忍的最大错误是什么,此时需要进行一些错误分析,然后你需要实现多时间尺度的方法来准确即使对于O(Δt 4 )或更大(考虑到重力吊索),两个物体太近的情况下的积分也是如此。
它们可以是全局的(即,在任何地方减少period
)或局部的(仅针对系统的某些分区,粒子太近)...但此时你应该能够去找自己更多的参考文献。