Conservare la massa nella simulazione liquida


8

Sto cercando di implementare una versione 2D del documento di Foster e Fedkiw, "Animazione pratica dei liquidi" qui: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf

Quasi tutto funziona, tranne per la sezione 8: "Conservazione della messa". Lì, abbiamo creato una matrice di equazioni per calcolare le pressioni necessarie per rendere il liquido divergente libero.

Credo che il mio codice corrisponda al documento, tuttavia sto ottenendo una matrice irrisolvibile durante la conservazione del passo di massa.

Ecco i miei passaggi per generare la matrice A:

  1. Imposta le voci diagonali Ai,i al negativo del numero di celle liquide adiacenti alla cella i.
  2. Imposta le voci Ai,j e Aj,i a 1 se entrambe le celle i e j hanno liquido.

Si noti che, nella mia implementazione, cella i,j nella griglia del liquido corrisponde alla riga i+gridWidthj nella matrice.

L'articolo menziona "L'oggetto statico e le celle vuote non disturbano questa struttura. In tal caso i termini di pressione e velocità possono scomparire da entrambi i lati", quindi elimino le colonne e le righe per le celle che non hanno liquido.

Quindi la mia domanda è: perché la mia matrice è singolare? Mi sto perdendo una sorta di condizione al contorno in qualche altro punto del documento? È il fatto che la mia implementazione sia in 2D?

Ecco una matrice di esempio dalla mia implementazione per una griglia 2x2 in cui la cella a 0,0 non ha liquido:

-1   0   1

 0  -1   1

 1   1  -2

modificare

La mia ricerca mi ha portato a credere che non sto gestendo correttamente le condizioni al contorno.

Prima di tutto, a questo punto posso dire che la mia matrice rappresenta l'equazione di Poisson a pressione discreta. È l'analogo discreto dell'applicazione dell'operatore laplaciano che accoppia le variazioni di pressione locale alla divergenza cellulare.

Per quanto posso capire, poiché abbiamo a che fare con differenze di pressione, sono necessarie condizioni al contorno per "ancorare" le pressioni a un valore di riferimento assoluto. Altrimenti potrebbe esserci un numero infinito di soluzioni all'insieme di equazioni.

In queste note , vengono forniti 3 modi diversi per applicare le condizioni al contorno, per quanto ne so:

  1. Dirichlet: specifica i valori assoluti ai confini.

  2. Neummann: specifica il derivato ai confini.

  3. Robin: specifica una sorta di combinazione lineare del valore assoluto e della derivata ai confini.

Il documento di Foster e Fedki non menziona nessuno di questi, ma credo che facciano rispettare le condizioni al contorno di Dirichlet, in particolare a causa di questa affermazione alla fine di 7.1.2, "La pressione in una cellula superficiale è impostata sulla pressione atmosferica".

Ho letto le note che ho collegato alcune volte e ancora non capisco bene la matematica in corso. Come imponiamo esattamente queste condizioni al contorno? Guardando altre implementazioni, sembra che ci sia una sorta di nozione di cellule "fantasma" che giacciono al limite.

Qui ho collegato ad alcune fonti che potrebbero essere utili ad altri leggendo questo.

Note sulle condizioni al contorno per le matrici di Poisson

Stack di calcolo computazionale Scambio post sulle condizioni al contorno di Neumann

Stack di calcolo computazionale Scambio post su Risolutore di Poisson

Implementazione di Water Physbam


Ecco il codice che uso per generare la matrice. Si noti che, invece di eliminare esplicitamente colonne e righe, generi e utilizzo una mappa dagli indici delle celle liquide alle colonne / righe della matrice finale.

for (int i = 0; i < cells.length; i++) {
  for (int j = 0; j < cells[i].length; j++) {
    FluidGridCell cell = cells[i][j];

    if (!cell.hasLiquid)
      continue;

    // get indices for the grid and matrix
    int gridIndex = i + cells.length * j;
    int matrixIndex = gridIndexToMatrixIndex.get((Integer)gridIndex);

    // count the number of adjacent liquid cells
    int adjacentLiquidCellCount = 0;
    if (i != 0) {
      if (cells[i-1][j].hasLiquid)
        adjacentLiquidCellCount++;
    }
    if (i != cells.length-1) {
      if (cells[i+1][j].hasLiquid)
        adjacentLiquidCellCount++;
    }
    if (j != 0) {
      if (cells[i][j-1].hasLiquid)
      adjacentLiquidCellCount++;
    }
    if (j != cells[0].length-1) {
      if (cells[i][j+1].hasLiquid)
        adjacentLiquidCellCount++;
    }

    // the diagonal entries are the negative count of liquid cells
    liquidMatrix.setEntry(matrixIndex, // column
                          matrixIndex, // row
                          -adjacentLiquidCellCount); // value

    // set off-diagonal values of the pressure matrix
    if (cell.hasLiquid) {
      if (i != 0) {
        if (cells[i-1][j].hasLiquid) {
          int adjacentGridIndex = (i-1) + j * cells.length;
          int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
          liquidMatrix.setEntry(matrixIndex, // column
                                adjacentMatrixIndex, // row
                                1.0); // value
          liquidMatrix.setEntry(adjacentMatrixIndex, // column
                                matrixIndex, // row
                                1.0); // value
        }
      }
      if (i != cells.length-1) {
        if (cells[i+1][j].hasLiquid) {
          int adjacentGridIndex = (i+1) + j * cells.length;
          int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
          liquidMatrix.setEntry(matrixIndex, // column
                                adjacentMatrixIndex, // row
                                1.0); // value
          liquidMatrix.setEntry(adjacentMatrixIndex, // column
                                matrixIndex, // row
                                1.0); // value
        }
      }
      if (j != 0) {
        if (cells[i][j-1].hasLiquid) {
          int adjacentGridIndex = i + (j-1) * cells.length;
          int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
          liquidMatrix.setEntry(matrixIndex, // column
                                adjacentMatrixIndex, // row
                                1.0); // value
          liquidMatrix.setEntry(adjacentMatrixIndex, // column
                                matrixIndex, // row
                                1.0); // value
        }
      }
      if (j != cells[0].length-1) {
        if (cells[i][j+1].hasLiquid) {
          int adjacentGridIndex = i + (j+1) * cells.length;
          int adjacentMatrixIndex = gridIndexToMatrixIndex.get((Integer)adjacentGridIndex);
          liquidMatrix.setEntry(matrixIndex, // column
                                adjacentMatrixIndex, // row
                                1.0); // value
          liquidMatrix.setEntry(adjacentMatrixIndex, // column
                                matrixIndex, // row
                                1.0); // value
        }
      }
    }

Non è chiaro perché gli oggetti statici e le celle vuote debbano consentire l'eliminazione di righe e colonne. Stai impostando queste righe e colonne su zero o rimuovendole del tutto per dare una matrice più piccola?
trichoplax,

Nel caso in cui il problema si trovi in ​​un posto diverso da quello che indovini, sarebbe utile vedere il codice, se questo è qualcosa che sei felice di condividere. Idealmente un MCVE
trichoplax

Ehi, tricoplax. Una matrice con una riga o una colonna tutto zero sarebbe singolare, per quanto ne so, quindi le rimuovo dalla matrice per creare una matrice più piccola (così come le loro voci corrispondenti nel vettore b).
Jared conta il

Stasera modificherò un MCVE quando sono vicino al mio computer con la fonte.
Jared conta il

Sospettavo anche che forse stavo facendo un'ipotesi sbagliata da qualche altra parte nel codice, tuttavia ciò riguarda solo la struttura della matrice (e se è singolare o meno). L'unica cosa che mi viene in mente è ciò che si qualifica come una "cellula superficiale" rispetto a una cellula d'aria o una cellula liquida. Se questa è una cella liquida adiacente a una cella d'aria, c'è qualcosa di diverso che dovrei fare con le sue corrispondenti colonne / file?
Jared conta il

Risposte:


2

Dal tuo frammento di codice e dal tuo risultato per esempio 2x2, posso vedere che stai effettivamente simulando un dominio con solo condizioni al contorno di Neumann (parete scorrevole). In questo caso, il sistema contiene uno spazio nullo e la tua matrice è singolare.

Se questa è la configurazione di simulazione che si desidera (ovvero senza Dirichlet (pressione) BC), sarà necessario proiettare lo spazio nullo dalla propria soluzione. Questo è semplice se si utilizza il gradiente coniugato (CG) come suggerito in quel documento. In ogni iterazione della tua iterazione CG, prendi semplicemente il vettore della soluzione correntex, e fai

x=(Iu^u^T)x=x(u^x)u^
dove u^ è lo spazio null normalizzato dell'operatore gradiente: u=(1,1,,1), u^=uu.

Altrimenti, se si desidera simulare l'aria (confine libero o Dirichlet BC), sarà necessario distinguere una parete e una cella d'aria (ovvero avere un valore booleano hasLiquidnon è sufficiente) e applicare la discretizzazione corretta per loro (vedi sotto).

Come nota finale, le voci diagonali sono negative. Si consiglia di capovolgere i segni in modo che il metodo CG funzioni.


Di seguito vorrei mostrare maggiori dettagli. Considera il processo di proiezione della pressione. Indica la velocità prima della proiezione della pressione come . Potrebbe essere divergente, quindi calcoliamo la pressione per correggerlo e ottenere la velocità libera di divergenza . Cioè, Prendi la divergenza e dal momento che è privo di divergenze, Supponiamo che non ci sia pressione Dirichlet BC presesnt e abbiamo una soluzione , quindi per qualsiasi costantevvn+1

vn+1=vΔtρP
vn+1
P=v
P0P0+ccè anche una soluzione perché . è lo spazio nullo che vogliamo proiettare fuori.(P0+c)=P0=vc

Per gestire Dirichlet BC, consideriamo il caso 1D come esempio. Supponiamo di utilizzare una griglia sfalsata, in cui le pressioni si trovano ai centri della griglia e le velocità si trovano sulle facce tra nodi e . Quindi la discretizzazione generale per una cella è Supponi che è una cella d'aria, ovvero la sua pressione è stata specificata, quindi il termine viene spostato sul lato destro e scompare dalla matrice. Si noti che il conteggio del termine diagonale è ancora due. Ecco perché ho detto che il tuo esempio 2x2 non conteneva un Dirichlet BC.pivi+1/2ii+1

pi+1pi(pipi1)Δx2=rhs
pi+1pi+1pi

Con Dirichlet o Neumann BC la matrice è sempre simmetrica positiva definita. Ecco perché gli autori hanno detto

Static object and empty cells don’t disrupt this structure.
In that case pressure and velocity terms can disappear from both sides
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.