Conservando Massa em Simulação Líquida

8

Estou tentando implementar uma versão 2D do artigo de Foster e Fedkiw, "Animação prática de líquidos" aqui: http://physbam.stanford.edu/~fedkiw/papers/stanford2001-02.pdf

Principalmente tudo funciona, exceto a seção 8: "Conservação de massa". Lá, montamos uma matriz de equações para calcular as pressões necessárias para tornar o líquido livre de divergências.

Acredito que meu código coincide com o artigo, no entanto, estou obtendo uma matriz insolúvel durante a etapa de conservação da massa.

Aqui estão meus passos para gerar a matriz A:

  1. Defina as entradas diagonais Ai,i para o número negativo de células líquidas adjacentes à célula i.
  2. Defina as entradas Ai,j e Aj,i para 1 se ambas as células iej tiverem líquido.

Observe que, na minha implementação, o cell i,j na grade líquida corresponde à linha i+gridWidthj na matriz.

O artigo menciona: "Objeto estático e células vazias não perturbam essa estrutura. Nesse caso, os termos de pressão e velocidade podem desaparecer dos dois lados", então excluo as colunas e linhas de células que não têm líquido.

Então, minha pergunta é: Por que minha matriz é singular? Estou perdendo algum tipo de condição de contorno em algum outro lugar do jornal? É o fato de que minha implementação é 2D?

Aqui está um exemplo de matriz da minha implementação para uma grade 2x2 em que a célula em 0,0 não possui líquido:

-1   0   1

 0  -1   1

 1   1  -2

Editar

Minha pesquisa me levou a acreditar que não estou lidando adequadamente com as condições de contorno.

Antes de tudo, neste momento, posso dizer que minha matriz representa a equação de Poisson de pressão discreta. É o análogo discreto da aplicação do operador laplaciano que acopla as alterações de pressão local à divergência celular.

Pelo que entendi, como estamos lidando com diferenças de pressão, são necessárias condições de contorno para "ancorar" as pressões a um valor de referência absoluto. Caso contrário, pode haver um número infinito de soluções para o conjunto de equações.

Em essas notas , 3 maneiras diferentes são dadas para aplicar condições de contorno, para o melhor de meu entendimento:

  1. Dirichlet - especifica valores absolutos nos limites.

  2. Neummann - especifica a derivada nos limites.

  3. Robin - especifica algum tipo de combinação linear do valor absoluto e a derivada nos limites.

O artigo de Foster e Fedki não menciona nada disso, mas acredito que eles impõem condições de contorno de Dirichlet, notáveis ​​por causa dessa afirmação no final de 7.1.2, "A pressão em uma célula de superfície é definida como pressão atmosférica".

Eu li as anotações que vinculei algumas vezes e ainda não entendi direito a matemática. Como exatamente aplicamos essas condições de contorno? Olhando para outras implementações, parece haver algum tipo de noção de uma célula "fantasma" que se encontra na fronteira.

Aqui, vinculei a algumas fontes que podem ser úteis para outras pessoas que estão lendo isso.

Notas sobre condições de contorno para matrizes de Poisson

Computational Science StackExchange post sobre condições de contorno de Neumann

Ciência da Computação StackExchange post on Poisson Solver

Implementação de Physbam de Água


Aqui está o código que eu uso para gerar a matriz. Observe que, em vez de excluir explicitamente colunas e linhas, eu gero e uso um mapa dos índices de células líquidas para as colunas / linhas finais da matriz.

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
        }
      }
    }
Jared Counts
fonte
Não está claro por que objetos estáticos e células vazias devem permitir a exclusão de linhas e colunas. Você está configurando essas linhas e colunas para zero ou removendo-as completamente para fornecer uma matriz menor?
precisa saber é o seguinte
Caso o problema esteja em algum lugar diferente do que você imagina, seria útil ver o código, se é algo que você deseja compartilhar. O ideal é um MCVE
trichoplax 03/03
Hey trichoplax. Uma matriz com uma linha ou coluna totalmente zero seria singular, tanto quanto eu sei, então eu as removo da matriz para criar uma matriz menor (assim como suas entradas correspondentes no vetor b).
Jared Counts
Editarei um MCVE hoje à noite quando estiver perto do meu computador com a fonte.
Jared Counts
Eu também suspeitava que talvez estivesse fazendo uma suposição errada em algum outro lugar do código, no entanto, isso pertence apenas à estrutura da matriz (e se é singular ou não). A única coisa em que consigo pensar é o que se qualifica como uma "célula superficial" versus uma célula aérea ou uma célula líquida. Se esta é uma célula líquida adjacente a uma célula aérea, há algo diferente que eu deveria estar fazendo com suas colunas / linhas correspondentes?
Jared Counts

Respostas:

2

No seu snippet de código e no resultado do exemplo 2x2, posso ver que você está realmente simulando um domínio apenas com condições de contorno de Neumann (slip wall). Nesse caso, o sistema contém um espaço nulo e sua matriz é singular.

Se esta é a configuração de simulação que você deseja (ou seja, sem Dirichlet (pressão) BC), será necessário projetar o espaço nulo da sua solução. Isso é direto se você estiver usando o gradiente conjugado (CG), conforme sugerido nesse artigo. Em cada iteração da iteração de CG, basta usar o vetor de solução atualx, e fazer

x=(Iu^u^T)x=x(u^x)u^
Onde u^ é o espaço nulo normalizado do operador de gradiente: u=(1,1,,1), u^=uu.

Caso contrário, se você deseja simular o ar (limite livre ou Dirichlet BC), será necessário distinguir uma parede e uma célula do ar (por exemplo, ter um booleano hasLiquidnão é suficiente) e aplicar a discretização correta para eles (veja abaixo).

Como nota final, suas entradas diagonais são negativas. Você pode virar os sinais para que o método CG funcione.


Abaixo eu gostaria de mostrar mais detalhes. Considere o processo de projeção de pressão. Denote a velocidade antes da projeção da pressão comov. Pode ser divergente, por isso calculamos a pressão para corrigi-la e obter a velocidade livre de divergênciavn+1. Isso é,

vn+1=vΔtρP
Tome divergência disso, e desde vn+1 é livre de divergências,
P=v
Suponha que não haja pressão que o Dirichlet BC faça e tenhamos uma solução P0, então P0+c para qualquer constante c também é uma solução porque (P0+c)=P0=v. c é o espaço nulo que queremos projetar.

Para lidar com o Dirichlet BC, vamos considerar o caso 1D como um exemplo. Suponha que você esteja usando uma grade escalonada, onde as pressõespi estão localizados nos centros e velocidades da grade vi+1/2 estão localizados nas faces entre os nós i e i+1. Então a discretização geral para uma célula é

pi+1pi(pipi1)Δx2=rhs
Suponha que pi+1 é uma célula de ar, ou seja, sua pressão foi especificada, então o pi+1O termo é movido para o lado direito e desaparece da matriz. Observe que a contagem do termo diagonalpiainda são dois. Foi por isso que eu disse que o seu exemplo 2x2 não continha um Dirichlet BC.

Com Dirichlet ou Neumann BC, a matriz é sempre positiva simétrica definida. Por isso os autores disseram

Static object and empty cells don’t disrupt this structure.
In that case pressure and velocity terms can disappear from both sides
TheBusyTypist
fonte