Подтвердить что ты не робот

Моделирование N тела в С++

Я пытаюсь реализовать версию OpenMP двумерного моделирования n-body.

Но есть проблема: я предполагаю, что начальная скорость и ускорение каждой частицы равны нулю. Когда частицы сначала собираются вместе, они рассеиваются на высокой скорости и не собираются снова.

Это не похоже на Закон Ньютона, верно?

Может кто-нибудь объяснить, почему это происходит и как я могу исправить ошибку?

Вот часть моего кода:

/* 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;
            }
    }
}
4b9b3361

Ответ 1

Я расширяю свой комментарий до ответа (как предложено Z boson) с несколькими предложениями относительно того, как вы можете решить эту проблему.

Этот вопрос действительно относится к Computational Science.SE, так как я не думаю, что в коде есть что-то неправильное, но алгоритм кажется ошибочным: По мере приближения частиц вы можете получить силу G / pow(e,1.5) ~ G * 1e7. Это большой. Очень, очень большой (по сравнению с вашим шагом времени). Зачем? Предположим, у вас две планеты, одна из которых сидит в (0, 0) и маленькая в (0, 1), где последняя получает очень большое ускорение по отношению к первому. На следующем шаге малая планета будет находиться на (0, -100) или что угодно, и сила на ней равна нулю, а это значит, что она никогда не вернется и теперь имеет значительную скорость. Ваше моделирование взорвано.

Как вы сказали, это не слишком хорошо соответствует законам Ньютона, поэтому это означает, что ваша численная схема не удалась. Не волнуйтесь, есть несколько способов исправить это. Вы уже ожидали столько, сколько добавили e. Просто сделайте это больше, скажите 10, и вы должны быть в порядке. В качестве альтернативы установите очень малую отметку времени, period. Вы также можете просто не вычислять силу, если частицы слишком близки или имеют какую-то эвристику относительно того, что должно произойти, если планеты сталкиваются (возможно, взрываются и исчезают, или бросают исключение). Или скажите силы отталкивания с потенциалом r - 2 или что-то: 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, если хотите), что означает, что отталкивание оказывает меньшее влияние (хорошее), но что его нужно решать более точно, что приводит к меньшему period (плохому). В идеале вы должны начать с начальной конфигурации, которая не приведет к столкновениям, но это невозможно предсказать. В конце концов, вы, вероятно, захотите использовать комбинацию методов, перечисленных выше.

Использование OpenMP может привести к небольшому ускорению, но вы действительно должны использовать алгоритмы для дальних сил, такие как Моделирование Barnes-Hut. Более подробно см., Например, Летняя школа 2013 года по быстрым методам дальнего взаимодействия в сложных системах частиц и (доступно бесплатно), они опубликованы в самых последних событиях. Вы также можете не отображать каждый шаг: научное моделирование обычно сохраняет каждый 5000-й шаг или около того. Если вы хотите красивые фильмы, вы можете интерполировать некоторые скользящие средние, чтобы сгладить шум (у вашей симуляции нет температуры или чего-то подобного, поэтому вы, вероятно, будете в порядке, не усредняя). Кроме того, я не уверен, что ваши структуры данных оптимизированы для задания или если вы можете запускать пропуски кеша. Я не эксперт, поэтому, возможно, кто-то может захотеть взвесить это. Наконец, не стоит использовать pow, а скорее быстрый обратный квадратный корень или аналогичные методы.

Ответ 2

Первая очевидная проблема заключается в том, что вы используете простую "схему интеграции Тейлора". Дело в том, что, поскольку вы приближаетесь к бесконечно малой dt до конечной разности времени & Delta; t, которая является вашим period, вы расширяете известные уравнения движения в Расширение Тейлора, с бесконечными терминами.... что вы усекаете.

В общем:

x t + & Delta; t= x t + x & prime t & Delta; t + 1/2 x & Prime; t & Delta; t 2 + 1/6 x & prime; & Prime; t & Delta; t 3 + O (& Delta; t 4)

x & prime; t первая производная в момент времени t, скорость v; x & Prime; t вторая производная, ускорение a,...; O (& Delta; t 4) - это порядок ошибки. В этом примере мы усекаем разложение по порядку 3 rd и мы имеем локальную ошибку 4 th.

В вашем случае вы используете (метод Эйлера):

x t + & Delta; t= x t + v t & Delta; t + O (& Delta; t 2)

и

v t + & Delta; t= v t + a t & Delta; t + O (& Delta; t 2)

В этом случае, поскольку вы останавливаете расширение до первого слагаемого, это приближение первого порядка. вы получите локальную ошибку порядка O (& Delta; t 2), что соответствует глобальной ошибке порядка O (& Delta; t). То, как ведет себя ошибка усечения, см. В этой ссылке для более подробной информации.

Я хорошо знаю две разные схемы интеграции, которые улучшают метод Эйлера:

и я знаю методы Рунге-Кутты (найти ссылки из двух цитированных статей), которые даже имеют более высокий порядок, но я никогда не использовал их лично.

Порядок, о котором я говорю здесь, - это порядок усечения аппроксимации, строго связанный с порядком ошибки, выполняемой с самой усечкой.

Метод Leapfrog имеет второй порядок.

Метод Verlet имеет третий порядок, с локальной ошибкой O (& Delta; t 4). Это метод третьего порядка, даже если вы не видите никакой производной третьего порядка, поскольку они отменяются при выводе, как показано в ссылка на wikipedia.

Схема интеграции более высокого порядка позволяет получить более точные траектории, не сокращая время-шаг, & Delta; t.

Однако наиболее важные свойства, которые эти методы интеграции имеют, отсутствующие в простом интеграле Taylor forward, независимо от порядка усечения, следующие:

  • обратимость
  • они Symplectic, то есть они сохраняют энергию

Из ссылок вы найдете много материала, чтобы изучить его, но хорошая книга N-body позволит вам изучить ее более структурированным способом.

Просто последнее замечание о очень важном различии между двумя схемами: метод Verlet не дает автоматически скорости, которые необходимо оценить (прямо) в качестве последующего шага. С другой стороны, метод Leapfrog оценивает как положения, так и скорости, но в разное время - с этой схемой вы не можете одновременно оценивать обе величины.

После того, как у вас есть хорошая схема интеграции, вам нужно беспокоиться о том, какую максимальную ошибку вы можете терпеть, в этот момент необходим некоторый анализ ошибок, а затем вам нужно будет внедрить методы многократного масштаба, чтобы иметь точные интеграция даже в случае, если два объекта слишком близки даже для O (& Delta; t 4) или больше (мышление для гравитационных стропов).

Они могут быть глобальными (т.е. уменьшать period везде) или локальными (делать это только для некоторого раздела системы, где частицы слишком близки)... но на этом этапе вы должны быть в состоянии пойти и найдите больше ссылок.

Ответ 3

Простым способом решения, когда частицы близки друг к другу, является использование линейного уравнения силы. Вместо того, чтобы F = k * m1 * m2 / r² это может быть F = k * m1 * m2 * r то, что оказывается правильным, если вы предполагаете, что частицы имеют конечный размер и могут идти один в другой. Расстояние, когда это уравнение выполняется, - это радиус частицы.