Моделирование жидкости взрывается

Следующее моделирование жидкости является переводом статьи Стама. Произошло нечто поистине ужасное. Каждый раз, когда программа запускается с низким значением DIFF=0.01, значения начинаются с малого, а затем быстро увеличиваются или "взрываются". Я тщательно проверил математические процедуры. Поскольку код начинается с одного 0.5, математически он умножается и добавляет кучу нулей, поэтому конечный результат должен быть близок к нулевой плотности и другим векторам.

Код довольно длинный, поэтому я разделил его на куски и удалил лишний код. За вычетом всего начала и SDL-кода всего около 120 строк. Я потратил несколько часов, пытаясь внести изменения, но безрезультатно, поэтому помощь очень ценится.

После некоторых экспериментов я считаю, что может быть какая-то ошибка с плавающей запятой, когда DIFF установлено слишком низко. Когда значение увеличивается с 0.01 до 0.02, значения не увеличиваются. Хотя я не думаю, что это вся проблема.

Для ясности: текущие ответы 1201ProgramAlarm и vidstige не решают проблему.

Разделы, выделенные жирным шрифтом, являются важными частями, остальные предназначены для полноты.


Начало, пропустить

#include <SDL2/SDL.h>
#include <stdio.h>
#include <iostream>
#include <algorithm>


#define IX(i,j) ((i)+(N+2)*(j))
using namespace std;

// Constants
const int SCREEN_WIDTH = 600;
const int SCREEN_HEIGHT = 600;  // Should match SCREEN_WIDTH
const int N = 20;               // Grid size
const int SIM_LEN = 1000;
const int DELAY_LENGTH = 40;    // ms

const float VISC = 0.01;
const float dt = 0.1;
const float DIFF = 0.01;

const bool DISPLAY_CONSOLE = false; // Console or graphics
const bool DRAW_GRID = false; // implement later

const int nsize = (N+2)*(N+2);

Математические подпрограммы Рассеянные подпрограммы делятся на 1+4*a. Означает ли это, что плотность должна быть ‹= 1?

void set_bnd(int N, int b, vector<float> &x)
{
    // removed
}


inline void lin_solve(int N, int b, vector<float> &x, vector<float> &x0, float a, float c)
{
    for (int k=0; k<20; k++)
    {
        for (int i=1; i<=N; i++)
        {
            for (int j=1; j<=N; j++)
            {
                x[IX(i,j)] = (x0[IX(i,j)] + a*(x[IX(i-1,j)]+x[IX(i+1,j)]+x[IX(i,j-1)]+x[IX(i,j+1)])) / c;
            }
        }
        set_bnd ( N, b, x );
    }
}

// Add forces
void add_source(vector<float> &x, vector<float> &s, float dt)
{
    for (int i=0; i<nsize; i++) x[i] += dt*s[i];
}

// Diffusion with Gauss-Seidel relaxation
void diffuse(int N, int b, vector<float> &x, vector<float> &x0, float diff, float dt)
{
    float a = dt*diff*N*N;
    lin_solve(N, b, x, x0, a, 1+4*a);

}

// Backwards advection
void advect(int N, int b, vector<float> &d, vector<float> &d0, vector<float> &u, vector<float> &v, float dt)
{
    float dt0 = dt*N;
        for (int i=1; i<=N; i++)
        {
            for (int j=1; j<=N; j++)
            {
                float x = i - dt0*u[IX(i,j)];
                float y = j - dt0*v[IX(i,j)];
                if (x<0.5) x=0.5; if (x>N+0.5) x=N+0.5;
                int i0=(int)x; int i1=i0+1;
                if (y<0.5) y=0.5; if (y>N+0.5) y=N+0.5;
                int j0=(int)y; int j1=j0+1;

                float s1 = x-i0; float s0 = 1-s1; float t1 = y-j0; float t0 = 1-t1;
                d[IX(i,j)] = s0*(t0*d0[IX(i0,j0)] + t1*d0[IX(i0,j1)]) +
                             s1*(t0*d0[IX(i1,j0)] + t1*d0[IX(i1,j1)]);
            }
        }
        set_bnd(N, b, d);
    }
}

void project(int N, vector<float> &u, vector<float> &v, vector<float> &p, vector<float> &div)
{
    float h = 1.0/N;
    for (int i=1; i<=N; i++)
    {
        for (int j=1; j<=N; j++)
        {
            div[IX(i,j)] = -0.5*h*(u[IX(i+1,j)] - u[IX(i-1,j)] +
                                   v[IX(i,j+1)] - v[IX(i,j-1)]);
            p[IX(i,j)] = 0;
        }
    }
    set_bnd(N, 0, div); set_bnd(N, 0, p);

    lin_solve(N, 0, p, div, 1, 4);

    for (int i=1; i<=N; i++)
    {
        for (int j=1; j<=N; j++)
        {
            u[IX(i,j)] -= 0.5*(p[IX(i+1,j)] - p[IX(i-1,j)])/h;
            v[IX(i,j)] -= 0.5*(p[IX(i,j+1)] - p[IX(i,j-1)])/h;
        }
    }
    set_bnd(N, 1, u); set_bnd(N, 2, v);
}

Решатель плотности и скорости

// Density solver
void dens_step(int N, vector<float> &x, vector<float> &x0, vector<float> &u, vector<float> &v, float diff, float dt)
{
    add_source(x, x0, dt);
    swap(x0, x); diffuse(N, 0, x, x0, diff, dt);
    swap(x0, x); advect(N, 0, x, x0, u, v, dt);
}

// Velocity solver: addition of forces, viscous diffusion, self-advection
void vel_step(int N, vector<float> &u, vector<float> &v, vector<float> &u0, vector<float> &v0, float visc, float dt)
{
    add_source(u, u0, dt); add_source(v, v0, dt);
    swap(u0, u); diffuse(N, 1, u, u0, visc, dt);
    swap(v0, v); diffuse(N, 2, v, v0, visc, dt);
    project(N, u, v, u0, v0);
    swap(u0, u); swap(v0, v);
    advect(N, 1, u, u0, u0, v0, dt); advect(N, 2, v, v0, u0, v0, dt);
    project(N, u, v, u0, v0);
}

Я рассмотрел несоответствия с плавающей запятой, но после компиляции с -ffloat-store проблема осталась.


person qwr    schedule 27.11.2015    source источник
comment
Попробуйте запустить программу под valgrind. Он может автоматически сказать вам, что не так. Я предполагаю, что вы на Linux ....   -  person John Zwinck    schedule 27.11.2015
comment
Откройте два отдельных экземпляра в отладчике и пошагово пройдитесь рядом и посмотрите, где они расходятся и почему. Скиньте еще этот макрос, пожалуйста. Подойдет встроенная функция.   -  person Neil Kirk    schedule 27.11.2015
comment
@JohnZwinck Хорошо, я новичок в отладке и попробую это   -  person qwr    schedule 27.11.2015
comment
1 - Какую версию С++ вы используете? 2 - ... которые не вводят (или не должны) новые переменные...; вы имеете в виду ... без изменения каких-либо переменных...? Если да, то следите за эталонными параметрами. Вы можете изменять их, не зная. 3 - swap делает то, что говорит: меняет местами два элемента. Он использует семантику перемещения, но вы можете игнорировать это. Он просто меняет местами, и точка.   -  person Ely    schedule 27.11.2015
comment
4. В отношении static см. stackoverflow.com/a/3698179/1566187 5. Если вы можете предоставить код где-нибудь, может быть, мы могли бы поиграть. Вам нужно будет сообщить нам, каков ожидаемый результат для определенного ввода (или, лучше, для многих пар ввода/вывода)...   -  person Ely    schedule 27.11.2015
comment
@Elyasin Вопрос был сильно изменен. 1. C++11 gcc 4.8.3 без оптимизаций 2. больше не применяется 5. включен весь код, необходимый для запуска программы. Поскольку все математические процедуры складывают и умножают кучу нулей и одно значение, ожидаемый результат должен стремиться к нулю.   -  person qwr    schedule 27.11.2015
comment
Что произойдет, если вы измените float на double? По моему опыту, расчеты, в которых подозреваются ошибки с плавающей запятой, всегда должны выполняться в double.   -  person SirGuy    schedule 16.12.2015
comment
Это связано с отсутствием нормализации в add_source(). Когда ваша плотность становится достаточно стационарной (x0 очень похожей на x), тогда add_source() фактически умножает x на 1.0+dt, что приводит к вашему взрыву. Высокие значения DIFF маскируют этот эффект, увеличивая вес x в lin_solve(), что означает, что эффективный множитель приближается к 1 (но все еще выше 1). Исправление состоит в том, чтобы нормализовать add_source: x[i] = (x[i]+dt*s[i])/(1.0f+dt); или вычислить c = 1+4*a+dt;. Если вы вычислите среднее значение вашей сетки плотности, вы увидите, что в какой-то момент среднее [...]   -  person Iwillnotexist Idonotexist    schedule 17.12.2015
comment
[...] начинает увеличиваться в 1 + (dt / (4*a)) раз; С вашими заданными настройками (dt=0.1, a=0.1*0.01*20*20=0.4) это 1+0.1/1.6 ~ 1.06: Следовательно, как только плотность становится достаточно стационарной, она начинает увеличиваться в массе со скоростью 6% увеличения за итерацию.   -  person Iwillnotexist Idonotexist    schedule 17.12.2015
comment
@IwillnotexistIdonotexist Я смогу протестировать его через несколько часов, но вау! пожалуйста, сделайте это ответом. Я не ожидал чего-то настолько безобидного.   -  person qwr    schedule 17.12.2015


Ответы (3)


Проблема связана с отсутствием нормализации в add_source().

Когда ваша плотность становится достаточно стационарной (x0 очень похожа по распределению на x, вплоть до масштабного коэффициента), тогда add_source() эффективно умножает x примерно на 1+dt, что приводит к вашему экспоненциальному увеличению. Высокие значения DIFF маскируют этот эффект за счет большего веса x по сравнению с x0 в lin_solve(), а это означает, что эффективный множитель приближается к 1, но все же остается выше 1.

Таким образом, эффект заключается в том, что с каждой итерацией добавляется больше массы. Если он не может достаточно быстро «растекаться» по краям, он начнет накапливаться. Как только плотность станет совершенно стационарной, ее масса будет увеличиваться с экспоненциальной скоростью, определяемой 1+dt/(4a).

С заданными вами настройками (dt=0.1, a=0.1*0.01*20*20=0.4) это 1+0.1/1.6 ~ 1.06.

Исправление заключается в нормализации в add_source:

x[i] = (x[i]+dt*s[i])/(1.0f+dt);

или вычислить аргумент c для lin_solve() как 1+4*a+dt. Либо заставит массу упасть.

person Iwillnotexist Idonotexist    schedule 16.12.2015

Один источник проблем находится в lin_solve. Ваши циклы i и j начинаются с нуля, но вы ссылаетесь на IX(i-1,j), который будет обращаться к элементу массива за пределами границ x[-1].

person 1201ProgramAlarm    schedule 27.11.2015
comment
@qwr Но вы все еще ссылаетесь на элемент x[-1] в lin_solve. - person 1201ProgramAlarm; 27.11.2015
comment
Также расчет x для более поздних значений i и j зависит от расчета x для более ранних значений i и j. Было ли это особенностью оригинальной бумаги или это ошибка? - person James Picone; 27.11.2015
comment
@JamesPicone Я тоже это подозревал, но это есть в оригинальной статье. - person qwr; 27.11.2015
comment
@JamesPicone да, я считаю, что это интеграция чехарды. Так намеренно. - person vidstige; 16.12.2015

Увидев это, я сразу почувствовал, что должен ответить. Я читал эту статью еще тогда, когда она была опубликована. Я реализовал его вещи на Android, и мне это очень нравится. Я даже встретил этого парня, когда выступал в Умео в начале 2000-х, и он очень дружелюбный парень. И высокий. :)

Итак к проблеме. Вы не делаете шаг распространения скорости, я думаю, что это важно, чтобы не «взорваться», если я правильно помню.

person vidstige    schedule 16.12.2015
comment
Это было намеренно опущено, чтобы сократить этот вопрос SO, поскольку значения все еще взрываются с шагом распространения. Я могу вернуть эти шаги обратно, когда доберусь до этого. Но ваша помощь будет очень признательна. - person qwr; 16.12.2015