diff --git a/docs/source/_rst/_code.rst b/docs/source/_rst/_code.rst index 704298020..60eb42408 100644 --- a/docs/source/_rst/_code.rst +++ b/docs/source/_rst/_code.rst @@ -224,6 +224,7 @@ Equation Zoo Acoustic Wave Equation Advection Equation Allen-Cahn Equation + Burgers' Equation Diffusion-Reaction Equation Fixed Flux Fixed Gradient @@ -252,14 +253,15 @@ Problem Zoo .. toctree:: :titlesonly: - AcousticWaveProblem - AdvectionProblem - AllenCahnProblem - DiffusionReactionProblem - HelmholtzProblem - InversePoisson2DSquareProblem - Poisson2DSquareProblem - SupervisedProblem + Acoustic Wave Problem + Advection Problem + Allen-Cahn Problem + Burgers' Problem + Diffusion-Reaction Problem + Helmholtz Problem + Inverse Poisson 2D Square Problem + Poisson 2D Square Problem + Supervised Problem Geometrical Domains diff --git a/docs/source/_rst/equation/zoo/burgers_equation.rst b/docs/source/_rst/equation/zoo/burgers_equation.rst new file mode 100644 index 000000000..8f478621f --- /dev/null +++ b/docs/source/_rst/equation/zoo/burgers_equation.rst @@ -0,0 +1,7 @@ +Burgers' Equation +==================== +.. currentmodule:: pina.equation.zoo.burgers_equation + +.. automodule:: pina._src.equation.zoo.burgers_equation + :members: + :show-inheritance: diff --git a/docs/source/_rst/problem/zoo/burgers_problem.rst b/docs/source/_rst/problem/zoo/burgers_problem.rst new file mode 100644 index 000000000..75151d8d8 --- /dev/null +++ b/docs/source/_rst/problem/zoo/burgers_problem.rst @@ -0,0 +1,9 @@ +Burgers' Problem +===================== +.. currentmodule:: pina.problem.zoo.burgers_problem + +.. automodule:: pina._src.problem.zoo.burgers_problem + +.. autoclass:: pina._src.problem.zoo.burgers_problem.BurgersProblem + :members: + :show-inheritance: diff --git a/pina/_src/equation/zoo/burgers_equation.py b/pina/_src/equation/zoo/burgers_equation.py new file mode 100644 index 000000000..07c8eed22 --- /dev/null +++ b/pina/_src/equation/zoo/burgers_equation.py @@ -0,0 +1,84 @@ +"""Module for defining the Burgers equation.""" + +from pina._src.core.operator import laplacian, grad +from pina._src.core.utils import check_consistency +from pina._src.equation.equation import Equation +import torch + + +class BurgersEquation(Equation): + r""" + Implementation of the N-dimensional Burgers' equation, defined as follows: + + .. math:: + + \frac{\partial u}{\partial t} + u \cdot \nabla u = \nu \Delta u + + Here, :math:`\nu` is the viscosity coefficient. + """ + + def __init__(self, nu): + """ + Initialization of the :class:`BurgersEquation` class. + + :param nu: The viscosity coefficient. + :type nu: float | int + :raises ValueError: If ``nu`` is not a float or an int. + :raises ValueError: If ``nu`` is negative. + """ + # Check consistency + check_consistency(nu, (float, int)) + if nu < 0: + raise ValueError( + "The viscosity ``nu`` must be a non-negative float or int." + ) + + # Store viscosity coefficient + self.nu = nu + + def equation(input_, output_): + """ + Implementation of the Burgers' equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :raises ValueError: If the number of output components does not + match the number of spatial dimensions. + :raises ValueError: If the ``input_`` labels do not contain the time + variable 't'. + :return: The residual of the Burgers' equation. + :rtype: LabelTensor + """ + # Store labels + spatial_d = [di for di in input_.labels if di != "t"] + + # Ensure consistency between output and spatial dimensions + if len(output_.labels) != len(spatial_d): + raise ValueError( + f"The number of output components must match the number of " + f"spatial dimensions. Got {len(output_.labels)} and " + f"{len(spatial_d)}." + ) + + # Ensure time is passed as input + if "t" not in input_.labels: + raise ValueError( + "The ``input_`` labels must contain the time 't' variable." + ) + + # Compute the differential terms + u_t = grad(output_, input_, d=["t"]) + u_x = grad(output_, input_, d=spatial_d) + u_xx = laplacian(output_, input_, d=spatial_d) + + # Compute the convective term componentwise + convection = torch.zeros_like(output_) + for i, c in enumerate(output_.labels): + convection[:, i] = sum( + output_[output_.labels[j]] * u_x[f"d{c}d{spatial_d[j]}"] + for j in range(len(spatial_d)) + ).reshape(-1) + + return u_t + convection - self.nu * u_xx + + super().__init__(equation) diff --git a/pina/_src/problem/zoo/burgers_problem.py b/pina/_src/problem/zoo/burgers_problem.py new file mode 100644 index 000000000..0ba779a22 --- /dev/null +++ b/pina/_src/problem/zoo/burgers_problem.py @@ -0,0 +1,102 @@ +"""Formulation of the Burgers' problem.""" + +import torch +from pina._src.problem.time_dependent_problem import TimeDependentProblem +from pina._src.domain.cartesian_domain import CartesianDomain +from pina._src.problem.spatial_problem import SpatialProblem +from pina._src.condition.condition import Condition +from pina._src.core.utils import check_consistency +from pina._src.equation.equation import Equation +from pina._src.equation.zoo.fixed_value import FixedValue +from pina._src.equation.zoo.burgers_equation import BurgersEquation + + +def initial_condition(input_, output_): + """ + Definition of the initial condition of the Burgers' problem. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the initial condition. + :rtype: LabelTensor + """ + return output_ + torch.sin(torch.pi * input_["x"]) + + +class BurgersProblem(TimeDependentProblem, SpatialProblem): + r""" + Implementation of the one-dimensional Burgers' problem on the space-time + domain :math:`\Omega\times T = [-1, 1] \times [0, 1]`. + + The problem is governed by the Burgers' equation + + .. math:: + + \frac{\partial u}{\partial t} + u \frac{\partial u}{\partial x} = + \nu \frac{\partial^2 u}{\partial x^2}, + + where :math:`u = u(x, t)` is the solution field and :math:`\nu \geq 0` + is the viscosity coefficient. For :math:`\nu = 0`, the equation reduces + to the inviscid Burgers' equation. + + Homogeneous Dirichlet boundary conditions are imposed at the spatial + boundaries: + + .. math:: + u(-1, t) = u(1, t) = 0, \qquad t \in [0, 1]. + + The initial condition is prescribed as + + .. math:: + u(x, 0) = -\sin(\pi x), \qquad x \in [-1, 1]. + + + .. seealso:: + + **Original reference**: Raissi M., Perdikaris P., Karniadakis G. E. + (2017). + *Physics Informed Deep Learning (Part I): Data-driven Solutions of + Nonlinear Partial Differential Equations*. + DOI: `10.48550 `_. + + :Example: + + >>> problem = BurgersProblem() + """ + + output_variables = ["u"] + spatial_domain = CartesianDomain({"x": [-1, 1]}) + temporal_domain = CartesianDomain({"t": [0, 1]}) + + domains = { + "D": spatial_domain.update(temporal_domain), + "t0": spatial_domain.update(CartesianDomain({"t": 0})), + "boundary": spatial_domain.partial().update(temporal_domain), + } + + conditions = { + "boundary": Condition(domain="boundary", equation=FixedValue(0.0)), + "t0": Condition(domain="t0", equation=Equation(initial_condition)), + } + + def __init__(self, nu=0): + """ + Initialization of the :class:`BurgersProblem` class. + + :param nu: The viscosity coefficient. + :type nu: float | int + :raises ValueError: If ``nu`` is not a float or an int. + :raises ValueError: If ``nu`` is negative. + """ + super().__init__() + + # Check consistency + check_consistency(nu, (float, int)) + if nu < 0: + raise ValueError( + "The viscosity ``nu`` must be a non-negative float or int." + ) + + self.conditions["D"] = Condition( + domain="D", equation=BurgersEquation(nu) + ) diff --git a/pina/equation/__init__.py b/pina/equation/__init__.py index 5c2806f53..1a0ce8bb0 100644 --- a/pina/equation/__init__.py +++ b/pina/equation/__init__.py @@ -36,6 +36,7 @@ "AdvectionEquation": ".zoo", "AllenCahnEquation": ".zoo", "DiffusionReactionEquation": ".zoo", + "BurgersEquation": ".zoo", } diff --git a/pina/equation/zoo.py b/pina/equation/zoo.py index 140c836d7..4febb63e6 100644 --- a/pina/equation/zoo.py +++ b/pina/equation/zoo.py @@ -12,6 +12,7 @@ "Laplace", "PoissonEquation", "AcousticWaveEquation", + "BurgersEquation", ] from pina._src.equation.zoo.acoustic_wave_equation import AcousticWaveEquation @@ -22,6 +23,7 @@ ) from pina._src.equation.zoo.helmholtz_equation import HelmholtzEquation from pina._src.equation.zoo.poisson_equation import PoissonEquation +from pina._src.equation.zoo.burgers_equation import BurgersEquation from pina._src.equation.zoo.fixed_value import FixedValue from pina._src.equation.zoo.fixed_gradient import FixedGradient from pina._src.equation.zoo.fixed_flux import FixedFlux diff --git a/pina/problem/zoo.py b/pina/problem/zoo.py index 6c027ed54..47b204425 100644 --- a/pina/problem/zoo.py +++ b/pina/problem/zoo.py @@ -9,6 +9,7 @@ "DiffusionReactionProblem", "InversePoisson2DSquareProblem", "AcousticWaveProblem", + "BurgersProblem", ] from pina._src.problem.zoo.acoustic_wave_problem import AcousticWaveProblem @@ -17,6 +18,7 @@ from pina._src.problem.zoo.advection_problem import AdvectionProblem from pina._src.problem.zoo.helmholtz_problem import HelmholtzProblem from pina._src.problem.zoo.poisson_problem import Poisson2DSquareProblem +from pina._src.problem.zoo.burgers_problem import BurgersProblem from pina._src.problem.zoo.diffusion_reaction_problem import ( DiffusionReactionProblem, ) diff --git a/tests/test_equation_zoo/test_burgers_equation.py b/tests/test_equation_zoo/test_burgers_equation.py new file mode 100644 index 000000000..89825aa9b --- /dev/null +++ b/tests/test_equation_zoo/test_burgers_equation.py @@ -0,0 +1,37 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import BurgersEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.sin(pts["x", "y"]) * torch.cos(pts["y", "t"]) +u.labels = ["u", "v"] + + +@pytest.mark.parametrize("nu", [0, 1, 2.5]) +def test_burgers_equation(nu): + + # Constructor + equation = BurgersEquation(nu=nu) + + # Should fail if nu is not a float or int + with pytest.raises(ValueError): + BurgersEquation(nu="invalid") + + # Should fail if nu is negative + with pytest.raises(ValueError): + BurgersEquation(nu=-1) + + # Residual + residual = equation.residual(pts, u) + assert residual.shape == u.shape + + # Should fail if the input has no 't' label + with pytest.raises(ValueError): + residual = equation.residual(pts["x", "y"], u) + + # Should fail if output and spatial dimensions do not match + with pytest.raises(ValueError): + residual = equation.residual(pts, u["u"]) diff --git a/tests/test_problem_zoo/test_burgers_problem.py b/tests/test_problem_zoo/test_burgers_problem.py new file mode 100644 index 000000000..d7f9d2128 --- /dev/null +++ b/tests/test_problem_zoo/test_burgers_problem.py @@ -0,0 +1,23 @@ +import pytest +from pina.problem.zoo import BurgersProblem +from pina.problem import SpatialProblem, TimeDependentProblem + + +@pytest.mark.parametrize("nu", [0.1, 1]) +def test_constructor(nu): + + problem = BurgersProblem(nu=nu) + problem.discretise_domain(n=10, mode="random", domains=None) + assert problem.are_all_domains_discretised + assert isinstance(problem, SpatialProblem) + assert isinstance(problem, TimeDependentProblem) + assert hasattr(problem, "conditions") + assert isinstance(problem.conditions, dict) + + # Should fail if nu is not a float or int + with pytest.raises(ValueError): + BurgersProblem(nu="invalid") + + # Should fail if nu is negative + with pytest.raises(ValueError): + BurgersProblem(nu=-0.1)