Estoy tratando de implementar una versión 2D del documento de Foster y Fedkiw, "Animación práctica de líquidos" aquí: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf
Casi todo funciona, excepto la sección 8: "Conservación de la misa". Allí, configuramos una matriz de ecuaciones para calcular las presiones necesarias para liberar el líquido divergente.
Creo que mi código coincide con el documento, sin embargo, obtengo una matriz insoluble durante la conservación del paso de masa.
Aquí están mis pasos para generar la matriz A:
- Establezca las entradas diagonales en el negativo del número de celdas líquidas adyacentes a la celda i.
- Establecer las entradas y a 1 si ambas celdas i y j tienen líquido.
Tenga en cuenta que, en mi implementación, la celda , en la rejilla líquida corresponde a la fila gridWidth en la matriz
El documento menciona: "El objeto estático y las celdas vacías no interrumpen esta estructura. En ese caso, los términos de presión y velocidad pueden desaparecer de ambos lados", por lo que elimino las columnas y filas de las celdas que no tienen líquido.
Entonces mi pregunta es: ¿Por qué mi matriz es singular? ¿Me estoy perdiendo algún tipo de condición límite en algún otro lugar del periódico? ¿Es el hecho de que mi implementación es 2D?
Aquí hay una matriz de ejemplo de mi implementación para una cuadrícula de 2x2 donde la celda a 0,0 no tiene líquido:
-1 0 1
0 -1 1
1 1 -2
Editar
Mi investigación me ha llevado a creer que no estoy manejando adecuadamente las condiciones de contorno.
En primer lugar, en este punto puedo decir que mi matriz representa la ecuación de Poisson de presión discreta. Es el análogo discreto de aplicar el operador laplaciano acoplando los cambios de presión locales a la divergencia celular.
Hasta donde puedo entender, dado que estamos lidiando con diferencias de presión, se necesitan condiciones de frontera para "anclar" las presiones a un valor de referencia absoluto. De lo contrario, puede haber un número infinito de soluciones para el conjunto de ecuaciones.
En estas notas , se dan 3 formas diferentes de aplicar condiciones de contorno, a mi entender:
Dirichlet: especifica valores absolutos en los límites.
Neummann: especifica la derivada en los límites.
Robin: especifica algún tipo de combinación lineal del valor absoluto y la derivada en los límites.
El artículo de Foster y Fedki no menciona ninguno de estos, pero creo que imponen las condiciones de frontera de Dirichlet, notables debido a esta declaración al final de 7.1.2, "La presión en una celda de superficie se ajusta a la presión atmosférica".
He leído las notas que vinculé varias veces y todavía no entiendo las matemáticas. ¿Cómo aplicamos exactamente estas condiciones límite? Mirando otras implementaciones, parece haber algún tipo de noción de celdas "fantasmas" que se encuentran en el límite.
Aquí he vinculado a algunas fuentes que pueden ser útiles para otros que leen esto.
Notas sobre condiciones de contorno para matrices de Poisson
Computational Science StackExchange post en condiciones de contorno de Neumann
Computational Science StackExchange post en Poisson Solver
Implementación de agua Physbam
Aquí está el código que uso para generar la matriz. Tenga en cuenta que, en lugar de eliminar explícitamente columnas y filas, genero y uso un mapa de índices de celdas líquidas a las columnas / filas de la matriz final.
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
}
}
}
fuente
Respuestas:
A partir de su fragmento de código y su resultado para el ejemplo 2x2, puedo ver que en realidad está simulando un dominio con solo condiciones de contorno de Neumann (pared de deslizamiento). En este caso, el sistema contiene un espacio nulo y su matriz es singular.
Si esta es la configuración de simulación que desea (es decir, sin Dirichlet (presión) BC), deberá proyectar el espacio nulo de su solución. Esto es sencillo si está utilizando el gradiente conjugado (CG) como se sugiere en ese documento. En cada iteración de su iteración CG, simplemente tome el vector de solución actualX , y hacer
X′= ( I -tu^tu^T) x= x - (tu^⋅ x )tu^
dónde tu^ es el espacio nulo normalizado del operador de gradiente:
u =(1,1,...,1) , tu^=tu∥ u ∥ .
De lo contrario, si desea simular el aire (límite libre o Dirichlet BC), deberá distinguir un muro y una celda de aire (es decir, tener un booleano
hasLiquid
no es suficiente) y aplicar la discretización correcta para ellos (ver más abajo).Como nota final, sus entradas diagonales son negativas. Es posible que desee voltear los signos para que funcione el método CG.
A continuación me gustaría mostrar más detalles. Considere el proceso de proyección de presión. Denote la velocidad antes de la proyección de presión comov∗ . Podría ser divergente, por lo que calculamos la presión para corregirlo y obtener la velocidad libre de divergenciavn + 1 . Es decir,
vn + 1=v∗-Δ tρ∇ P
Tome la divergencia de eso, y desde vn + 1 es divergencia libre,
∇ ⋅ ∇ P= ∇ ⋅v∗
Supongamos que no hay presión Dirichlet BC presente y tenemos una solución PAGS0 0 , entonces PAGS0 0+ c para cualquier constante C también es una solución porque ∇ ⋅ ∇ (PAGS0 0+ c ) = ∇ ⋅ ∇PAGS0 0= ∇ ⋅v∗ .
C es el espacio nulo que queremos proyectar.
Para manejar el Dirichlet BC, consideremos el caso 1D como ejemplo. Suponga que está utilizando una cuadrícula escalonada, donde las presionespagsyo están ubicados en centros de rejilla y velocidades vi + 1 / 2 están ubicados en las caras entre nodos yo y i + 1 . Entonces la discretización general para una celda es
pagsi + 1-pagsyo- (pagsyo-pagsi - 1)ΔX2= rhs
Suponer que pagsi + 1 es una celda de aire, es decir, se ha especificado su presión, entonces el pagsi + 1 El término se mueve hacia el lado derecho y desaparece de la matriz. Tenga en cuenta que el recuento del término diagonalpagsyo Todavía son dos. Es por eso que dije que su ejemplo de 2x2 no contenía un Dirichlet BC.
Con Dirichlet o Neumann BC, la matriz siempre es simétrica positiva definida. Por eso los autores dijeron
fuente