问题 流体模拟“爆炸”


以下流体模拟是a的翻译 斯塔姆的论文。发生了真正可怕的事情。每次程序以低速运行 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 问题仍然存在。


5256
2017-11-27 04:39


起源

尝试运行下的程序 valgrind。它可能会自动告诉你出了什么问题。我假设你在Linux上...... - John Zwinck
在调试器中打开两个单独的实例,并排并排,看看两者分歧的原因和原因。请放下那个宏。内联函数可以。 - Neil Kirk
如果你改变会发生什么 float 至 double?怀疑浮点错误的计算应该始终在 double 在我的经验中。 - SirGuy
这与缺乏正常化有关 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;。如果你计算密度网格的平均值,你会在某个时候看到平均值[....] - Iwillnotexist Idonotexist
[...]开始增加一倍 1 + (dt / (4*a));使用您给定的设置(dt=0.1, a=0.1*0.01*20*20=0.4), 这是 1+0.1/1.6 ~ 1.06:Ergo,一旦密度变得足够稳定,它开始以每次迭代增加6%的速度增加质量。 - Iwillnotexist Idonotexist


答案:


问题与缺乏正常化有关 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。要么会迫使质量下降。


5
2017-12-16 22:42



终于解决了,谢谢 - qwr


一个麻烦来源是 lin_solve。你的 i 和 j 循环从零开始,但是你引用 IX(i-1,j),它将访问out of bounds数组元素 x[-1]


3
2017-11-27 05:42



@qwr但你还在引用元素 x[-1] 在 lin_solve。 - 1201ProgramAlarm
嗯,一定是SO不保存编辑,编辑 - qwr
对于后来的i和j值,x的计算还取决于对于较早的i和j值的x的计算。这是原始论文的一个特征,还是一个错误? - James Picone
@JamesPicone我也怀疑它,但它在原始论文中 - qwr
@JamesPicone是的,我相信这是一个跳蛙整合。所以故意。 - vidstige


看到这一点,我立刻觉得我必须回答。我在阅读这篇文章的时候就已经发布了。我在Android上实现了他的东西并且喜欢它。我甚至在21世纪初在Umeå演讲时遇到了这个人,他是一个非常友善的人。高大的。 :)

所以问题。你没有进行速度传播步骤,我认为如果我没记错的话,这对于“炸毁”至关重要。


2
2017-12-16 18:38



故意忽略了这个SO问题,因为价值仍然随着传播步骤而“爆炸”。当我绕过它时,我可以把这些步骤放回去。但是你的帮助将非常感激。 - qwr