Conserver la masse dans la simulation liquide


8

J'essaie d'implémenter une version 2D du document de Foster et Fedkiw, "Practical Animation of Liquids" ici: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf

Généralement, tout fonctionne, sauf pour la section 8: «Conservation de la masse». Là, nous avons mis en place une matrice d'équations pour calculer les pressions nécessaires pour libérer le liquide divergeant.

Je crois que mon code correspond au papier, mais je reçois une matrice insoluble lors de la conservation de l'étape de masse.

Voici mes étapes pour générer la matrice A:

  1. Définissez les entrées diagonales Ai,i au négatif du nombre de cellules liquides adjacentes à la cellule i.
  2. Définissez les entrées Ai,j et Aj,i à 1 si les deux cellules i et j contiennent du liquide.

Notez que, dans mon implémentation, la cellule i,j dans la grille liquide correspond à la ligne i+gridWidthj dans la matrice.

Le document mentionne, "L'objet statique et les cellules vides ne perturbent pas cette structure. Dans ce cas, les termes de pression et de vitesse peuvent disparaître des deux côtés", donc je supprime les colonnes et les lignes pour les cellules qui n'ont pas de liquide.

Ma question est donc la suivante: pourquoi ma matrice est-elle singulière? Suis-je en train de manquer une sorte de condition aux limites à un autre endroit du document? Est-ce le fait que mon implémentation est 2D?

Voici un exemple de matrice de mon implémentation pour une grille 2x2 où la cellule à 0,0 n'a pas de liquide:

-1   0   1

 0  -1   1

 1   1  -2

Éditer

Mes recherches m'ont amené à croire que je ne gère pas correctement les conditions aux limites.

Tout d'abord, à ce stade, je peux dire que ma matrice représente l'équation de Poisson à pression discrète. C'est l'analogue discret de l'application de l'opérateur laplacien couplant les changements de pression locaux à la divergence cellulaire.

Pour autant que je puisse comprendre, étant donné qu'il s'agit de différences de pression, des conditions aux limites sont nécessaires pour «ancrer» les pressions à une valeur de référence absolue. Sinon, il peut y avoir un nombre infini de solutions à l'ensemble des équations.

Dans ces notes , 3 façons différentes sont données pour appliquer des conditions aux limites, au mieux de ma compréhension:

  1. Dirichlet - spécifie des valeurs absolues aux limites.

  2. Neummann - spécifie la dérivée aux limites.

  3. Robin - spécifie une sorte de combinaison linéaire de la valeur absolue et de la dérivée aux limites.

L'article de Foster et Fedki ne mentionne aucun de ces éléments, mais je pense qu'ils appliquent les conditions aux limites de Dirichlet, notamment en raison de cette déclaration à la fin du 7.1.2, "La pression dans une cellule de surface est réglée sur la pression atmosphérique."

J'ai lu les notes que j'ai liées plusieurs fois et je ne comprends toujours pas très bien le calcul. Comment appliquons-nous exactement ces conditions aux limites? En regardant d'autres implémentations, il semble y avoir une sorte de notion de cellules "fantômes" qui se trouvent à la frontière.

Ici, j'ai lié à quelques sources qui peuvent être utiles à ceux qui lisent ceci.

Remarques sur les conditions aux limites pour les matrices de Poisson

Poste de StackExchange en science computationnelle sur les conditions aux limites de Neumann

Publication de StackExchange en science informatique sur le solveur de Poisson

Mise en œuvre du Physbam de l'eau


Voici le code que j'utilise pour générer la matrice. Notez qu'au lieu de supprimer explicitement les colonnes et les lignes, je génère et utilise une carte des indices de cellules liquides aux colonnes / lignes de la 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
        }
      }
    }

Il n'est pas clair pourquoi les objets statiques et les cellules vides devraient permettre la suppression des lignes et des colonnes. Définissez-vous ces lignes et colonnes à zéro ou supprimez-les complètement pour donner une matrice plus petite?
trichoplax

Dans le cas où le problème est ailleurs que là où vous le pensez, il serait utile de voir le code, si c'est quelque chose que vous êtes heureux de partager. Idéalement un MCVE
trichoplax

Hé trichoplax. Une matrice avec une ligne ou une colonne entièrement nulle serait singulière, pour autant que je sache, je les supprime donc de la matrice pour en faire une matrice plus petite (ainsi que leurs entrées correspondantes dans le vecteur b).
Jared compte

Je modifierai un MCVE ce soir lorsque je serai près de mon ordinateur avec la source.
Jared compte

Je soupçonnais également que je faisais peut-être une hypothèse erronée ailleurs dans le code, mais cela ne concerne que la structure de la matrice (et qu'elle soit singulière ou non). La seule chose à laquelle je peux penser est ce qui peut être qualifié de "cellule de surface" par rapport à une cellule à air ou à une cellule liquide. S'il s'agit d'une cellule liquide adjacente à une cellule à air, y a-t-il quelque chose de différent que je devrais faire avec ses colonnes / lignes correspondantes?
Jared compte

Réponses:


2

À partir de votre extrait de code et de votre résultat pour un exemple 2x2, je peux voir que vous simulez en fait un domaine avec uniquement les conditions aux limites de Neumann (paroi de glissement). Dans ce cas, le système contient un espace nul et votre matrice est singulière.

S'il s'agit de la configuration de simulation que vous souhaitez (c'est-à-dire pas de Dirichlet (pression) BC), vous devrez projeter l'espace nul de votre solution. C'est simple si vous utilisez le gradient conjugué (CG) comme suggéré dans cet article. Dans chaque itération de votre itération CG, prenez simplement le vecteur de solution actuelx, et fait

x=(Iu^u^T)x=x(u^x)u^
u^ est l'espace nul normalisé de l'opérateur de gradient: u=(1,1,,1), u^=uu.

Sinon, si vous voulez simuler de l'air (frontière libre ou Dirichlet BC), vous devrez distinguer un mur et une cellule d'air (c'est-à-dire avoir un booléen hasLiquid ne suffit pas) et leur appliquer une discrétisation correcte (voir ci-dessous).

Enfin, vos entrées en diagonale sont négatives. Vous souhaiterez peut-être retourner les signes afin que la méthode CG fonctionne.


Ci-dessous, je voudrais montrer plus de détails. Considérez le processus de projection de pression. Désigner la vitesse avant la projection de pression commev. Cela peut être divergent, donc nous calculons la pression pour le corriger et obtenir la vitesse libre de divergencevn+1. C'est,

vn+1=vΔtρP
Prenez-en la divergence, et depuis vn+1 est sans divergence,
P=v
Supposons qu'il n'y ait pas de pression que Dirichlet BC présente et que nous ayons une solution P0, puis P0+c pour toute constante c est aussi une solution car (P0+c)=P0=v. c est l'espace nul que nous voulons projeter.

Pour gérer le Dirichlet BC, considérons le cas 1D comme exemple. Supposons que vous utilisez une grille décalée, où les pressionspje sont situés aux centres de grille et aux vitesses vje+1/2 sont situés sur les faces entre les nœuds je et je+1. Ensuite, la discrétisation générale pour une cellule est

pje+1-pje-(pje-pje-1)ΔX2=rhs
Supposer que pje+1 est une cellule à air, c'est-à-dire que sa pression a été spécifiée, pje+1terme est déplacé vers la droite et disparaît de la matrice. Notez que le nombre de termes diagonauxpjeest toujours deux. C'est pourquoi j'ai dit que votre exemple 2x2 ne contenait pas de Dirichlet BC.

Avec Dirichlet ou Neumann BC, la matrice est toujours définie positive symétrique. Voilà pourquoi les auteurs ont dit

Static object and empty cells don’t disrupt this structure.
In that case pressure and velocity terms can disappear from both sides
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.