Massenerhaltung in der Flüssigkeitssimulation


8

Ich versuche hier eine 2D-Version von Foster und Fedkiws Artikel "Praktische Animation von Flüssigkeiten" zu implementieren: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf

Fast alles funktioniert, außer Abschnitt 8: "Erhaltung der Masse". Dort haben wir eine Gleichungsmatrix erstellt, um die Drücke zu berechnen, die erforderlich sind, um die Flüssigkeit divergent frei zu machen.

Ich glaube, mein Code stimmt mit dem Papier überein, aber ich erhalte eine unlösbare Matrix während der Erhaltung des Massenschritts.

Hier sind meine Schritte zum Generieren der Matrix A:

  1. Setzen Sie die diagonalen Einträge Ai,i auf das Negativ der Anzahl benachbarter Flüssigkeitszellen zu Zelle i.
  2. Setzen Sie die Einträge Ai,j und Aj,i auf 1, wenn beide Zellen i und j Flüssigkeit haben.

Beachten Sie, dass in meiner Implementierung die Zelle i , j im Flüssigkeitsgitter der Zeile i+ gridWidth j in der Matrix entspricht.

In dem Artikel wird erwähnt: "Statische Objekte und leere Zellen stören diese Struktur nicht. In diesem Fall können Druck- und Geschwindigkeitsterme von beiden Seiten verschwinden." Daher lösche ich die Spalten und Zeilen für Zellen, die keine Flüssigkeit enthalten.

Meine Frage lautet also: Warum ist meine Matrix singulär? Vermisse ich eine Randbedingung an einer anderen Stelle in der Zeitung? Ist es die Tatsache, dass meine Implementierung 2D ist?

Hier ist eine Beispielmatrix aus meiner Implementierung für ein 2x2-Gitter, in dem die Zelle bei 0,0 keine Flüssigkeit enthält:

-1   0   1

 0  -1   1

 1   1  -2

Bearbeiten

Meine Forschung hat mich zu der Annahme geführt, dass ich mit den Randbedingungen nicht richtig umgehe.

Zunächst kann ich an dieser Stelle sagen, dass meine Matrix die diskrete Druck-Poisson-Gleichung darstellt. Es ist das diskrete Analogon zur Anwendung des Laplace-Operators, der lokale Druckänderungen an die Zelldivergenz koppelt.

Soweit ich verstehen kann, sind Randbedingungen erforderlich, um die Drücke auf einem absoluten Referenzwert zu "verankern", da es sich um Druckunterschiede handelt. Andernfalls kann es unendlich viele Lösungen für den Satz von Gleichungen geben.

In diesen Anmerkungen werden nach meinem besten Verständnis drei verschiedene Möglichkeiten angegeben, um Randbedingungen anzuwenden:

  1. Dirichlet - Gibt Absolutwerte an den Grenzen an.

  2. Neummann - gibt die Ableitung an den Grenzen an.

  3. Robin - gibt eine Art lineare Kombination des Absolutwerts und der Ableitung an den Grenzen an.

In Foster und Fedkis Artikel wird keines davon erwähnt, aber ich glaube, dass sie Dirichlet-Randbedingungen durchsetzen, was aufgrund dieser Aussage am Ende von 7.1.2, "Der Druck in einer Oberflächenzelle wird auf atmosphärischen Druck eingestellt", bemerkenswert ist.

Ich habe die Notizen, die ich ein paar Mal verlinkt habe, gelesen und verstehe die Mathematik immer noch nicht ganz. Wie genau setzen wir diese Randbedingungen durch? Bei anderen Implementierungen scheint es eine Art Vorstellung von "Ghost" -Zellen zu geben, die an der Grenze liegen.

Hier habe ich einige Quellen verlinkt, die für andere, die dies lesen, hilfreich sein können.

Hinweise zu Randbedingungen für Poisson-Matrizen

Computational Science StackExchange-Beitrag zu Neumann-Randbedingungen

Computational Science StackExchange-Beitrag auf Poisson Solver

Implementierung von Water Physbam


Hier ist der Code, mit dem ich die Matrix generiere. Beachten Sie, dass ich anstelle des expliziten Löschens von Spalten und Zeilen eine Zuordnung von Flüssigkeitszellenindizes zu den endgültigen Matrixspalten / -zeilen generiere und verwende.

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
        }
      }
    }

Es ist nicht klar, warum statische Objekte und leere Zellen das Löschen von Zeilen und Spalten ermöglichen sollten. Setzen Sie diese Zeilen und Spalten auf Null oder entfernen Sie sie insgesamt, um eine kleinere Matrix zu erhalten?
Trichoplax

Falls das Problem an einem anderen Ort liegt als dem, den Sie vermuten, ist es hilfreich, den Code zu sehen, wenn Sie dies gerne teilen. Idealerweise ein MCVE
Trichoplax

Hey Trichoplax. Eine Matrix mit einer Null-Zeile oder -Spalte wäre meines Wissens singulär, daher entferne ich sie stattdessen aus der Matrix, um eine kleinere Matrix (sowie die entsprechenden Einträge im b-Vektor) zu erstellen.
Jared zählt

Ich werde heute Abend eine MCVE bearbeiten, wenn ich mich mit der Quelle in der Nähe meines Computers befinde.
Jared zählt

Ich vermutete auch, dass ich irgendwo anders im Code eine falsche Annahme gemacht hatte, dies betrifft jedoch nur die Matrixstruktur (und ob sie singulär ist oder nicht). Ich kann mir nur vorstellen, was sich als "Oberflächenzelle" gegenüber einer Luftzelle oder einer Flüssigkeitszelle qualifiziert. Wenn dies eine Flüssigkeitszelle neben einer Luftzelle ist, gibt es etwas anderes, das ich mit den entsprechenden Spalten / Zeilen tun sollte?
Jared zählt

Antworten:


2

Anhand Ihres Code-Snippets und Ihres Ergebnisses für ein 2x2-Beispiel kann ich sehen, dass Sie tatsächlich eine Domain mit nur Neumann-Randbedingungen (Slip Wall) simulieren. In diesem Fall enthält das System einen Nullraum und Ihre Matrix ist singulär.

x

x=(Iu^u^T)x=x(u^x)u^
u^u=(1,1,,1)u^=uu

Andernfalls müssen Sie, wenn Sie Luft simulieren möchten (freie Grenze oder Dirichlet BC), eine Wand und eine Luftzelle unterscheiden (dh ein Boolescher Wert hasLiquidreicht nicht aus) und eine korrekte Diskretisierung für sie anwenden (siehe unten).

Abschließend sind Ihre diagonalen Einträge negativ. Möglicherweise möchten Sie die Zeichen umdrehen, damit die CG-Methode funktioniert.


vvn+1

vn+1=v- -ΔtρP.
vn+1
P.=v
P.0P.0+cc(P.0+c)=P.0=vc

pichvich+1/.2ichich+1

pich+1- -pich- -(pich- -pich- -1)Δx2=rhs
pich+1pich+1pich

Bei Dirichlet oder Neumann BC ist die Matrix immer symmetrisch positiv definit. Deshalb sagten die Autoren

Static object and empty cells don’t disrupt this structure.
In that case pressure and velocity terms can disappear from both sides
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.