2015-11-27 18 views
10

La seguente simulazione di fluido è una traduzione di uno paper by Stam. È successo qualcosa di veramente terribile. Ogni volta che il programma viene eseguito con un valore basso DIFF=0.01, i valori iniziano in piccolo e quindi si espandono rapidamente o "esplodono". Ho controllato attentamente le routine matematiche. Poiché il codice inizia con uno 0.5, matematicamente si moltiplica e aggiunge un gruppo di zeri, quindi il risultato finale dovrebbe essere vicino a densità zero e altri vettori.Simulazione fluido "Esplosione"

Il codice è piuttosto lungo, quindi l'ho separato in blocchi e rimosso codice aggiuntivo. Meno l'inizio e il codice SDL ci sono solo circa 120 righe. Ho trascorso alcune ore a provare inutili modifiche, quindi l'aiuto è molto apprezzato.

Dopo alcuni esperimenti, credo che potrebbe esserci qualche errore a virgola mobile quando DIFF è impostato su un valore troppo basso. Quando il valore viene aumentato da 0.01 a 0.02, i valori non scoppiano. Non penso che questo sia l'intero problema, però.

Per essere chiari, le risposte correnti di 1201ProgramAlarm e vidstige non risolvono il problema.

Sezioni in in grassetto sono parti importanti, il resto è per completezza.


partire roba, saltare

#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); 

Math routines routine Diffuse dividere per 1+4*a. Questo implica densità deve essere < = 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); 
} 

densità e velocità risolutore

// 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); 
} 

ho ritenuto floating-point inconsistencies, ma dopo la compilazione con -ffloat-store il problema persisteva.

+1

Prova a eseguire il programma in 'valgrind'. Potrebbe automaticamente dirti cosa c'è che non va. Presumo che tu sia su Linux .... –

+0

Apri due istanze separate nel tuo debugger e passa fianco a fianco e vedi dove le due divergono e perché. Lascia anche cadere quella macro per favore. Funzionerà una funzione inline. –

+0

@JohnZwinck Ok, sono nuovo nel debug e proverò questo – qwr

risposta

4

Il problema è legato a una mancanza di normalizzazione in add_source().

Quando la densità diventa sufficientemente stazionario (x0 molto simile a distribuzione x, fino ad un fattore di scala), quindi add_source() moltiplica efficacemente x di circa 1+dt, portando alla ingrandimento esponenziale.Alti valori di DIFF mascherano questo effetto pesando x più pesantemente su x0 in lin_solve(), il che significa che il moltiplicatore efficace viene avvicinata al 1, ma è ancora superiore 1.

L'effetto, quindi è che ad ogni iterazione, più di massa è aggiunto. Se non riesce a "allargarsi" abbastanza velocemente ai bordi, inizierà ad accumularsi. Una volta che la densità diventa perfettamente stazionaria, aumenterà in massa ad una velocità esponenziale determinata da 1+dt/(4a).

Con le impostazioni fornite (dt=0.1, a=0.1*0.01*20*20=0.4), questo è 1+0.1/1.6 ~ 1.06.

la correzione è di normalizzare in add_source:

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

, o per calcolare l'argomento c-lin_solve() come 1+4*a+dt. O costringerà la massa a cadere.

+1

Finalmente risolto, grazie – qwr

3

Una fonte di problemi è in lin_solve. I loop i e j iniziano da zero, ma si fa riferimento a IX(i-1,j), che accederà all'elemento dell'array fuori limite x[-1].

+0

@qwr Ma stai ancora facendo riferimento a elemento 'x [-1]' in 'lin_solve'. – 1201ProgramAlarm

+0

hmm che deve essere stato COSÌ non salvare la modifica, modificato – qwr

+0

Anche il calcolo di x per i valori successivi di i e j dipende dal calcolo di x per i valori precedenti di i e j. Era una caratteristica della carta originale o è un errore? –

2

Vedendo questo ho immediatamente sentito che dovevo rispondere. Ho letto questo articolo molto tempo fa quando è stato pubblicato. Ho implementato le sue cose su Android e lo adoro. Ho persino incontrato il ragazzo quando parlavo a Umeå nei primi anni 2000, ed è un tipo molto amichevole. E alto. :)

Quindi al problema. Non stai facendo un passo di propagazione della velocità, penso che sia fondamentale non "scoppiare" se non ricordo male.

+0

Questo è stato intenzionalmente omesso per abbreviare questa domanda SO, poiché i valori continuano a "esplodere" con la fase di propagazione.Posso rimettermi quei passi quando ci arrivo. Ma il tuo aiuto sarebbe molto apprezzato. – qwr