Implementação Base

Aqui esclareceremos como foi pensada a implementação base deste projeto.

Generalização

Estruturas

No código existem estruturas e tipos que são essenciais para seu funcionamento e entendimento.

Mesh

Aqui se encontram valores que definem uma malha. Nela se encontram, por exemplo, o número de subdivisões para cada eixo, número de elementos totais, as coordenadas de cada um dos nós, sua dimensão, a matriz LG, o vetor EQ, entre outros.

Existem funções para montagens específicas de malha. Um exemplo é a assemble_uniform_mesh_1D que retorna uma variável do tipo Mesh para um intervalo uniforme discreto dentro de $[a, b],\ a<b$.

Manter todos esses valores agrupados, torna-os facilmente acessíveis.

RunValues

Essa estrutura é utilizada para agrupar valores normalmente utilizados nos problemas, ou seja, parâmetros, coeficientes, a $f$ e a solução $u$ esperada. É o que retorna quando se chama funções "exemplos", localizadas em examples.jl. Seu uso é opcional, recomendado para montar esses exemplos prontos.

EquationTerms

Para determinar os termos a serem utilizados no operador bilinear $a(u,v)$, é necessário o EquationTerms. É uma forma de indicar como $u$, $v$, $\nabla u$, $\nabla v$... são aplicados em $a(u, v)$.

Montando operador $a(u, v)$ e aplicando-o na montagem do sistema linear

O operador pode se encontrar, por exemplo, na seguinte forma:

\[a(u, v) = \beta \int{ u(x)\cdot v(x)d\Omega} + \alpha \int{ \nabla{u}(x)\cdot \nabla{v}(x) d\Omega}\]

No entanto, o que utilizamos é uma forma sem a integração, ou seja, um pseudo operador, por exemplo, pseudo_a(u,v) = β * dot(u, v) + α * dot(∇u, ∇v). Nesse caso, α e β são constantes escolhidas arbitrariamente, mas u, v, ∇u e ∇v são referenciadas diretamente de EquationTerms.

Então, a função que utilizamos para definir esse pseudo operador, na verdade, é:

function pseudo_a(equation_terms::EquationTerms)
  (; ∇u, ∇v, u, v) = equation_terms

  return β * dot(u, v) + α * dot(∇u, ∇v)
end

O argumento equation_terms contém todos os termos possíveis de serem utilizados e pegamos apenas os que queremos em (; ∇u, ∇v, u, v) = equation_terms. Essa nomenclatura (; var1, var2, var3,...) no lado esquerdo da atribuição permite extrair os campos de EquationTerms através de seus nomes, sem precisar de uma ordem específica. Se quisermos um outro, por exemplo, x, podemos digitar (; ∇u, ∇v, u, x, v) = equation_terms.

Já o retorno da função é o que será aplicado na montagem das K locais do problema aproximado. Temos que uma entrada da matriz $K^e$ local é

\[K^{e}_{a,b} = \int \alpha \nabla{\varphi^{e}_{b}(x(\xi))} \cdot \nabla{\varphi^{e}_{a}(x(\xi))} + \beta \varphi^{e}_{b}(x(\xi)) \cdot \varphi^{e}_{a}(x(\xi))\ |J(\xi)|d\xi_{1}d\xi_{2}\]

Essa integração é feita com a Quadratura Gaussiana, assim, para cada ponto de gauss $i$, temos a expressão

\[w_i(\alpha \nabla{\varphi^{e}_{b}(x(\xi))} \cdot \nabla{\varphi^{e}_{a}(x(\xi))} + \beta \varphi^{e}_{b}(x(\xi)) \cdot \varphi^{e}_{a}(x(\xi)))|J(\xi)|\]

Portanto, se substituirmos $\nabla{\varphi^{e}_{b}(x(\xi))}$ por ∇u, $\nabla{\varphi^{e}_{a}(x(\xi))}$ por ∇v, $\varphi^{e}_{b}(x(\xi))$ por u e $\varphi^{e}_{b}(x(\xi))$ por v, temos wᵢ*(α*∇u⋅∇v + β*u⋅v)|J(ξ)|. Ou seja, a expressão entre parênteses é examente o que a função pseudo_a retorna.

Isso na montagem da K local é feito com o trecho de código a seguir

  equation_terms = EquationTerms(
    ϕᵉ_b,
    ϕᵉ_a,
    ∇ϕᵉ_b,
    ∇ϕᵉ_a
  )
  sum = pseudo_a(equation_terms)
  "sum = β * dot(ϕᵉ_b, ϕᵉ_a) + α * dot(∇ϕᵉ_b, ∇ϕᵉ_a)"

  Kᵉ[a, b] += wᵢ * sum * detJ

Assim, é possível definir uma referência ao operador $a(u, v)$, com os termos apresentados, sem precisar mexer no código da montagem da K local.

Adicionando um novo termo

Adicionar um novo termo vem da ideia de tornar flexível a implementação de novos problemas. Necessariamente tem que ser calculado e obtido dentro da montagem da K local, assim como ∇ϕᵉ_b, por exemplo. Um exemplo pronto de como incluir um termo novo se encontra no [tutorial]