Source code for scimba_torch.physical_models.elliptic_pde.advection_diffusion

"""Advection-reaction-diffusion problems in 1D and 2D.

Use Dirichlet boundary conditions in strong form.
"""

from typing import Callable

import torch

from scimba_torch.approximation_space.abstract_space import AbstractApproxSpace
from scimba_torch.physical_models.elliptic_pde.abstract_elliptic_pde import (
    StrongFormEllipticPDE,
)
from scimba_torch.utils.scimba_tensors import LabelTensor, MultiLabelTensor


[docs] def scalar_zero(x: LabelTensor, mu: LabelTensor) -> torch.Tensor: """Return a tensor of zeros with shape (batch_size, 1). Args: x: Input tensor. mu: Parameter tensor. Returns: A tensor of zeros with shape (batch_size, 1). """ return torch.zeros((x.shape[0], 1))
[docs] def scalar_ones(x: LabelTensor, mu: LabelTensor) -> torch.Tensor: """Return a tensor of ones with shape (batch_size, 1). Args: x: Input tensor. mu: Parameter tensor. Returns: A tensor of ones with shape (batch_size, 1). """ return torch.ones((x.shape[0], 1))
[docs] def vector_ones(x: LabelTensor, mu: LabelTensor) -> torch.Tensor: """Return a tensor of ones with shape (batch_size, 2). Args: x: Input tensor. mu: Parameter tensor. Returns: A tensor of ones with shape (batch_size, 2). """ return torch.ones((x.shape[0], 2))
[docs] def matrix_zero(x: LabelTensor, mu: LabelTensor) -> torch.Tensor: """Return a tensor of zeros with shape (batch_size, 2, 2). Args: x: Input tensor. mu: Parameter tensor. Returns: A tensor of zeros with shape (batch_size, 2, 2). """ return torch.zeros((x.shape[0], 2, 2))
[docs] class AdvectionReactionDiffusion1DDirichletStrongForm(StrongFormEllipticPDE): r"""1D advection-reaction-diffusion problem with strong Dirichlet BCs. .. math:: r(x, \mu) u(x, \mu) + a(x, \mu) \partial_x u(x, \mu) - \partial_x (d(x, \mu) \partial_x u(x, \mu)) & = f(x, \mu) \text{ in } \Omega, \\ u(x, \mu) & = g(x, \mu) \text{ on } \partial \Omega. By default, $r = 0$, $a = 1$, $d = 0$, $f = 0$, and $g = 0$. The user can specify these functions as needed. Args: space: The approximation space for the problem. r: Callable representing the reaction term r(x, mu). a: Callable representing the advection term a(x, mu). d: Callable representing the advection term d(x, mu). f: Callable representing the source term f(x, mu). g: Callable representing the Dirichlet boundary condition g(x, mu). constant_advection: Whether the advection term is constant. constant_diffusion: Whether the diffusion term is constant. zero_diffusion: Whether the diffusion term is zero. **kwargs: Additional keyword arguments. """ def __init__( self, space: AbstractApproxSpace, r: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, a: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_ones, d: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, f: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, g: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, constant_advection: bool = True, constant_diffusion: bool = True, zero_diffusion: bool = True, **kwargs, ): super().__init__( space, linear=True, residual_size=1, bc_residual_size=1, **kwargs ) self.r = r self.a = a self.d = d self.f = f self.g = g self.constant_diffusion = constant_diffusion self.constant_advection = constant_advection self.zero_diffusion = zero_diffusion
[docs] def rhs(self, w: MultiLabelTensor, x: LabelTensor, mu: LabelTensor) -> torch.Tensor: r"""Compute the right-hand side (RHS) of the PDE. Args: w: State tensor. x: Spatial coordinates tensor. mu: Parameter tensor. Returns: The source term f(x, mu). """ return self.f(x, mu)
[docs] def operator( self, w: MultiLabelTensor, x: LabelTensor, mu: LabelTensor ) -> torch.Tensor: r"""Compute the differential operator of the PDE. Args: w: State tensor. x: Spatial coordinates tensor. mu: Parameter tensor. Returns: The result of applying the operator to the state. """ u = w.get_components() u_x = self.grad(w, x) reaction = self.r(x, mu) * u advection = self.a(x, mu) * u_x if self.zero_diffusion: return reaction + advection diffusion = self.grad(self.d(x, mu) * u_x, x) return reaction + advection - diffusion
[docs] def bc_rhs( self, w: MultiLabelTensor, x: LabelTensor, n: LabelTensor, mu: LabelTensor ) -> torch.Tensor: r"""Compute the RHS for the boundary conditions. Args: w: State tensor. x: Boundary coordinates tensor. n: Normal vector tensor. mu: Parameter tensor. Returns: The boundary condition g(x, mu). """ return self.g(x, mu)
[docs] def bc_operator( self, w: MultiLabelTensor, x: LabelTensor, n: LabelTensor, mu: LabelTensor ) -> torch.Tensor: r"""Compute the operator for the boundary conditions. Args: w: State tensor. x: Boundary coordinates tensor. n: Normal vector tensor. mu: Parameter tensor. Returns: The boundary operator applied to the state. """ u = w.get_components() assert isinstance(u, torch.Tensor) return u
[docs] class AdvectionReactionDiffusion2DDirichletStrongForm(StrongFormEllipticPDE): r"""2D advection-reaction-diffusion problem with strong Dirichlet BCs. .. math:: r(x, \mu) u(x, \mu) + a(x, \mu) \cdot \nabla_x u(x, \mu) - \nabla_x \cdot (d(x, \mu) \nabla_x u(x, \mu)) & = f(x, \mu) \text{ in } \Omega, \\ u(x, \mu) & = g(x, \mu) \text{ on } \partial \Omega. By default, $r = 0$, $a = (1, 1)$, $d = [(0, 0), (0, 0)]$, $f = 0$, and $g = 0$. The user can specify these functions as needed. Args: space: The approximation space for the problem. r: Callable representing the reaction term r(x, mu). a: Callable representing the advection term a(x, mu). d: Callable representing the advection term d(x, mu). f: Callable representing the source term f(x, mu). g: Callable representing the Dirichlet boundary condition g(x, mu). constant_advection: Whether the advection term is constant. constant_diffusion: Whether the diffusion term is constant. zero_diffusion: Whether the diffusion term is zero. **kwargs: Additional keyword arguments. """ def __init__( self, space: AbstractApproxSpace, r: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, a: Callable[[LabelTensor, LabelTensor], torch.Tensor] = vector_ones, d: Callable[[LabelTensor, LabelTensor], torch.Tensor] = matrix_zero, f: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, g: Callable[[LabelTensor, LabelTensor], torch.Tensor] = scalar_zero, constant_advection: bool = True, constant_diffusion: bool = True, zero_diffusion: bool = True, **kwargs, ): super().__init__( space, linear=True, residual_size=1, bc_residual_size=1, **kwargs ) self.r = r self.a = a self.d = d self.f = f self.g = g self.constant_diffusion = constant_diffusion self.constant_advection = constant_advection self.zero_diffusion = zero_diffusion
[docs] def rhs(self, w: MultiLabelTensor, x: LabelTensor, mu: LabelTensor) -> torch.Tensor: r"""Compute the right-hand side (RHS) of the PDE. Args: w: State tensor. x: Spatial coordinates tensor. mu: Parameter tensor. Returns: The source term f(x, mu). """ return self.f(x, mu)
[docs] def operator( self, w: MultiLabelTensor, x: LabelTensor, mu: LabelTensor ) -> torch.Tensor: r"""Compute the differential operator of the PDE. Args: w: State tensor. x: Spatial coordinates tensor. mu: Parameter tensor. Returns: The result of applying the operator to the state. """ u = w.get_components() # scalar field, [batch, 1] a = self.a(x, mu) # 2D vector, [batch, 2] d = self.d(x, mu) # 2x2 matrix, [batch, 2, 2] reaction = self.r(x, mu) * u u_x, u_y = self.grad(w, x) advection = a[:, 0:1] * u_x + a[:, 1:2] * u_y if self.zero_diffusion: return reaction + advection diffusion_x = d[:, 0, 0:1] * u_x + d[:, 0, 1:2] * u_y diffusion_y = d[:, 1, 0:1] * u_x + d[:, 1, 1:2] * u_y diffusion_xx, _ = self.grad(diffusion_x, x) _, diffusion_yy = self.grad(diffusion_y, x) diffusion = diffusion_xx + diffusion_yy return reaction + advection - diffusion
[docs] def bc_rhs( self, w: MultiLabelTensor, x: LabelTensor, n: LabelTensor, mu: LabelTensor ) -> torch.Tensor: r"""Compute the RHS for the boundary conditions. Args: w: State tensor. x: Boundary coordinates tensor. n: Normal vector tensor. mu: Parameter tensor. Returns: The boundary condition g(x, mu). """ return self.g(x, mu)
[docs] def bc_operator( self, w: MultiLabelTensor, x: LabelTensor, n: LabelTensor, mu: LabelTensor ) -> torch.Tensor: r"""Compute the operator for the boundary conditions. Args: w: State tensor. x: Boundary coordinates tensor. n: Normal vector tensor. mu: Parameter tensor. Returns: The boundary operator applied to the state. """ u = w.get_components() assert isinstance(u, torch.Tensor) return u