Source code for scimba_torch.physical_models.temporal_pde.heat_equation
"""Heat equation in strong form."""
from typing import Callable
import torch
from torch._tensor import Tensor
from scimba_torch.approximation_space.abstract_space import AbstractApproxSpace
from scimba_torch.physical_models.elliptic_pde.laplacians import (
Laplacian1DDirichletStrongForm,
Laplacian1DNeumannStrongForm,
Laplacian2DDirichletStrongForm,
)
from scimba_torch.physical_models.temporal_pde.abstract_temporal_pde import (
FirstOrderTemporalPDE,
)
from scimba_torch.utils.scimba_tensors import LabelTensor, MultiLabelTensor
from scimba_torch.utils.typing_protocols import VarArgCallable
[docs]
class HeatEquation1DStrongForm(FirstOrderTemporalPDE):
"""Implementation of a 1D heat equation with Neumann BCs in strong form.
Args:
space: The approximation space for the problem
init: Callable for the initial condition
f: Source term function
g: Neumann boundary condition function
**kwargs: Additional keyword arguments
"""
def __init__(
self,
space: AbstractApproxSpace,
init: Callable,
f: Callable,
g: Callable,
**kwargs,
):
super().__init__(space, linear=True, **kwargs)
self.space_component = Laplacian1DNeumannStrongForm(space, f, g)
self.f = f
self.g = g
self.init = init
[docs]
def space_operator(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> torch.Tensor | tuple[torch.Tensor, ...]:
"""Apply the spatial operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Spatial operator tensor
"""
return self.space_component.operator(w, x, mu)
[docs]
def bc_operator(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Apply the boundary condition operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition operator tensor
"""
return self.space_component.bc_operator(w, x, n, mu)
[docs]
def rhs(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Compute the right-hand side (RHS) of the PDE.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
RHS tensor
"""
return self.f(w, t, x, mu)
[docs]
def bc_rhs(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Compute the boundary condition RHS.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition RHS tensor
"""
return self.g(w, t, x, n, mu)
[docs]
def initial_condition(self, x: LabelTensor, mu: LabelTensor) -> Tensor:
"""Compute the initial condition.
Args:
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Initial condition tensor
"""
return self.init(x, mu)
[docs]
def functional_operator(
self,
func: VarArgCallable,
t: torch.Tensor,
x: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator.
Args:
func: Callable representing the function
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor
"""
# space operator
grad_func = torch.func.jacrev(func, 1)
space_op = -mu[0] * torch.func.jacrev(grad_func, 1)(t, x, mu, theta)
# time operator
time_op = torch.func.jacrev(func, 0)(t, x, mu, theta)
return (time_op + space_op)[0, 0]
# Neumann conditions
[docs]
def functional_operator_bc(
self,
func: VarArgCallable,
t: torch.Tensor,
x: torch.Tensor,
n: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator for boundary conditions.
Args:
func: Callable representing the function
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor for boundary conditions
"""
grad_u = torch.func.jacrev(func, 1)(t, x, mu, theta)
return (grad_u * n)[0]
[docs]
def functional_operator_ic(
self,
func: VarArgCallable,
x: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator for initial conditions.
Args:
func: Callable representing the function
x: Spatial coordinate tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor for initial conditions
"""
t = torch.zeros_like(x)
return func(t, x, mu, theta)
[docs]
class HeatEquation1DDirichletStrongForm(FirstOrderTemporalPDE):
r"""Implementation of a 1D heat equation with Dirichlet BCs in strong form.
Args:
space: The approximation space for the problem
init: Callable for the initial condition
f: Source term function
g: Dirichlet boundary condition function
**kwargs: Additional keyword arguments
"""
def __init__(
self,
space: AbstractApproxSpace,
init: Callable,
f: Callable,
g: Callable,
**kwargs,
):
super().__init__(space, linear=True, **kwargs)
self.space_component = Laplacian1DDirichletStrongForm(space, f, g)
self.f = f
self.g = g
self.init = init
[docs]
def space_operator(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Apply the spatial operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Spatial operator tensor
"""
return self.space_component.operator(w, x, mu)
# Rémi: time_operator is already in FirstOrderTemporalPDE!
# def time_operator(
# self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
# ) -> Tensor:
# return self.grad(w.get_components(), t)
[docs]
def bc_operator(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Apply the boundary condition operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition operator tensor
"""
return self.space_component.bc_operator(w, x, n, mu)
[docs]
def rhs(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Compute the right-hand side (RHS) of the PDE.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
RHS tensor
"""
return self.f(w, t, x, mu)
[docs]
def bc_rhs(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Compute the boundary condition RHS.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition RHS tensor
"""
return self.g(w, t, x, n, mu)
[docs]
def initial_condition(self, x: LabelTensor, mu: LabelTensor) -> Tensor:
"""Compute the initial condition.
Args:
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Initial condition tensor
"""
return self.init(x, mu)
[docs]
def functional_operator(
self,
func: VarArgCallable,
t: torch.Tensor,
x: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator.
Args:
func: Callable representing the function
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor
"""
# space operator
grad_func = torch.func.jacrev(func, 1)
space_op = -mu[0] * torch.func.jacrev(grad_func, 1)(t, x, mu, theta)
# time operator
time_op = torch.func.jacrev(func, 0)(t, x, mu, theta)
return (time_op + space_op)[0, 0]
# Dirichlet conditions
[docs]
def functional_operator_bc(
self,
func: VarArgCallable,
t: torch.Tensor,
x: torch.Tensor,
n: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator for boundary conditions.
Args:
func: Callable representing the function
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor for boundary conditions
"""
return func(t, x, mu, theta)
[docs]
def functional_operator_ic(
self,
func: VarArgCallable,
x: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator for initial conditions.
Args:
func: Callable representing the function
x: Spatial coordinate tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor for initial conditions
"""
t = torch.zeros_like(x)
return func(t, x, mu, theta)
[docs]
class HeatEquation2DStrongForm(FirstOrderTemporalPDE):
r"""Implementation of a 2D Laplacian problem with Dirichlet BCs in strong form.
Args:
space: The approximation space for the problem
init: Callable for the initial condition
f: Source term function
g: Dirichlet boundary condition function
**kwargs: Additional keyword arguments
"""
def __init__(
self,
space: AbstractApproxSpace,
init: Callable,
f: Callable,
g: Callable,
**kwargs,
):
super().__init__(space, linear=True, **kwargs)
self.space_component = Laplacian2DDirichletStrongForm(space, f, g)
self.f = f
self.g = g
self.init = init
[docs]
def space_operator(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Apply the spatial operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Spatial operator tensor
"""
return self.space_component.operator(w, x, mu)
# Rémi: time_operator is already in FirstOrderTemporalPDE!
# def time_operator(
# self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
# ) -> Tensor:
# return self.grad(w.get_components(), t)
[docs]
def bc_operator(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Apply the boundary condition operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition operator tensor
"""
return self.space_component.bc_operator(w, x, n, mu)
[docs]
def rhs(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Compute the right-hand side (RHS) of the PDE.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
RHS tensor
"""
return self.f(w, t, x, mu)
[docs]
def bc_rhs(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Compute the boundary condition RHS.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition RHS tensor
"""
return self.g(w, t, x, n, mu)
[docs]
def initial_condition(self, x: LabelTensor, mu: LabelTensor) -> Tensor:
"""Compute the initial condition.
Args:
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Initial condition tensor
"""
return self.init(x, mu)
[docs]
class HeatEquation2DStrongFormImplicit(FirstOrderTemporalPDE):
r"""2D heat equation for implicit discrete_pinns.
Args:
space: the approx. space.
init: the rhs of the initial condition.
f: the rhs of the residual.
g: the rhs of the boundary condition.
**kwargs: Additional keyword arguments.
"""
def __init__(
self,
space: AbstractApproxSpace,
init: Callable,
f: Callable,
g: Callable,
**kwargs,
):
super().__init__(space, linear=True, **kwargs)
self.space_component = Laplacian2DDirichletStrongForm(space, f, g)
self.f = f
self.g = g
self.init = init
self.dt = kwargs.get("dt", 1e-3)
self.alpha = kwargs.get("alpha", 1.0)
[docs]
def space_operator(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Apply the spatial operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Spatial operator tensor
"""
return self.space_component.operator(w, x, mu)
[docs]
def bc_operator(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Apply the boundary condition operator.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition operator tensor
"""
return self.space_component.bc_operator(w, x, n, mu)
[docs]
def rhs(
self, w: MultiLabelTensor, t: LabelTensor, x: LabelTensor, mu: LabelTensor
) -> Tensor:
"""Compute the right-hand side (RHS) of the PDE.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
RHS tensor
"""
return self.f(w, t, x, mu)
[docs]
def bc_rhs(
self,
w: MultiLabelTensor,
t: LabelTensor,
x: LabelTensor,
n: LabelTensor,
mu: LabelTensor,
) -> Tensor:
"""Compute the boundary condition RHS.
Args:
w: Solution tensor
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
Returns:
Boundary condition RHS tensor
"""
return self.g(w, t, x, n, mu)
[docs]
def initial_condition(self, x: LabelTensor, mu: LabelTensor) -> Tensor:
"""Compute the initial condition.
Args:
x: Spatial coordinate tensor
mu: Parameter tensor
Returns:
Initial condition tensor
"""
return self.init(x, mu)
[docs]
def functional_operator(
self,
func: VarArgCallable,
# t: LabelTensor,
x: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator.
Args:
func: Callable representing the function
x: Spatial coordinate tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor
"""
# space operator
grad_func = torch.func.jacrev(func, 0)
space_op = -mu[0] * torch.func.jacrev(grad_func, 0)(x, mu, theta)
return func(x, mu, theta) - self.alpha * self.dt * space_op[0, 0]
# Dirichlet conditions
[docs]
def functional_operator_bc(
self,
func: VarArgCallable,
t: LabelTensor,
x: torch.Tensor,
n: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator for boundary conditions.
Args:
func: Callable representing the function
t: Temporal coordinate tensor
x: Spatial coordinate tensor
n: Normal vector tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor for boundary conditions
"""
return func(x, mu, theta)
[docs]
def functional_operator_ic(
self,
func: VarArgCallable,
x: torch.Tensor,
mu: torch.Tensor,
theta: torch.Tensor,
) -> torch.Tensor:
"""Compute the functional operator for initial conditions.
Args:
func: Callable representing the function
x: Spatial coordinate tensor
mu: Parameter tensor
theta: Additional parameters tensor
Returns:
Functional operator tensor for initial conditions
"""
return func(x, mu, theta)