From 429a505ac8cf8bb0285fb90715279fc7d27124f7 Mon Sep 17 00:00:00 2001 From: GiovanniCanali Date: Thu, 16 Apr 2026 15:26:39 +0200 Subject: [PATCH 1/5] add interface + base class structure --- docs/source/_rst/_code.rst | 5 +- docs/source/_rst/equation/base_equation.rst | 7 ++ .../source/_rst/equation/equation_factory.rst | 4 + pina/_src/equation/base_equation.py | 67 +++++++++++ pina/_src/equation/equation.py | 57 +++++----- pina/_src/equation/equation_factory.py | 42 +++---- pina/_src/equation/equation_interface.py | 54 ++------- pina/_src/equation/system_equation.py | 94 ++++++++-------- pina/equation/__init__.py | 11 +- tests/test_equation/test_equation.py | 47 +++++--- tests/test_equation/test_system_equation.py | 104 ++++++++---------- 11 files changed, 271 insertions(+), 221 deletions(-) create mode 100644 docs/source/_rst/equation/base_equation.rst create mode 100644 pina/_src/equation/base_equation.py diff --git a/docs/source/_rst/_code.rst b/docs/source/_rst/_code.rst index 02e8e1242..6238e38b2 100644 --- a/docs/source/_rst/_code.rst +++ b/docs/source/_rst/_code.rst @@ -195,9 +195,10 @@ Equations and Differential Operators .. toctree:: :titlesonly: - EquationInterface + Equation Interface + Base Equation Equation - SystemEquation + System Equation Equation Factory Differential Operators diff --git a/docs/source/_rst/equation/base_equation.rst b/docs/source/_rst/equation/base_equation.rst new file mode 100644 index 000000000..5bb98901f --- /dev/null +++ b/docs/source/_rst/equation/base_equation.rst @@ -0,0 +1,7 @@ +Base Equation +==================== + +.. currentmodule:: pina.equation.base_equation +.. autoclass:: pina._src.equation.base_equation.BaseEquation + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/_rst/equation/equation_factory.rst b/docs/source/_rst/equation/equation_factory.rst index 5282aa948..010813319 100644 --- a/docs/source/_rst/equation/equation_factory.rst +++ b/docs/source/_rst/equation/equation_factory.rst @@ -39,5 +39,9 @@ Equation Factory :show-inheritance: .. autoclass:: pina._src.equation.equation_factory.Poisson + :members: + :show-inheritance: + +.. autoclass:: pina._src.equation.equation_factory.AcousticWave :members: :show-inheritance: \ No newline at end of file diff --git a/pina/_src/equation/base_equation.py b/pina/_src/equation/base_equation.py new file mode 100644 index 000000000..4fff8dd3b --- /dev/null +++ b/pina/_src/equation/base_equation.py @@ -0,0 +1,67 @@ +"""Module for the Base Equation.""" + +from abc import ABCMeta, abstractmethod +import torch + + +class BaseEquation(metaclass=ABCMeta): + """ + Base class for all equations, implementing common functionality. + + Equations are fundamental components in PINA, representing mathematical + constraints that must be satisfied by the model outputs. They can be passed + to :class:`~pina.condition.condition.Condition` objects to define the + conditions under which the model is trained. + + All specific equation types should inherit from this class and implement its + abstract methods. + + This class is not meant to be instantiated directly. + """ + + @abstractmethod + def residual(self, input_, output_, params_): + """ + Evaluate the equation residual at the given inputs. + + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced by a + :class:`torch.nn.Module` instance. + :param dict params_: An optional dictionary of unknown parameters, used + in :class:`~pina.problem.inverse_problem.InverseProblem` settings. + If the equation is not related to an inverse problem, this should be + set to ``None``. Default is ``None``. + :return: The residual values of the equation. + :rtype: LabelTensor + """ + + def to(self, device): + """ + Move all tensor attributes to the specified device. + + :param torch.device device: The target device to move the tensors to. + :return: The instance moved to the specified device. + :rtype: BaseEquation + """ + # Iterate over all attributes of the Equation + for key, val in self.__dict__.items(): + + # Move tensors in dictionaries to the specified device + if isinstance(val, dict): + self.__dict__[key] = { + k: v.to(device) if torch.is_tensor(v) else v + for k, v in val.items() + } + + # Move tensors in lists to the specified device + elif isinstance(val, list): + self.__dict__[key] = [ + v.to(device) if torch.is_tensor(v) else v for v in val + ] + + # Move tensor attributes to the specified device + elif torch.is_tensor(val): + self.__dict__[key] = val.to(device) + + return self diff --git a/pina/_src/equation/equation.py b/pina/_src/equation/equation.py index a1d67628c..d10da2bbe 100644 --- a/pina/_src/equation/equation.py +++ b/pina/_src/equation/equation.py @@ -1,62 +1,65 @@ """Module for the Equation.""" import inspect -from pina._src.equation.equation_interface import EquationInterface +from pina._src.equation.base_equation import BaseEquation -class Equation(EquationInterface): +class Equation(BaseEquation): """ - Implementation of the Equation class. Every ``equation`` passed to a - :class:`~pina.condition.condition.Condition` object must be either an - instance of :class:`Equation` or - :class:`~pina.equation.system_equation.SystemEquation`. + Implementation of the Equation class, representing a single mathematical + equation to be satisfied by the model outputs. + + It can be passed to a :class:`~pina.condition.condition.Condition` object to + define the conditions under which the model is trained. """ def __init__(self, equation): """ Initialization of the :class:`Equation` class. - :param Callable equation: A ``torch`` callable function used to compute - the residual of a mathematical equation. + :param Callable equation: A callable function used to compute the + residual of a mathematical equation. :raises ValueError: If the equation is not a callable function. """ + # Check consistency if not callable(equation): - raise ValueError( - "equation must be a callable function." - "Expected a callable function, got " - f"{equation}" - ) - # compute the signature + raise ValueError(f"Expected a callable function, got {equation}") + + # Compute the signature length sig = inspect.signature(equation) self.__len_sig = len(sig.parameters) self.__equation = equation def residual(self, input_, output_, params_=None): """ - Compute the residual of the equation. + Evaluate the equation residual at the given inputs. - :param LabelTensor input_: Input points where the equation is evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced by a :class:`torch.nn.Module` instance. - :param dict params_: Dictionary of unknown parameters, associated with a - :class:`~pina.problem.inverse_problem.InverseProblem` instance. - If the equation is not related to a - :class:`~pina.problem.inverse_problem.InverseProblem` instance, the - parameters must be initialized to ``None``. Default is ``None``. - :return: The computed residual of the equation. + :param dict params_: An optional dictionary of unknown parameters, used + in :class:`~pina.problem.inverse_problem.InverseProblem` settings. + If the equation is not related to an inverse problem, this should be + set to ``None``. Default is ``None``. + :raises RuntimeError: If the underlying equation signature is neither of + length 2 for direct problems nor of length 3 for inverse problems. + :return: The residual values of the equation. :rtype: LabelTensor - :raises RuntimeError: If the underlying equation signature length is not - 2 (direct problem) or 3 (inverse problem). """ # Move the equation to the input_ device self.to(input_.device) - # Call the underlying equation based on its signature length + # Evaluate the equation for direct problems if self.__len_sig == 2: return self.__equation(input_, output_) + + # Evaluate the equation for inverse problems if self.__len_sig == 3: return self.__equation(input_, output_, params_) + + # Raise an error if the signature length is unexpected raise RuntimeError( f"Unexpected number of arguments in equation: {self.__len_sig}. " - "Expected either 2 (direct problem) or 3 (inverse problem)." + "Expected either 2 for direct problems, or 3 for inverse problems." ) diff --git a/pina/_src/equation/equation_factory.py b/pina/_src/equation/equation_factory.py index fccd2520f..acc9fd1f3 100644 --- a/pina/_src/equation/equation_factory.py +++ b/pina/_src/equation/equation_factory.py @@ -28,11 +28,11 @@ def equation(_, output_): """ Definition of the equation to enforce a fixed value. - :param LabelTensor input_: Input points where the equation is - evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a - :class:`torch.nn.Module` instance. - :return: The computed residual of the equation. + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. :rtype: LabelTensor """ if components is None: @@ -66,11 +66,11 @@ def equation(input_, output_): """ Definition of the equation to enforce a fixed gradient. - :param LabelTensor input_: Input points where the equation is - evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a - :class:`torch.nn.Module` instance. - :return: The computed residual of the equation. + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. :rtype: LabelTensor """ return grad(output_, input_, components=components, d=d) - value @@ -101,11 +101,11 @@ def equation(input_, output_): """ Definition of the equation to enforce a fixed flux. - :param LabelTensor input_: Input points where the equation is - evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a - :class:`torch.nn.Module` instance. - :return: The computed residual of the equation. + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. :rtype: LabelTensor """ return div(output_, input_, components=components, d=d) - value @@ -137,11 +137,11 @@ def equation(input_, output_): """ Definition of the equation to enforce a fixed laplacian. - :param LabelTensor input_: Input points where the equation is - evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a - :class:`torch.nn.Module` instance. - :return: The computed residual of the equation. + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. :rtype: LabelTensor """ return ( @@ -158,7 +158,7 @@ class Laplace(FixedLaplacian): # pylint: disable=R0903 .. math:: - \delta u = 0 + \Delta u = 0 """ diff --git a/pina/_src/equation/equation_interface.py b/pina/_src/equation/equation_interface.py index 82b86dbd0..fa59de678 100644 --- a/pina/_src/equation/equation_interface.py +++ b/pina/_src/equation/equation_interface.py @@ -1,40 +1,31 @@ """Module for the Equation Interface.""" from abc import ABCMeta, abstractmethod -import torch class EquationInterface(metaclass=ABCMeta): """ - Abstract base class for equations. - - Equations in PINA simplify the training process. When defining a problem, - each equation passed to a :class:`~pina.condition.condition.Condition` - object must be either an :class:`~pina.equation.equation.Equation` or a - :class:`~pina.equation.system_equation.SystemEquation` instance. - - An :class:`~pina.equation.equation.Equation` is a wrapper for a callable - function, while :class:`~pina.equation.system_equation.SystemEquation` - wraps a list of callable functions. To streamline code writing, PINA - provides a diverse set of pre-implemented equations, such as - :class:`~pina.equation.equation_factory.FixedValue`, - :class:`~pina.equation.equation_factory.FixedGradient`, and many others. + Abstract interface for all equations. """ @abstractmethod - def residual(self, input_, output_, params_): + def residual(self, input_, output_, params_=None): """ - Abstract method to compute the residual of an equation. + Evaluate the equation residual at the given inputs. - :param LabelTensor input_: Input points where the equation is evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced by a :class:`torch.nn.Module` instance. - :param dict params_: Dictionary of unknown parameters, associated with a - :class:`~pina.problem.inverse_problem.InverseProblem` instance. - :return: The computed residual of the equation. + :param dict params_: An optional dictionary of unknown parameters, used + in :class:`~pina.problem.inverse_problem.InverseProblem` settings. + If the equation is not related to an inverse problem, this should be + set to ``None``. Default is ``None``. + :return: The residual values of the equation. :rtype: LabelTensor """ + @abstractmethod def to(self, device): """ Move all tensor attributes to the specified device. @@ -43,24 +34,3 @@ def to(self, device): :return: The instance moved to the specified device. :rtype: EquationInterface """ - # Iterate over all attributes of the Equation - for key, val in self.__dict__.items(): - - # Move tensors in dictionaries to the specified device - if isinstance(val, dict): - self.__dict__[key] = { - k: v.to(device) if torch.is_tensor(v) else v - for k, v in val.items() - } - - # Move tensors in lists to the specified device - elif isinstance(val, list): - self.__dict__[key] = [ - v.to(device) if torch.is_tensor(v) else v for v in val - ] - - # Move tensor attributes to the specified device - elif torch.is_tensor(val): - self.__dict__[key] = val.to(device) - - return self diff --git a/pina/_src/equation/system_equation.py b/pina/_src/equation/system_equation.py index adaeca444..7d3bdafd4 100644 --- a/pina/_src/equation/system_equation.py +++ b/pina/_src/equation/system_equation.py @@ -1,35 +1,31 @@ """Module for the System of Equation.""" +from typing import Callable import torch -from pina._src.equation.equation_interface import EquationInterface -from pina._src.equation.equation import Equation +from pina._src.equation.base_equation import BaseEquation from pina._src.core.utils import check_consistency +from pina._src.equation.equation import Equation -class SystemEquation(EquationInterface): +class SystemEquation(BaseEquation): """ - Implementation of the System of Equations, to be passed to a - :class:`~pina.condition.condition.Condition` object. + Implementation of the SystemEquation class, representing a system of + mathematical equation to be satisfied by the model outputs. It is useful for + multi-component outputs or coupled problems, where multiple constraints must + be evaluated together. - Unlike the :class:`~pina.equation.equation.Equation` class, which represents - a single equation, the :class:`SystemEquation` class allows multiple - equations to be grouped together into a system. This is particularly useful - when dealing with multi-component outputs or coupled physical models, where - the residual must be computed collectively across several constraints. + It can be passed to a :class:`~pina.condition.condition.Condition` object to + define the conditions under which the model is trained. - Each equation in the system must be either: - - An instance of :class:`~pina.equation.equation.Equation`; - - A callable function. + Each equation in the system must be either an instance of + :class:`~pina.equation.equation.Equation`, or a callable function. - The residuals from each equation are computed independently and then - aggregated using an optional reduction strategy (e.g., ``mean``, ``sum``). - The resulting residual is returned as a single :class:`~pina.LabelTensor`. + Residuals are computed independently for each equation and then aggregated + using an optional reduction (e.g., ``mean``, ``sum``). The final result is + returned as a single :class:`~pina.LabelTensor`. :Example: - >>> from pina.equation import SystemEquation, FixedValue, FixedGradient - >>> from pina import LabelTensor - >>> import torch >>> pts = LabelTensor(torch.rand(10, 2), labels=["x", "y"]) >>> pts.requires_grad = True >>> output_ = torch.pow(pts, 2) @@ -37,40 +33,44 @@ class SystemEquation(EquationInterface): >>> system_equation = SystemEquation( ... [ ... FixedValue(value=1.0, components=["u"]), - ... FixedGradient(value=0.0, components=["v"],d=["y"]), + ... FixedGradient(value=0.0, components=["v"], d=["y"]), ... ], ... reduction="mean", ... ) >>> residual = system_equation.residual(pts, output_) - """ def __init__(self, list_equation, reduction=None): """ Initialization of the :class:`SystemEquation` class. - :param list_equation: A list containing either callable functions or - instances of :class:`~pina.equation.equation.Equation`, used to - compute the residuals of mathematical equations. + :param list_equation: The list of equations used for the computation of + the residuals. Each element of the list can be either a callable + function or a :class:`~pina.equation.equation.Equation` instance. :type list_equation: list[Callable] | list[Equation] - :param str reduction: The reduction method to aggregate the residuals of - each equation. Available options are: ``None``, ``mean``, ``sum``, - ``callable``. - If ``None``, no reduction is applied. If ``mean``, the output sum is - divided by the number of elements in the output. If ``sum``, the - output is summed. ``callable`` is a user-defined callable function - to perform reduction, no checks guaranteed. Default is ``None``. - :raises NotImplementedError: If the reduction is not implemented. + :param reduction: The method used to combine the residuals from each + equation. Available options are: ``None``, ``"mean"``, ``"sum"``, or + a custom callable. If ``None``, no reduction is applied. If + ``"mean"``, the residuals are averaged. If ``"sum"``, the residuals + are summed. If a callable is provided, it is used as a custom + reduction (no validation is performed). + :raises ValueError: If the list of equations is not a list. + :raises ValueError: If any element of the list of equations is not a + callable function or a :class:`~pina.equation.equation.Equation` + instance. + :raises ValueError: If an invalid reduction method is used. """ + # Check consistency check_consistency([list_equation], list) + check_consistency(list_equation, (Callable, Equation)) - # equations definition + # Convert all callable functions to Equation instances, if necessary self.equations = [ equation if isinstance(equation, Equation) else Equation(equation) for equation in list_equation ] - # possible reduction + # Validate and set the reduction method if reduction == "mean": self.reduction = torch.mean elif reduction == "sum": @@ -78,26 +78,24 @@ def __init__(self, list_equation, reduction=None): elif (reduction is None) or callable(reduction): self.reduction = reduction else: - raise NotImplementedError( - "Only mean and sum reductions are currenly supported." + raise ValueError( + "Invalid reduction method. Available options include: None, " + "'mean', 'sum', or a custom callable." ) def residual(self, input_, output_, params_=None): """ - Compute the residual for each equation in the system of equations and - aggregate it according to the ``reduction`` specified in the - ``__init__`` method. + Evaluate each equation residual from the system of equations at the + given inputs and aggregate it according to the specified ``reduction``. - :param LabelTensor input_: Input points where each equation of the - system is evaluated. - :param LabelTensor output_: Output tensor, eventually produced by a + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced by a :class:`torch.nn.Module` instance. - :param dict params_: Dictionary of unknown parameters, associated with a - :class:`~pina.problem.inverse_problem.InverseProblem` instance. - If the equation is not related to a - :class:`~pina.problem.inverse_problem.InverseProblem` instance, the - parameters must be initialized to ``None``. Default is ``None``. - + :param dict params_: An optional dictionary of unknown parameters, used + in :class:`~pina.problem.inverse_problem.InverseProblem` settings. + If the equation is not related to an inverse problem, this should be + set to ``None``. Default is ``None``. :return: The aggregated residuals of the system of equations. :rtype: LabelTensor """ diff --git a/pina/equation/__init__.py b/pina/equation/__init__.py index 551099af6..a3300d786 100644 --- a/pina/equation/__init__.py +++ b/pina/equation/__init__.py @@ -1,4 +1,5 @@ -"""Mathematical equations and physical laws. +""" +Mathematical equations and physical laws. This module provides a framework for defining differential equations, boundary conditions, and complex systems of equations. It includes @@ -7,8 +8,10 @@ """ __all__ = [ - "SystemEquation", + "EquationInterface", + "BaseEquation", "Equation", + "SystemEquation", "FixedValue", "FixedGradient", "FixedFlux", @@ -22,7 +25,10 @@ "AcousticWave", ] +from pina._src.equation.equation_interface import EquationInterface +from pina._src.equation.base_equation import BaseEquation from pina._src.equation.equation import Equation +from pina._src.equation.system_equation import SystemEquation from pina._src.equation.equation_factory import ( FixedFlux, FixedGradient, @@ -36,4 +42,3 @@ Poisson, AcousticWave, ) -from pina._src.equation.system_equation import SystemEquation diff --git a/tests/test_equation/test_equation.py b/tests/test_equation/test_equation.py index 096b2d5e7..0569d2f49 100644 --- a/tests/test_equation/test_equation.py +++ b/tests/test_equation/test_equation.py @@ -1,10 +1,11 @@ -from pina.equation import Equation -from pina.operator import grad, laplacian -from pina import LabelTensor import torch import pytest +from pina.operator import grad, laplacian +from pina.equation import Equation +from pina import LabelTensor +# Define equations for testing def eq1(input_, output_): u_grad = grad(output_, input_) u1_xx = grad(u_grad, input_, components=["du1dx"], d=["x"]) @@ -24,26 +25,38 @@ def foo(): pass -def test_constructor(): - Equation(eq1) - Equation(eq2) +@pytest.mark.parametrize("equation", [eq1, eq2]) +def test_constructor(equation): + Equation(equation) + + # Should fail if the equation is not a callable function with pytest.raises(ValueError): Equation([1, 2, 4]) + + # Should fail if the equation is not a callable function with pytest.raises(ValueError): Equation(foo()) -def test_residual(): - eq_1 = Equation(eq1) - eq_2 = Equation(eq2) +@pytest.mark.parametrize("equation, last_dim", [(eq1, 2), (eq2, 1)]) +def test_residual(equation, last_dim): - pts = LabelTensor(torch.rand(10, 2), labels=["x", "y"]) - pts.requires_grad = True - u = torch.pow(pts, 2) - u.labels = ["u1", "u2"] + # Define the equation + eq = Equation(equation) - eq_1_res = eq_1.residual(pts, u) - eq_2_res = eq_2.residual(pts, u) + # Manage number of points and variables + n_pts = 10 + input_vars = ["x", "y"] + output_vars = ["u1", "u2"] + + # Define the input and output tensors + pts = LabelTensor( + torch.rand(n_pts, len(input_vars), requires_grad=True), + labels=input_vars, + ) + u = torch.pow(pts, 2) + u.labels = output_vars - assert eq_1_res.shape == torch.Size([10, 2]) - assert eq_2_res.shape == torch.Size([10, 1]) + # Compute the residuals and check the shape + eq_res = eq.residual(pts, u) + assert eq_res.shape == torch.Size([n_pts, last_dim]) diff --git a/tests/test_equation/test_system_equation.py b/tests/test_equation/test_system_equation.py index bf6268148..294c30d56 100644 --- a/tests/test_equation/test_system_equation.py +++ b/tests/test_equation/test_system_equation.py @@ -5,6 +5,7 @@ import pytest +# Define equations for testing def eq1(input_, output_): u_grad = grad(output_, input_) u1_xx = grad(u_grad, input_, components=["du1dx"], d=["x"]) @@ -20,82 +21,63 @@ def eq2(input_, output_): return delta_u - force_term -def foo(): - pass +def reduction_fn(residuals, dim): + return torch.sum(residuals, dim=dim) / residuals.shape[dim] -@pytest.mark.parametrize("reduction", [None, "mean", "sum"]) -def test_constructor(reduction): +# Test cases for the SystemEquation class +eq_list1 = [eq1, eq2] +eq_list2 = [FixedValue(value=0.0), FixedGradient(value=0.0, components=["u2"])] +eq_list3 = [FixedValue(value=0.0, components=["u1"]), eq1] - # Constructor with callable functions - SystemEquation([eq1, eq2], reduction=reduction) - # Constructor with Equation instances - SystemEquation( - [ - FixedValue(value=0.0, components=["u1"]), - FixedGradient(value=0.0, components=["u2"]), - ], - reduction=reduction, - ) +@pytest.mark.parametrize("eq_list", [eq_list1, eq_list2, eq_list3]) +@pytest.mark.parametrize("reduction", [None, "mean", "sum", reduction_fn]) +def test_constructor(eq_list, reduction): - # Constructor with mixed types - SystemEquation( - [ - FixedValue(value=0.0, components=["u1"]), - eq1, - ], - reduction=reduction, - ) + SystemEquation(list_equation=eq_list, reduction=reduction) - # Non-standard reduction not implemented - with pytest.raises(NotImplementedError): - SystemEquation([eq1, eq2], reduction="foo") + # Should fail if the list of equations is not a list + with pytest.raises(ValueError): + SystemEquation(list_equation=eq1, reduction=reduction) - # Invalid input type + # Should fail if any element of the list is neither callable nor Equation with pytest.raises(ValueError): - SystemEquation(foo) + SystemEquation(list_equation=[eq1, "equation"], reduction=reduction) + # Should fail if the reduction is not available + with pytest.raises(ValueError): + SystemEquation(list_equation=[eq1, eq2], reduction="foo") -@pytest.mark.parametrize("reduction", [None, "mean", "sum"]) -def test_residual(reduction): - # Generate random points and output - pts = LabelTensor(torch.rand(10, 2), labels=["x", "y"]) - pts.requires_grad = True - u = torch.pow(pts, 2) - u.labels = ["u1", "u2"] +@pytest.mark.parametrize("reduction", [None, "mean", "sum", reduction_fn]) +@pytest.mark.parametrize( + "eq_list, last_dim", + [(eq_list1, 3), (eq_list2, 4), (eq_list3, 3)], +) +def test_residual(eq_list, last_dim, reduction): - # System with callable functions - system_eq = SystemEquation([eq1, eq2], reduction=reduction) - res = system_eq.residual(pts, u) + # Define the system of equations + system_eq = SystemEquation(list_equation=eq_list, reduction=reduction) - # Checks on the shape of the residual - shape = torch.Size([10, 3]) if reduction is None else torch.Size([10]) - assert res.shape == shape + # Manage number of points and variables + n_pts = 10 + input_vars = ["x", "y"] + output_vars = ["u1", "u2"] - # System with Equation instances - system_eq = SystemEquation( - [ - FixedValue(value=0.0, components=["u1"]), - FixedGradient(value=0.0, components=["u2"]), - ], - reduction=reduction, + # Define the input and output tensors + pts = LabelTensor( + torch.rand(n_pts, len(input_vars), requires_grad=True), + labels=input_vars, ) + u = torch.pow(pts, 2) + u.labels = output_vars - # Checks on the shape of the residual - shape = torch.Size([10, 3]) if reduction is None else torch.Size([10]) - assert res.shape == shape - - # System with mixed types - system_eq = SystemEquation( - [ - FixedValue(value=0.0, components=["u1"]), - eq1, - ], - reduction=reduction, + # Compute the residuals and check the shape + res = system_eq.residual(pts, u) + shape = ( + torch.Size([n_pts, last_dim]) + if reduction is None + else torch.Size([n_pts]) ) - - # Checks on the shape of the residual - shape = torch.Size([10, 3]) if reduction is None else torch.Size([10]) assert res.shape == shape From 180f9470bc512ba5af5054339dd3264d8ba8184e Mon Sep 17 00:00:00 2001 From: GiovanniCanali Date: Thu, 16 Apr 2026 15:27:42 +0200 Subject: [PATCH 2/5] update documentation --- pina/_src/condition/data_manager.py | 4 ++-- .../condition/domain_equation_condition.py | 6 +++--- .../_src/condition/input_equation_condition.py | 18 ++++++++---------- .../solver/ensemble_solver/ensemble_pinn.py | 4 ++-- .../physics_informed_solver/causal_pinn.py | 2 +- .../competitive_pinn.py | 2 +- .../physics_informed_solver/gradient_pinn.py | 2 +- .../solver/physics_informed_solver/pinn.py | 2 +- .../physics_informed_solver/pinn_interface.py | 6 +++--- .../self_adaptive_pinn.py | 2 +- 10 files changed, 23 insertions(+), 25 deletions(-) diff --git a/pina/_src/condition/data_manager.py b/pina/_src/condition/data_manager.py index 2d80a5b6f..2f7095fa1 100644 --- a/pina/_src/condition/data_manager.py +++ b/pina/_src/condition/data_manager.py @@ -7,7 +7,7 @@ from torch_geometric.data.batch import Batch from pina import LabelTensor from pina._src.core.graph import Graph, LabelBatch -from ..equation.equation_interface import EquationInterface +from pina._src.equation.base_equation import BaseEquation from .batch_manager import _BatchManager @@ -39,7 +39,7 @@ def __new__(cls, **kwargs): # Does the data contain only tensors/LabelTensors/Equations? is_tensor_only = all( - isinstance(v, (torch.Tensor, LabelTensor, EquationInterface)) + isinstance(v, (torch.Tensor, LabelTensor, BaseEquation)) for v in kwargs.values() ) # Choose the appropriate subclass, GraphDataManager or TensorDataManager diff --git a/pina/_src/condition/domain_equation_condition.py b/pina/_src/condition/domain_equation_condition.py index 08095bbcd..42b448ce6 100644 --- a/pina/_src/condition/domain_equation_condition.py +++ b/pina/_src/condition/domain_equation_condition.py @@ -2,7 +2,7 @@ from pina._src.condition.condition_base import ConditionBase from pina._src.domain.domain_interface import DomainInterface -from pina._src.equation.equation_interface import EquationInterface +from pina._src.equation.base_equation import BaseEquation class DomainEquationCondition(ConditionBase): @@ -32,7 +32,7 @@ class DomainEquationCondition(ConditionBase): __fields__ = ["domain", "equation"] _avail_domain_cls = (DomainInterface, str) - _avail_equation_cls = EquationInterface + _avail_equation_cls = BaseEquation def __new__(cls, domain, equation): """ @@ -52,7 +52,7 @@ def __new__(cls, domain, equation): if not isinstance(equation, cls._avail_equation_cls): raise ValueError( - "The equation must be an instance of EquationInterface." + "The equation must be an instance of BaseEquation." ) return super().__new__(cls) diff --git a/pina/_src/condition/input_equation_condition.py b/pina/_src/condition/input_equation_condition.py index 62dac3a30..965501e1a 100644 --- a/pina/_src/condition/input_equation_condition.py +++ b/pina/_src/condition/input_equation_condition.py @@ -3,7 +3,7 @@ from pina._src.condition.condition_base import ConditionBase from pina._src.core.label_tensor import LabelTensor from pina._src.core.graph import Graph -from pina._src.equation.equation_interface import EquationInterface +from pina._src.equation.base_equation import BaseEquation from pina._src.condition.data_manager import _DataManager @@ -32,7 +32,7 @@ class InputEquationCondition(ConditionBase): # Available input data types __fields__ = ["input", "equation"] _avail_input_cls = (LabelTensor, Graph) - _avail_equation_cls = EquationInterface + _avail_equation_cls = BaseEquation def __new__(cls, input, equation): """ @@ -41,7 +41,7 @@ def __new__(cls, input, equation): :param input: The input data for the condition. :type input: LabelTensor | Graph | list[Graph] | tuple[Graph] - :param EquationInterface equation: The equation to be satisfied over the + :param BaseEquation equation: The equation to be satisfied over the specified ``input`` data. :return: The subclass of InputEquationCondition. :rtype: pina.condition.input_equation_condition. @@ -61,7 +61,7 @@ def __new__(cls, input, equation): # Check equation type if not isinstance(equation, cls._avail_equation_cls): raise ValueError( - "The equation must be an instance of EquationInterface." + "The equation must be an instance of BaseEquation." ) return super().__new__(cls) @@ -90,7 +90,7 @@ def equation(self): Return the equation associated with this condition. :return: Equation associated with this condition. - :rtype: EquationInterface + :rtype: BaseEquation """ return self._equation @@ -99,11 +99,9 @@ def equation(self, value): """ Set the equation associated with this condition. - :param EquationInterface value: The equation to associate with this + :param BaseEquation value: The equation to associate with this condition """ - if not isinstance(value, EquationInterface): - raise TypeError( - "The equation must be an instance of EquationInterface." - ) + if not isinstance(value, BaseEquation): + raise TypeError("The equation must be an instance of BaseEquation.") self._equation = value diff --git a/pina/_src/solver/ensemble_solver/ensemble_pinn.py b/pina/_src/solver/ensemble_solver/ensemble_pinn.py index 6d50ddd05..af117d702 100644 --- a/pina/_src/solver/ensemble_solver/ensemble_pinn.py +++ b/pina/_src/solver/ensemble_solver/ensemble_pinn.py @@ -145,7 +145,7 @@ def loss_phys(self, samples, equation): model. This method should not be overridden, if not intentionally. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ @@ -161,7 +161,7 @@ def _residual_loss(self, samples, equation): method. :param LabelTensor samples: The samples to evaluate the loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The residual loss. :rtype: torch.Tensor """ diff --git a/pina/_src/solver/physics_informed_solver/causal_pinn.py b/pina/_src/solver/physics_informed_solver/causal_pinn.py index 0539af339..cfcbbea20 100644 --- a/pina/_src/solver/physics_informed_solver/causal_pinn.py +++ b/pina/_src/solver/physics_informed_solver/causal_pinn.py @@ -120,7 +120,7 @@ def loss_phys(self, samples, equation): provided samples and equation. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ diff --git a/pina/_src/solver/physics_informed_solver/competitive_pinn.py b/pina/_src/solver/physics_informed_solver/competitive_pinn.py index cd80d5b2d..42096fa64 100644 --- a/pina/_src/solver/physics_informed_solver/competitive_pinn.py +++ b/pina/_src/solver/physics_informed_solver/competitive_pinn.py @@ -146,7 +146,7 @@ def loss_phys(self, samples, equation): provided samples and equation. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ diff --git a/pina/_src/solver/physics_informed_solver/gradient_pinn.py b/pina/_src/solver/physics_informed_solver/gradient_pinn.py index be31d51e8..4ee2b3089 100644 --- a/pina/_src/solver/physics_informed_solver/gradient_pinn.py +++ b/pina/_src/solver/physics_informed_solver/gradient_pinn.py @@ -110,7 +110,7 @@ def loss_phys(self, samples, equation): provided samples and equation. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ diff --git a/pina/_src/solver/physics_informed_solver/pinn.py b/pina/_src/solver/physics_informed_solver/pinn.py index dc6243b50..59b61214e 100644 --- a/pina/_src/solver/physics_informed_solver/pinn.py +++ b/pina/_src/solver/physics_informed_solver/pinn.py @@ -105,7 +105,7 @@ def loss_phys(self, samples, equation): provided samples and equation. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ diff --git a/pina/_src/solver/physics_informed_solver/pinn_interface.py b/pina/_src/solver/physics_informed_solver/pinn_interface.py index b435cb77c..5e1181bc1 100644 --- a/pina/_src/solver/physics_informed_solver/pinn_interface.py +++ b/pina/_src/solver/physics_informed_solver/pinn_interface.py @@ -176,7 +176,7 @@ def loss_phys(self, samples, equation): subclasses. It distinguishes different types of PINN solvers. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ @@ -186,7 +186,7 @@ def compute_residual(self, samples, equation): Compute the residuals of the equation. :param LabelTensor samples: The samples to evaluate the loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The residual of the solution of the model. :rtype: LabelTensor """ @@ -204,7 +204,7 @@ def _residual_loss(self, samples, equation): :param LabelTensor samples: The samples to evaluate the loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The residual loss. :rtype: torch.Tensor """ diff --git a/pina/_src/solver/physics_informed_solver/self_adaptive_pinn.py b/pina/_src/solver/physics_informed_solver/self_adaptive_pinn.py index 03ab795c2..983eb2966 100644 --- a/pina/_src/solver/physics_informed_solver/self_adaptive_pinn.py +++ b/pina/_src/solver/physics_informed_solver/self_adaptive_pinn.py @@ -258,7 +258,7 @@ def loss_phys(self, samples, equation): provided samples and equation. :param LabelTensor samples: The samples to evaluate the physics loss. - :param EquationInterface equation: The governing equation. + :param BaseEquation equation: The governing equation. :return: The computed physics loss. :rtype: LabelTensor """ From e609636d14f5eb9992373e431cfc5de3cc232cf7 Mon Sep 17 00:00:00 2001 From: GiovanniCanali Date: Thu, 16 Apr 2026 16:15:00 +0200 Subject: [PATCH 3/5] move specialized equations to zoo --- docs/source/_rst/_code.rst | 14 + .../source/_rst/equation/equation_factory.rst | 24 -- .../equation/zoo/acoustic_wave_equation.rst | 7 + .../_rst/equation/zoo/advection_equation.rst | 7 + .../_rst/equation/zoo/allen_cahn_equation.rst | 7 + .../zoo/diffusion_reaction_equation.rst | 7 + .../_rst/equation/zoo/helmholtz_equation.rst | 9 + .../_rst/equation/zoo/poisson_equation.rst | 9 + pina/_src/equation/equation_factory.py | 333 ------------------ pina/_src/equation/zoo/__init__.py | 0 .../equation/zoo/acoustic_wave_equation.py | 62 ++++ pina/_src/equation/zoo/advection_equation.py | 94 +++++ pina/_src/equation/zoo/allen_cahn_equation.py | 58 +++ .../zoo/diffusion_reaction_equation.py | 61 ++++ pina/_src/equation/zoo/helmholtz_equation.py | 47 +++ pina/_src/equation/zoo/poisson_equation.py | 42 +++ .../_src/problem/zoo/acoustic_wave_problem.py | 9 +- pina/_src/problem/zoo/advection_problem.py | 6 +- pina/_src/problem/zoo/allen_cahn_problem.py | 5 +- .../problem/zoo/diffusion_reaction_problem.py | 7 +- pina/_src/problem/zoo/helmholtz_problem.py | 6 +- pina/_src/problem/zoo/poisson_problem.py | 7 +- pina/equation/__init__.py | 12 - pina/equation/zoo.py | 19 + tests/test_equation/test_equation_factory.py | 149 +------- .../test_acoustic_wave_equation.py | 29 ++ .../test_advection_equation.py | 38 ++ .../test_allen_cahn_equation.py | 34 ++ .../test_diffusion_reaction_equation.py | 36 ++ .../test_helmholtz_equation.py | 32 ++ .../test_poisson_equation.py | 27 ++ 31 files changed, 662 insertions(+), 535 deletions(-) create mode 100644 docs/source/_rst/equation/zoo/acoustic_wave_equation.rst create mode 100644 docs/source/_rst/equation/zoo/advection_equation.rst create mode 100644 docs/source/_rst/equation/zoo/allen_cahn_equation.rst create mode 100644 docs/source/_rst/equation/zoo/diffusion_reaction_equation.rst create mode 100644 docs/source/_rst/equation/zoo/helmholtz_equation.rst create mode 100644 docs/source/_rst/equation/zoo/poisson_equation.rst create mode 100644 pina/_src/equation/zoo/__init__.py create mode 100644 pina/_src/equation/zoo/acoustic_wave_equation.py create mode 100644 pina/_src/equation/zoo/advection_equation.py create mode 100644 pina/_src/equation/zoo/allen_cahn_equation.py create mode 100644 pina/_src/equation/zoo/diffusion_reaction_equation.py create mode 100644 pina/_src/equation/zoo/helmholtz_equation.py create mode 100644 pina/_src/equation/zoo/poisson_equation.py create mode 100644 pina/equation/zoo.py create mode 100644 tests/test_equation_zoo/test_acoustic_wave_equation.py create mode 100644 tests/test_equation_zoo/test_advection_equation.py create mode 100644 tests/test_equation_zoo/test_allen_cahn_equation.py create mode 100644 tests/test_equation_zoo/test_diffusion_reaction_equation.py create mode 100644 tests/test_equation_zoo/test_helmholtz_equation.py create mode 100644 tests/test_equation_zoo/test_poisson_equation.py diff --git a/docs/source/_rst/_code.rst b/docs/source/_rst/_code.rst index 6238e38b2..6c4da42b3 100644 --- a/docs/source/_rst/_code.rst +++ b/docs/source/_rst/_code.rst @@ -203,6 +203,20 @@ Equations and Differential Operators Differential Operators +Equations Zoo +--------------------------------------- + +.. toctree:: + :titlesonly: + + Acoustic Wave Equation + Advection Equation + Allen-Cahn Equation + Diffusion-Reaction Equation + Helmholtz Equation + Poisson Equation + + Problems -------------- diff --git a/docs/source/_rst/equation/equation_factory.rst b/docs/source/_rst/equation/equation_factory.rst index 010813319..c5024b308 100644 --- a/docs/source/_rst/equation/equation_factory.rst +++ b/docs/source/_rst/equation/equation_factory.rst @@ -21,27 +21,3 @@ Equation Factory .. autoclass:: pina._src.equation.equation_factory.Laplace :members: :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.Advection - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.AllenCahn - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.DiffusionReaction - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.Helmholtz - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.Poisson - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.AcousticWave - :members: - :show-inheritance: \ No newline at end of file diff --git a/docs/source/_rst/equation/zoo/acoustic_wave_equation.rst b/docs/source/_rst/equation/zoo/acoustic_wave_equation.rst new file mode 100644 index 000000000..5bc19d920 --- /dev/null +++ b/docs/source/_rst/equation/zoo/acoustic_wave_equation.rst @@ -0,0 +1,7 @@ +AcousticWaveEquation +===================== +.. currentmodule:: pina.equation.zoo.acoustic_wave_equation + +.. automodule:: pina._src.equation.zoo.acoustic_wave_equation + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/advection_equation.rst b/docs/source/_rst/equation/zoo/advection_equation.rst new file mode 100644 index 000000000..4386b3a3d --- /dev/null +++ b/docs/source/_rst/equation/zoo/advection_equation.rst @@ -0,0 +1,7 @@ +Advection Equation +===================== +.. currentmodule:: pina.equation.zoo.advection_equation + +.. automodule:: pina._src.equation.zoo.advection_equation + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/allen_cahn_equation.rst b/docs/source/_rst/equation/zoo/allen_cahn_equation.rst new file mode 100644 index 000000000..fff220811 --- /dev/null +++ b/docs/source/_rst/equation/zoo/allen_cahn_equation.rst @@ -0,0 +1,7 @@ +Allen Cahn Equation +===================== +.. currentmodule:: pina.equation.zoo.allen_cahn_equation + +.. automodule:: pina._src.equation.zoo.allen_cahn_equation + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/diffusion_reaction_equation.rst b/docs/source/_rst/equation/zoo/diffusion_reaction_equation.rst new file mode 100644 index 000000000..d45143074 --- /dev/null +++ b/docs/source/_rst/equation/zoo/diffusion_reaction_equation.rst @@ -0,0 +1,7 @@ +Diffusion Reaction Equation +============================== +.. currentmodule:: pina.equation.zoo.diffusion_reaction_equation + +.. automodule:: pina._src.equation.zoo.diffusion_reaction_equation + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/helmholtz_equation.rst b/docs/source/_rst/equation/zoo/helmholtz_equation.rst new file mode 100644 index 000000000..7728b60ed --- /dev/null +++ b/docs/source/_rst/equation/zoo/helmholtz_equation.rst @@ -0,0 +1,9 @@ +Helmholtz Equation +===================== +.. currentmodule:: pina.equation.zoo.helmholtz_equation + +.. automodule:: pina._src.equation.zoo.helmholtz_equation + +.. autoclass:: pina._src.equation.zoo.helmholtz_equation.HelmholtzEquation + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/poisson_equation.rst b/docs/source/_rst/equation/zoo/poisson_equation.rst new file mode 100644 index 000000000..f23796450 --- /dev/null +++ b/docs/source/_rst/equation/zoo/poisson_equation.rst @@ -0,0 +1,9 @@ +Poisson Equation +===================== +.. currentmodule:: pina.equation.zoo.poisson_equation + +.. automodule:: pina._src.equation.zoo.poisson_equation + +.. autoclass:: pina._src.equation.zoo.poisson_equation.PoissonEquation + :members: + :show-inheritance: diff --git a/pina/_src/equation/equation_factory.py b/pina/_src/equation/equation_factory.py index acc9fd1f3..f52f108ab 100644 --- a/pina/_src/equation/equation_factory.py +++ b/pina/_src/equation/equation_factory.py @@ -1,10 +1,7 @@ """Module for defining various general equations.""" -from typing import Callable -import torch from pina._src.equation.equation import Equation from pina._src.core.operator import grad, div, laplacian -from pina._src.core.utils import check_consistency class FixedValue(Equation): # pylint: disable=R0903 @@ -176,333 +173,3 @@ def __init__(self, components=None, d=None): Default is ``None``. """ super().__init__(0.0, components=components, d=d) - - -class Advection(Equation): # pylint: disable=R0903 - r""" - Implementation of the N-dimensional advection equation with constant - velocity parameter. The equation is defined as follows: - - .. math:: - - \frac{\partial u}{\partial t} + c \cdot \nabla u = 0 - - Here, :math:`c` is the advection velocity parameter. - """ - - def __init__(self, c): - """ - Initialization of the :class:`Advection` class. - - :param c: The advection velocity. If a scalar is provided, the same - velocity is applied to all spatial dimensions. If a list is - provided, it must contain one value per spatial dimension. - :type c: float | int | List[float] | List[int] - :raises ValueError: If ``c`` is an empty list. - """ - # Check consistency - check_consistency(c, (float, int, list)) - if isinstance(c, list): - all(check_consistency(ci, (float, int)) for ci in c) - if len(c) < 1: - raise ValueError("'c' cannot be an empty list.") - else: - c = [c] - - # Store advection velocity parameter - self.c = torch.tensor(c).unsqueeze(0) - - def equation(input_, output_): - """ - Implementation of the advection equation. - - :param LabelTensor input_: The input data of the problem. - :param LabelTensor output_: The output data of the problem. - :return: The residual of the advection equation. - :rtype: LabelTensor - :raises ValueError: If the ``input_`` labels do not contain the time - variable 't'. - :raises ValueError: If ``c`` is a list and its length is not - consistent with the number of spatial dimensions. - """ - # Store labels - input_lbl = input_.labels - spatial_d = [di for di in input_lbl if di != "t"] - - # Ensure time is passed as input - if "t" not in input_lbl: - raise ValueError( - "The ``input_`` labels must contain the time 't' variable." - ) - - # Ensure consistency of c length - if self.c.shape[-1] != len(input_lbl) - 1 and self.c.shape[-1] > 1: - raise ValueError( - "If 'c' is passed as a list, its length must be equal to " - "the number of spatial dimensions." - ) - - # Repeat c to ensure consistent shape for advection - c = self.c.repeat(output_.shape[0], 1) - if c.shape[1] != (len(input_lbl) - 1): - c = c.repeat(1, len(input_lbl) - 1) - - # Add a dimension to c for the following operations - c = c.unsqueeze(-1) - - # Compute the time derivative and the spatial gradient - time_der = grad(output_, input_, components=None, d="t") - grads = grad(output_=output_, input_=input_, d=spatial_d) - - # Reshape and transpose - tmp = grads.reshape(*output_.shape, len(spatial_d)) - tmp = tmp.transpose(-1, -2) - - # Compute advection term - adv = (tmp * c).sum(dim=tmp.tensor.ndim - 2) - - return time_der + adv - - super().__init__(equation) - - -class AllenCahn(Equation): # pylint: disable=R0903 - r""" - Implementation of the N-dimensional Allen-Cahn equation, defined as follows: - - .. math:: - - \frac{\partial u}{\partial t} - \alpha \Delta u + \beta(u^3 - u) = 0 - - Here, :math:`\alpha` and :math:`\beta` are parameters of the equation. - """ - - def __init__(self, alpha, beta): - """ - Initialization of the :class:`AllenCahn` class. - - :param alpha: The diffusion coefficient. - :type alpha: float | int - :param beta: The reaction coefficient. - :type beta: float | int - """ - check_consistency(alpha, (float, int)) - check_consistency(beta, (float, int)) - self.alpha = alpha - self.beta = beta - - def equation(input_, output_): - """ - Implementation of the Allen-Cahn equation. - - :param LabelTensor input_: The input data of the problem. - :param LabelTensor output_: The output data of the problem. - :return: The residual of the Allen-Cahn equation. - :rtype: LabelTensor - :raises ValueError: If the ``input_`` labels do not contain the time - variable 't'. - """ - # 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 time derivative and the spatial laplacian - u_t = grad(output_, input_, d=["t"]) - u_xx = laplacian( - output_, input_, d=[di for di in input_.labels if di != "t"] - ) - - return u_t - self.alpha * u_xx + self.beta * (output_**3 - output_) - - super().__init__(equation) - - -class DiffusionReaction(Equation): # pylint: disable=R0903 - r""" - Implementation of the N-dimensional Diffusion-Reaction equation, - defined as follows: - - .. math:: - - \frac{\partial u}{\partial t} - \alpha \Delta u - f = 0 - - Here, :math:`\alpha` is a parameter of the equation, while :math:`f` is the - reaction term. - """ - - def __init__(self, alpha, forcing_term): - """ - Initialization of the :class:`DiffusionReaction` class. - - :param alpha: The diffusion coefficient. - :type alpha: float | int - :param Callable forcing_term: The forcing field function, taking as - input the points on which evaluation is required. - """ - check_consistency(alpha, (float, int)) - check_consistency(forcing_term, (Callable)) - self.alpha = alpha - self.forcing_term = forcing_term - - def equation(input_, output_): - """ - Implementation of the Diffusion-Reaction equation. - - :param LabelTensor input_: The input data of the problem. - :param LabelTensor output_: The output data of the problem. - :return: The residual of the Diffusion-Reaction equation. - :rtype: LabelTensor - :raises ValueError: If the ``input_`` labels do not contain the time - variable 't'. - """ - # 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 time derivative and the spatial laplacian - u_t = grad(output_, input_, d=["t"]) - u_xx = laplacian( - output_, input_, d=[di for di in input_.labels if di != "t"] - ) - - return u_t - self.alpha * u_xx - self.forcing_term(input_) - - super().__init__(equation) - - -class Helmholtz(Equation): # pylint: disable=R0903 - r""" - Implementation of the Helmholtz equation, defined as follows: - - .. math:: - - \Delta u + k u - f = 0 - - Here, :math:`k` is the squared wavenumber, while :math:`f` is the - forcing term. - """ - - def __init__(self, k, forcing_term): - """ - Initialization of the :class:`Helmholtz` class. - - :param k: The squared wavenumber. - :type k: float | int - :param Callable forcing_term: The forcing field function, taking as - input the points on which evaluation is required. - """ - check_consistency(k, (int, float)) - check_consistency(forcing_term, (Callable)) - self.k = k - self.forcing_term = forcing_term - - def equation(input_, output_): - """ - Implementation of the Helmholtz equation. - - :param LabelTensor input_: The input data of the problem. - :param LabelTensor output_: The output data of the problem. - :return: The residual of the Helmholtz equation. - :rtype: LabelTensor - """ - lap = laplacian(output_, input_) - return lap + self.k * output_ - self.forcing_term(input_) - - super().__init__(equation) - - -class Poisson(Equation): # pylint: disable=R0903 - r""" - Implementation of the Poisson equation, defined as follows: - - .. math:: - - \Delta u - f = 0 - - Here, :math:`f` is the forcing term. - """ - - def __init__(self, forcing_term): - """ - Initialization of the :class:`Poisson` class. - - :param Callable forcing_term: The forcing field function, taking as - input the points on which evaluation is required. - """ - check_consistency(forcing_term, (Callable)) - self.forcing_term = forcing_term - - def equation(input_, output_): - """ - Implementation of the Poisson equation. - - :param LabelTensor input_: The input data of the problem. - :param LabelTensor output_: The output data of the problem. - :return: The residual of the Poisson equation. - :rtype: LabelTensor - """ - lap = laplacian(output_, input_) - return lap - self.forcing_term(input_) - - super().__init__(equation) - - -class AcousticWave(Equation): # pylint: disable=R0903 - r""" - Implementation of the N-dimensional isotropic acoustic wave equation. - The equation is defined as follows: - - .. math:: - - \frac{\partial^2 u}{\partial t^2} - c^2 \Delta u = 0 - - or alternatively: - - .. math:: - - \Box u = 0 - - Here, :math:`c` is the wave propagation speed, and :math:`\Box` is the - d'Alembert operator. - """ - - def __init__(self, c): - """ - Initialization of the :class:`AcousticWaveEquation` class. - - :param c: The wave propagation speed. - :type c: float | int - """ - check_consistency(c, (float, int)) - self.c = c - - def equation(input_, output_): - """ - Implementation of the acoustic wave equation. - - :param LabelTensor input_: The input data of the problem. - :param LabelTensor output_: The output data of the problem. - :return: The residual of the acoustic wave equation. - :rtype: LabelTensor - :raises ValueError: If the ``input_`` labels do not contain the time - variable 't'. - """ - # 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 time second derivative and the spatial laplacian - u_tt = laplacian(output_, input_, d=["t"]) - u_xx = laplacian( - output_, input_, d=[di for di in input_.labels if di != "t"] - ) - - return u_tt - self.c**2 * u_xx - - super().__init__(equation) diff --git a/pina/_src/equation/zoo/__init__.py b/pina/_src/equation/zoo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pina/_src/equation/zoo/acoustic_wave_equation.py b/pina/_src/equation/zoo/acoustic_wave_equation.py new file mode 100644 index 000000000..45614cb5d --- /dev/null +++ b/pina/_src/equation/zoo/acoustic_wave_equation.py @@ -0,0 +1,62 @@ +"""Module for defining the acoustic wave equation.""" + +from pina._src.equation.equation import Equation +from pina._src.core.operator import laplacian +from pina._src.core.utils import check_consistency + + +class AcousticWaveEquation(Equation): # pylint: disable=R0903 + r""" + Implementation of the N-dimensional isotropic acoustic wave equation. + The equation is defined as follows: + + .. math:: + + \frac{\partial^2 u}{\partial t^2} - c^2 \Delta u = 0 + + or alternatively: + + .. math:: + + \Box u = 0 + + Here, :math:`c` is the wave propagation speed, and :math:`\Box` is the + d'Alembert operator. + """ + + def __init__(self, c): + """ + Initialization of the :class:`AcousticWaveEquation` class. + + :param c: The wave propagation speed. + :type c: float | int + """ + check_consistency(c, (float, int)) + self.c = c + + def equation(input_, output_): + """ + Implementation of the acoustic wave equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the acoustic wave equation. + :rtype: LabelTensor + :raises ValueError: If the ``input_`` labels do not contain the time + variable 't'. + """ + # 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 time second derivative and the spatial laplacian + u_tt = laplacian(output_, input_, d=["t"]) + u_xx = laplacian( + output_, input_, d=[di for di in input_.labels if di != "t"] + ) + + return u_tt - self.c**2 * u_xx + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/advection_equation.py b/pina/_src/equation/zoo/advection_equation.py new file mode 100644 index 000000000..903ee820b --- /dev/null +++ b/pina/_src/equation/zoo/advection_equation.py @@ -0,0 +1,94 @@ +"""Module for defining the advection equation.""" + +import torch +from pina._src.equation.equation import Equation +from pina._src.core.operator import grad +from pina._src.core.utils import check_consistency + + +class AdvectionEquation(Equation): # pylint: disable=R0903 + r""" + Implementation of the N-dimensional advection equation with constant + velocity parameter. The equation is defined as follows: + + .. math:: + + \frac{\partial u}{\partial t} + c \cdot \nabla u = 0 + + Here, :math:`c` is the advection velocity parameter. + """ + + def __init__(self, c): + """ + Initialization of the :class:`AdvectionEquation` class. + + :param c: The advection velocity. If a scalar is provided, the same + velocity is applied to all spatial dimensions. If a list is + provided, it must contain one value per spatial dimension. + :type c: float | int | List[float] | List[int] + :raises ValueError: If ``c`` is an empty list. + """ + # Check consistency + check_consistency(c, (float, int, list)) + if isinstance(c, list): + all(check_consistency(ci, (float, int)) for ci in c) + if len(c) < 1: + raise ValueError("'c' cannot be an empty list.") + else: + c = [c] + + # Store advection velocity parameter + self.c = torch.tensor(c).unsqueeze(0) + + def equation(input_, output_): + """ + Implementation of the advection equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the advection equation. + :rtype: LabelTensor + :raises ValueError: If the ``input_`` labels do not contain the time + variable 't'. + :raises ValueError: If ``c`` is a list and its length is not + consistent with the number of spatial dimensions. + """ + # Store labels + input_lbl = input_.labels + spatial_d = [di for di in input_lbl if di != "t"] + + # Ensure time is passed as input + if "t" not in input_lbl: + raise ValueError( + "The ``input_`` labels must contain the time 't' variable." + ) + + # Ensure consistency of c length + if self.c.shape[-1] != len(input_lbl) - 1 and self.c.shape[-1] > 1: + raise ValueError( + "If 'c' is passed as a list, its length must be equal to " + "the number of spatial dimensions." + ) + + # Repeat c to ensure consistent shape for advection + c = self.c.repeat(output_.shape[0], 1) + if c.shape[1] != (len(input_lbl) - 1): + c = c.repeat(1, len(input_lbl) - 1) + + # Add a dimension to c for the following operations + c = c.unsqueeze(-1) + + # Compute the time derivative and the spatial gradient + time_der = grad(output_, input_, components=None, d="t") + grads = grad(output_=output_, input_=input_, d=spatial_d) + + # Reshape and transpose + tmp = grads.reshape(*output_.shape, len(spatial_d)) + tmp = tmp.transpose(-1, -2) + + # Compute advection term + adv = (tmp * c).sum(dim=tmp.tensor.ndim - 2) + + return time_der + adv + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/allen_cahn_equation.py b/pina/_src/equation/zoo/allen_cahn_equation.py new file mode 100644 index 000000000..81c6c82b1 --- /dev/null +++ b/pina/_src/equation/zoo/allen_cahn_equation.py @@ -0,0 +1,58 @@ +"""Module for defining the Allen-Cahn equation.""" + +from pina._src.equation.equation import Equation +from pina._src.core.operator import grad, laplacian +from pina._src.core.utils import check_consistency + + +class AllenCahnEquation(Equation): # pylint: disable=R0903 + r""" + Implementation of the N-dimensional Allen-Cahn equation, defined as follows: + + .. math:: + + \frac{\partial u}{\partial t} - \alpha \Delta u + \beta(u^3 - u) = 0 + + Here, :math:`\alpha` and :math:`\beta` are parameters of the equation. + """ + + def __init__(self, alpha, beta): + """ + Initialization of the :class:`AllenCahnEquation` class. + + :param alpha: The diffusion coefficient. + :type alpha: float | int + :param beta: The reaction coefficient. + :type beta: float | int + """ + check_consistency(alpha, (float, int)) + check_consistency(beta, (float, int)) + self.alpha = alpha + self.beta = beta + + def equation(input_, output_): + """ + Implementation of the Allen-Cahn equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the Allen-Cahn equation. + :rtype: LabelTensor + :raises ValueError: If the ``input_`` labels do not contain the time + variable 't'. + """ + # 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 time derivative and the spatial laplacian + u_t = grad(output_, input_, d=["t"]) + u_xx = laplacian( + output_, input_, d=[di for di in input_.labels if di != "t"] + ) + + return u_t - self.alpha * u_xx + self.beta * (output_**3 - output_) + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/diffusion_reaction_equation.py b/pina/_src/equation/zoo/diffusion_reaction_equation.py new file mode 100644 index 000000000..76768088a --- /dev/null +++ b/pina/_src/equation/zoo/diffusion_reaction_equation.py @@ -0,0 +1,61 @@ +"""Module for defining the Diffusion-Reaction equation.""" + +from typing import Callable +from pina._src.equation.equation import Equation +from pina._src.core.operator import grad, laplacian +from pina._src.core.utils import check_consistency + + +class DiffusionReactionEquation(Equation): # pylint: disable=R0903 + r""" + Implementation of the N-dimensional Diffusion-Reaction equation, + defined as follows: + + .. math:: + + \frac{\partial u}{\partial t} - \alpha \Delta u - f = 0 + + Here, :math:`\alpha` is a parameter of the equation, while :math:`f` is the + reaction term. + """ + + def __init__(self, alpha, forcing_term): + """ + Initialization of the :class:`DiffusionReactionEquation` class. + + :param alpha: The diffusion coefficient. + :type alpha: float | int + :param Callable forcing_term: The forcing field function, taking as + input the points on which evaluation is required. + """ + check_consistency(alpha, (float, int)) + check_consistency(forcing_term, (Callable)) + self.alpha = alpha + self.forcing_term = forcing_term + + def equation(input_, output_): + """ + Implementation of the Diffusion-Reaction equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the Diffusion-Reaction equation. + :rtype: LabelTensor + :raises ValueError: If the ``input_`` labels do not contain the time + variable 't'. + """ + # 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 time derivative and the spatial laplacian + u_t = grad(output_, input_, d=["t"]) + u_xx = laplacian( + output_, input_, d=[di for di in input_.labels if di != "t"] + ) + + return u_t - self.alpha * u_xx - self.forcing_term(input_) + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/helmholtz_equation.py b/pina/_src/equation/zoo/helmholtz_equation.py new file mode 100644 index 000000000..3b628728a --- /dev/null +++ b/pina/_src/equation/zoo/helmholtz_equation.py @@ -0,0 +1,47 @@ +"""Module for defining the Helmholtz equation.""" + +from typing import Callable +from pina._src.equation.equation import Equation +from pina._src.core.operator import laplacian +from pina._src.core.utils import check_consistency + + +class HelmholtzEquation(Equation): # pylint: disable=R0903 + r""" + Implementation of the Helmholtz equation, defined as follows: + + .. math:: + + \Delta u + k u - f = 0 + + Here, :math:`k` is the squared wavenumber, while :math:`f` is the + forcing term. + """ + + def __init__(self, k, forcing_term): + """ + Initialization of the :class:`HelmholtzEquation` class. + + :param k: The squared wavenumber. + :type k: float | int + :param Callable forcing_term: The forcing field function, taking as + input the points on which evaluation is required. + """ + check_consistency(k, (int, float)) + check_consistency(forcing_term, (Callable)) + self.k = k + self.forcing_term = forcing_term + + def equation(input_, output_): + """ + Implementation of the Helmholtz equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the Helmholtz equation. + :rtype: LabelTensor + """ + lap = laplacian(output_, input_) + return lap + self.k * output_ - self.forcing_term(input_) + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/poisson_equation.py b/pina/_src/equation/zoo/poisson_equation.py new file mode 100644 index 000000000..15713539f --- /dev/null +++ b/pina/_src/equation/zoo/poisson_equation.py @@ -0,0 +1,42 @@ +"""Module for defining the Poisson equation.""" + +from typing import Callable +from pina._src.equation.equation import Equation +from pina._src.core.operator import laplacian +from pina._src.core.utils import check_consistency + + +class PoissonEquation(Equation): # pylint: disable=R0903 + r""" + Implementation of the Poisson equation, defined as follows: + + .. math:: + + \Delta u - f = 0 + + Here, :math:`f` is the forcing term. + """ + + def __init__(self, forcing_term): + """ + Initialization of the :class:`PoissonEquation` class. + + :param Callable forcing_term: The forcing field function, taking as + input the points on which evaluation is required. + """ + check_consistency(forcing_term, (Callable)) + self.forcing_term = forcing_term + + def equation(input_, output_): + """ + Implementation of the Poisson equation. + + :param LabelTensor input_: The input data of the problem. + :param LabelTensor output_: The output data of the problem. + :return: The residual of the Poisson equation. + :rtype: LabelTensor + """ + lap = laplacian(output_, input_) + return lap - self.forcing_term(input_) + + super().__init__(equation) diff --git a/pina/_src/problem/zoo/acoustic_wave_problem.py b/pina/_src/problem/zoo/acoustic_wave_problem.py index e4e241e8a..5445d2455 100644 --- a/pina/_src/problem/zoo/acoustic_wave_problem.py +++ b/pina/_src/problem/zoo/acoustic_wave_problem.py @@ -8,11 +8,8 @@ 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.equation_factory import ( - FixedValue, - FixedGradient, - AcousticWave, -) +from pina._src.equation.equation_factory import FixedValue, FixedGradient +from pina._src.equation.zoo.acoustic_wave_equation import AcousticWaveEquation def initial_condition(input_, output_): @@ -78,7 +75,7 @@ def __init__(self, c=2.0): self.c = c self.conditions["D"] = Condition( - domain="D", equation=AcousticWave(self.c) + domain="D", equation=AcousticWaveEquation(self.c) ) def solution(self, pts): diff --git a/pina/_src/problem/zoo/advection_problem.py b/pina/_src/problem/zoo/advection_problem.py index c1cfa85f6..b46eae737 100644 --- a/pina/_src/problem/zoo/advection_problem.py +++ b/pina/_src/problem/zoo/advection_problem.py @@ -4,7 +4,7 @@ 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.equation.equation_factory import Advection +from pina._src.equation.zoo.advection_equation import AdvectionEquation from pina._src.condition.condition import Condition from pina._src.core.utils import check_consistency from pina._src.equation.equation import Equation @@ -64,7 +64,9 @@ def __init__(self, c=1.0): check_consistency(c, (float, int)) self.c = c - self.conditions["D"] = Condition(domain="D", equation=Advection(self.c)) + self.conditions["D"] = Condition( + domain="D", equation=AdvectionEquation(self.c) + ) def solution(self, pts): """ diff --git a/pina/_src/problem/zoo/allen_cahn_problem.py b/pina/_src/problem/zoo/allen_cahn_problem.py index b46713d9d..5d80d8265 100644 --- a/pina/_src/problem/zoo/allen_cahn_problem.py +++ b/pina/_src/problem/zoo/allen_cahn_problem.py @@ -1,12 +1,11 @@ """Formulation of the Allen Cahn problem.""" import torch - from pina._src.condition.condition import Condition from pina._src.problem.spatial_problem import SpatialProblem from pina._src.problem.time_dependent_problem import TimeDependentProblem from pina._src.equation.equation import Equation -from pina._src.equation.equation_factory import AllenCahn +from pina._src.equation.zoo.allen_cahn_equation import AllenCahnEquation from pina._src.core.utils import check_consistency from pina._src.domain.cartesian_domain import CartesianDomain @@ -76,5 +75,5 @@ def __init__(self, alpha=1e-4, beta=5): self.conditions["D"] = Condition( domain="D", - equation=AllenCahn(alpha=self.alpha, beta=self.beta), + equation=AllenCahnEquation(alpha=self.alpha, beta=self.beta), ) diff --git a/pina/_src/problem/zoo/diffusion_reaction_problem.py b/pina/_src/problem/zoo/diffusion_reaction_problem.py index 5f05efedc..9b8870189 100644 --- a/pina/_src/problem/zoo/diffusion_reaction_problem.py +++ b/pina/_src/problem/zoo/diffusion_reaction_problem.py @@ -3,11 +3,14 @@ import torch from pina._src.condition.condition import Condition from pina._src.equation.equation import Equation -from pina._src.equation.equation_factory import FixedValue, DiffusionReaction +from pina._src.equation.equation_factory import FixedValue from pina._src.problem.spatial_problem import SpatialProblem from pina._src.problem.time_dependent_problem import TimeDependentProblem from pina._src.core.utils import check_consistency from pina._src.domain.cartesian_domain import CartesianDomain +from pina._src.equation.zoo.diffusion_reaction_equation import ( + DiffusionReactionEquation, +) def initial_condition(input_, output_): @@ -91,7 +94,7 @@ def forcing_term(input_): self.conditions["D"] = Condition( domain="D", - equation=DiffusionReaction(self.alpha, forcing_term), + equation=DiffusionReactionEquation(self.alpha, forcing_term), ) def solution(self, pts): diff --git a/pina/_src/problem/zoo/helmholtz_problem.py b/pina/_src/problem/zoo/helmholtz_problem.py index 9e07d0c59..6e59a24c9 100644 --- a/pina/_src/problem/zoo/helmholtz_problem.py +++ b/pina/_src/problem/zoo/helmholtz_problem.py @@ -1,9 +1,9 @@ """Formulation of the Helmholtz problem.""" import torch - from pina._src.condition.condition import Condition -from pina._src.equation.equation_factory import FixedValue, Helmholtz +from pina._src.equation.equation_factory import FixedValue +from pina._src.equation.zoo.helmholtz_equation import HelmholtzEquation from pina._src.problem.spatial_problem import SpatialProblem from pina._src.core.utils import check_consistency from pina._src.domain.cartesian_domain import CartesianDomain @@ -68,7 +68,7 @@ def forcing_term(input_): self.conditions["D"] = Condition( domain="D", - equation=Helmholtz(self.k, forcing_term), + equation=HelmholtzEquation(self.k, forcing_term), ) def solution(self, pts): diff --git a/pina/_src/problem/zoo/poisson_problem.py b/pina/_src/problem/zoo/poisson_problem.py index 6abe69967..b92fbce87 100644 --- a/pina/_src/problem/zoo/poisson_problem.py +++ b/pina/_src/problem/zoo/poisson_problem.py @@ -2,10 +2,11 @@ import torch -from pina._src.equation.equation_factory import FixedValue, Poisson +from pina._src.equation.equation_factory import FixedValue 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.equation.zoo.poisson_equation import PoissonEquation def forcing_term(input_): @@ -43,7 +44,9 @@ class Poisson2DSquareProblem(SpatialProblem): conditions = { "boundary": Condition(domain="boundary", equation=FixedValue(0.0)), - "D": Condition(domain="D", equation=Poisson(forcing_term=forcing_term)), + "D": Condition( + domain="D", equation=PoissonEquation(forcing_term=forcing_term) + ), } def solution(self, pts): diff --git a/pina/equation/__init__.py b/pina/equation/__init__.py index a3300d786..91b803c54 100644 --- a/pina/equation/__init__.py +++ b/pina/equation/__init__.py @@ -17,12 +17,6 @@ "FixedFlux", "FixedLaplacian", "Laplace", - "Advection", - "AllenCahn", - "DiffusionReaction", - "Helmholtz", - "Poisson", - "AcousticWave", ] from pina._src.equation.equation_interface import EquationInterface @@ -35,10 +29,4 @@ FixedLaplacian, FixedValue, Laplace, - Advection, - AllenCahn, - DiffusionReaction, - Helmholtz, - Poisson, - AcousticWave, ) diff --git a/pina/equation/zoo.py b/pina/equation/zoo.py new file mode 100644 index 000000000..daecc370d --- /dev/null +++ b/pina/equation/zoo.py @@ -0,0 +1,19 @@ +"""Module for implemented equations.""" + +__all__ = [ + "AdvectionEquation", + "AllenCahnEquation", + "DiffusionReactionEquation", + "HelmholtzEquation", + "PoissonEquation", + "AcousticWaveEquation", +] + +from pina._src.equation.zoo.acoustic_wave_equation import AcousticWaveEquation +from pina._src.equation.zoo.advection_equation import AdvectionEquation +from pina._src.equation.zoo.allen_cahn_equation import AllenCahnEquation +from pina._src.equation.zoo.diffusion_reaction_equation import ( + DiffusionReactionEquation, +) +from pina._src.equation.zoo.helmholtz_equation import HelmholtzEquation +from pina._src.equation.zoo.poisson_equation import PoissonEquation diff --git a/tests/test_equation/test_equation_factory.py b/tests/test_equation/test_equation_factory.py index 578d9ba30..3a931bd5f 100644 --- a/tests/test_equation/test_equation_factory.py +++ b/tests/test_equation/test_equation_factory.py @@ -1,15 +1,4 @@ -from pina.equation import ( - FixedValue, - FixedGradient, - FixedFlux, - FixedLaplacian, - Advection, - AllenCahn, - DiffusionReaction, - Helmholtz, - Poisson, - AcousticWave, -) +from pina.equation import FixedValue, FixedGradient, FixedFlux, FixedLaplacian from pina import LabelTensor import torch import pytest @@ -79,139 +68,3 @@ def test_fixed_laplacian(value, components, d): residual = equation.residual(pts, u) len_c = len(components) if components is not None else u.shape[1] assert residual.shape == (pts.shape[0], len_c) - - -@pytest.mark.parametrize("c", [1.0, 10, [1, 2.5]]) -def test_advection_equation(c): - - # Constructor - equation = Advection(c) - - # Should fail if c is an empty list - with pytest.raises(ValueError): - Advection([]) - - # Should fail if c is not a float, int, or list - with pytest.raises(ValueError): - Advection("invalid") - - # 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 c is a list and its length != spatial dimension - with pytest.raises(ValueError): - equation = Advection([1, 2, 3]) - residual = equation.residual(pts, u) - - -@pytest.mark.parametrize("alpha", [1.0, 10, -7.5]) -@pytest.mark.parametrize("beta", [1.0, 10, -7.5]) -def test_allen_cahn_equation(alpha, beta): - - # Constructor - equation = AllenCahn(alpha=alpha, beta=beta) - - # Should fail if alpha is not a float or int - with pytest.raises(ValueError): - AllenCahn(alpha="invalid", beta=beta) - - # Should fail if beta is not a float or int - with pytest.raises(ValueError): - AllenCahn(alpha=alpha, beta="invalid") - - # 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) - - -@pytest.mark.parametrize("alpha", [1.0, 10, -7.5]) -@pytest.mark.parametrize( - "forcing_term", [lambda x: torch.sin(x), lambda x: torch.exp(x)] -) -def test_diffusion_reaction_equation(alpha, forcing_term): - - # Constructor - equation = DiffusionReaction(alpha=alpha, forcing_term=forcing_term) - - # Should fail if alpha is not a float or int - with pytest.raises(ValueError): - DiffusionReaction(alpha="invalid", forcing_term=forcing_term) - - # Should fail if forcing_term is not a callable - with pytest.raises(ValueError): - DiffusionReaction(alpha=alpha, forcing_term="invalid") - - # 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) - - -@pytest.mark.parametrize("k", [1.0, 10, -7.5]) -@pytest.mark.parametrize( - "forcing_term", [lambda x: torch.sin(x), lambda x: torch.exp(x)] -) -def test_helmholtz_equation(k, forcing_term): - - # Constructor - equation = Helmholtz(k=k, forcing_term=forcing_term) - - # Should fail if k is not a float or int - with pytest.raises(ValueError): - Helmholtz(k="invalid", forcing_term=forcing_term) - - # Should fail if forcing_term is not a callable - with pytest.raises(ValueError): - Helmholtz(k=k, forcing_term="invalid") - - # Residual - residual = equation.residual(pts, u) - assert residual.shape == u.shape - - -@pytest.mark.parametrize( - "forcing_term", [lambda x: torch.sin(x), lambda x: torch.exp(x)] -) -def test_poisson_equation(forcing_term): - - # Constructor - equation = Poisson(forcing_term=forcing_term) - - # Should fail if forcing_term is not a callable - with pytest.raises(ValueError): - Poisson(forcing_term="invalid") - - # Residual - residual = equation.residual(pts, u) - assert residual.shape == u.shape - - -@pytest.mark.parametrize("c", [1.0, 10, -7.5]) -def test_acoustic_wave_equation(c): - - # Constructor - equation = AcousticWave(c=c) - - # Should fail if c is not a float or int - with pytest.raises(ValueError): - AcousticWave(c="invalid") - - # 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) diff --git a/tests/test_equation_zoo/test_acoustic_wave_equation.py b/tests/test_equation_zoo/test_acoustic_wave_equation.py new file mode 100644 index 000000000..7d4c3b3a8 --- /dev/null +++ b/tests/test_equation_zoo/test_acoustic_wave_equation.py @@ -0,0 +1,29 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import AcousticWaveEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("c", [1.0, 10, -7.5]) +def test_acoustic_wave_equation(c): + + # Constructor + equation = AcousticWaveEquation(c=c) + + # Should fail if c is not a float or int + with pytest.raises(ValueError): + AcousticWaveEquation(c="invalid") + + # 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) diff --git a/tests/test_equation_zoo/test_advection_equation.py b/tests/test_equation_zoo/test_advection_equation.py new file mode 100644 index 000000000..d78aef106 --- /dev/null +++ b/tests/test_equation_zoo/test_advection_equation.py @@ -0,0 +1,38 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import AdvectionEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("c", [1.0, 10, [1, 2.5]]) +def test_advection_equation(c): + + # Constructor + equation = AdvectionEquation(c) + + # Should fail if c is an empty list + with pytest.raises(ValueError): + AdvectionEquation([]) + + # Should fail if c is not a float, int, or list + with pytest.raises(ValueError): + AdvectionEquation("invalid") + + # 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 c is a list and its length != spatial dimension + with pytest.raises(ValueError): + equation = AdvectionEquation([1, 2, 3]) + residual = equation.residual(pts, u) diff --git a/tests/test_equation_zoo/test_allen_cahn_equation.py b/tests/test_equation_zoo/test_allen_cahn_equation.py new file mode 100644 index 000000000..7f9bf23f2 --- /dev/null +++ b/tests/test_equation_zoo/test_allen_cahn_equation.py @@ -0,0 +1,34 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import AllenCahnEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("alpha", [1.0, 10, -7.5]) +@pytest.mark.parametrize("beta", [1.0, 10, -7.5]) +def test_allen_cahn_equation(alpha, beta): + + # Constructor + equation = AllenCahnEquation(alpha=alpha, beta=beta) + + # Should fail if alpha is not a float or int + with pytest.raises(ValueError): + AllenCahnEquation(alpha="invalid", beta=beta) + + # Should fail if beta is not a float or int + with pytest.raises(ValueError): + AllenCahnEquation(alpha=alpha, beta="invalid") + + # 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) diff --git a/tests/test_equation_zoo/test_diffusion_reaction_equation.py b/tests/test_equation_zoo/test_diffusion_reaction_equation.py new file mode 100644 index 000000000..ae2d7fc51 --- /dev/null +++ b/tests/test_equation_zoo/test_diffusion_reaction_equation.py @@ -0,0 +1,36 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import DiffusionReactionEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("alpha", [1.0, 10, -7.5]) +@pytest.mark.parametrize( + "forcing_term", [lambda x: torch.sin(x), lambda x: torch.exp(x)] +) +def test_diffusion_reaction_equation(alpha, forcing_term): + + # Constructor + equation = DiffusionReactionEquation(alpha=alpha, forcing_term=forcing_term) + + # Should fail if alpha is not a float or int + with pytest.raises(ValueError): + DiffusionReactionEquation(alpha="invalid", forcing_term=forcing_term) + + # Should fail if forcing_term is not a callable + with pytest.raises(ValueError): + DiffusionReactionEquation(alpha=alpha, forcing_term="invalid") + + # 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) diff --git a/tests/test_equation_zoo/test_helmholtz_equation.py b/tests/test_equation_zoo/test_helmholtz_equation.py new file mode 100644 index 000000000..34b1a8a9f --- /dev/null +++ b/tests/test_equation_zoo/test_helmholtz_equation.py @@ -0,0 +1,32 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import HelmholtzEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("k", [1.0, 10, -7.5]) +@pytest.mark.parametrize( + "forcing_term", [lambda x: torch.sin(x), lambda x: torch.exp(x)] +) +def test_helmholtz_equation(k, forcing_term): + + # Constructor + equation = HelmholtzEquation(k=k, forcing_term=forcing_term) + + # Should fail if k is not a float or int + with pytest.raises(ValueError): + HelmholtzEquation(k="invalid", forcing_term=forcing_term) + + # Should fail if forcing_term is not a callable + with pytest.raises(ValueError): + HelmholtzEquation(k=k, forcing_term="invalid") + + # Residual + residual = equation.residual(pts, u) + assert residual.shape == u.shape diff --git a/tests/test_equation_zoo/test_poisson_equation.py b/tests/test_equation_zoo/test_poisson_equation.py new file mode 100644 index 000000000..b56a69073 --- /dev/null +++ b/tests/test_equation_zoo/test_poisson_equation.py @@ -0,0 +1,27 @@ +import pytest +import torch +from pina import LabelTensor +from pina.equation.zoo import PoissonEquation + + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize( + "forcing_term", [lambda x: torch.sin(x), lambda x: torch.exp(x)] +) +def test_poisson_equation(forcing_term): + + # Constructor + equation = PoissonEquation(forcing_term=forcing_term) + + # Should fail if forcing_term is not a callable + with pytest.raises(ValueError): + PoissonEquation(forcing_term="invalid") + + # Residual + residual = equation.residual(pts, u) + assert residual.shape == u.shape From d9f5698c44b023af4ee1e5c5a399a782f9bc1d16 Mon Sep 17 00:00:00 2001 From: GiovanniCanali Date: Fri, 17 Apr 2026 10:32:41 +0200 Subject: [PATCH 4/5] remove redundant check --- pina/_src/equation/zoo/advection_equation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pina/_src/equation/zoo/advection_equation.py b/pina/_src/equation/zoo/advection_equation.py index 903ee820b..73fb3de99 100644 --- a/pina/_src/equation/zoo/advection_equation.py +++ b/pina/_src/equation/zoo/advection_equation.py @@ -29,9 +29,8 @@ def __init__(self, c): :raises ValueError: If ``c`` is an empty list. """ # Check consistency - check_consistency(c, (float, int, list)) + check_consistency(c, (float, int)) if isinstance(c, list): - all(check_consistency(ci, (float, int)) for ci in c) if len(c) < 1: raise ValueError("'c' cannot be an empty list.") else: From 5cba39e8417ee8dfdcb20feca7ff73941fbacd9c Mon Sep 17 00:00:00 2001 From: GiovanniCanali Date: Mon, 20 Apr 2026 11:54:11 +0200 Subject: [PATCH 5/5] move all Fixed equations to the zoo --- docs/source/_rst/_code.rst | 5 +- .../source/_rst/equation/equation_factory.rst | 23 --- docs/source/_rst/equation/zoo/fixed_flux.rst | 7 + .../_rst/equation/zoo/fixed_gradient.rst | 7 + .../_rst/equation/zoo/fixed_laplacian.rst | 7 + docs/source/_rst/equation/zoo/fixed_value.rst | 7 + pina/_src/equation/equation_factory.py | 175 ------------------ .../equation/zoo/acoustic_wave_equation.py | 2 +- pina/_src/equation/zoo/advection_equation.py | 2 +- pina/_src/equation/zoo/allen_cahn_equation.py | 2 +- .../zoo/diffusion_reaction_equation.py | 2 +- pina/_src/equation/zoo/fixed_flux.py | 39 ++++ pina/_src/equation/zoo/fixed_gradient.py | 40 ++++ pina/_src/equation/zoo/fixed_laplacian.py | 54 ++++++ pina/_src/equation/zoo/fixed_value.py | 38 ++++ pina/_src/equation/zoo/helmholtz_equation.py | 2 +- pina/_src/equation/zoo/poisson_equation.py | 2 +- pina/_src/problem/base_problem.py | 2 +- .../_src/problem/zoo/acoustic_wave_problem.py | 3 +- .../problem/zoo/diffusion_reaction_problem.py | 2 +- pina/_src/problem/zoo/helmholtz_problem.py | 2 +- .../problem/zoo/inverse_poisson_problem.py | 2 +- pina/_src/problem/zoo/poisson_problem.py | 2 +- pina/equation/__init__.py | 50 +++-- pina/equation/zoo.py | 9 + .../test_domain_equation_condition.py | 2 +- tests/test_domain/test_ellipsoid_domain.py | 4 +- tests/test_equation/test_equation_factory.py | 70 ------- tests/test_equation/test_system_equation.py | 3 +- tests/test_equation_zoo/test_fixed_flux.py | 28 +++ .../test_equation_zoo/test_fixed_gradient.py | 24 +++ .../test_equation_zoo/test_fixed_laplacian.py | 23 +++ tests/test_equation_zoo/test_fixed_value.py | 22 +++ 33 files changed, 363 insertions(+), 299 deletions(-) delete mode 100644 docs/source/_rst/equation/equation_factory.rst create mode 100644 docs/source/_rst/equation/zoo/fixed_flux.rst create mode 100644 docs/source/_rst/equation/zoo/fixed_gradient.rst create mode 100644 docs/source/_rst/equation/zoo/fixed_laplacian.rst create mode 100644 docs/source/_rst/equation/zoo/fixed_value.rst delete mode 100644 pina/_src/equation/equation_factory.py create mode 100644 pina/_src/equation/zoo/fixed_flux.py create mode 100644 pina/_src/equation/zoo/fixed_gradient.py create mode 100644 pina/_src/equation/zoo/fixed_laplacian.py create mode 100644 pina/_src/equation/zoo/fixed_value.py delete mode 100644 tests/test_equation/test_equation_factory.py create mode 100644 tests/test_equation_zoo/test_fixed_flux.py create mode 100644 tests/test_equation_zoo/test_fixed_gradient.py create mode 100644 tests/test_equation_zoo/test_fixed_laplacian.py create mode 100644 tests/test_equation_zoo/test_fixed_value.py diff --git a/docs/source/_rst/_code.rst b/docs/source/_rst/_code.rst index 6c4da42b3..813f1e46b 100644 --- a/docs/source/_rst/_code.rst +++ b/docs/source/_rst/_code.rst @@ -199,7 +199,6 @@ Equations and Differential Operators Base Equation Equation System Equation - Equation Factory Differential Operators @@ -213,6 +212,10 @@ Equations Zoo Advection Equation Allen-Cahn Equation Diffusion-Reaction Equation + Fixed Flux + Fixed Gradient + Fixed Laplacian + Fixed Value Helmholtz Equation Poisson Equation diff --git a/docs/source/_rst/equation/equation_factory.rst b/docs/source/_rst/equation/equation_factory.rst deleted file mode 100644 index c5024b308..000000000 --- a/docs/source/_rst/equation/equation_factory.rst +++ /dev/null @@ -1,23 +0,0 @@ -Equation Factory -================== - -.. currentmodule:: pina.equation.equation_factory -.. autoclass:: pina._src.equation.equation_factory.FixedValue - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.FixedGradient - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.FixedFlux - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.FixedLaplacian - :members: - :show-inheritance: - -.. autoclass:: pina._src.equation.equation_factory.Laplace - :members: - :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/fixed_flux.rst b/docs/source/_rst/equation/zoo/fixed_flux.rst new file mode 100644 index 000000000..9b81db4b2 --- /dev/null +++ b/docs/source/_rst/equation/zoo/fixed_flux.rst @@ -0,0 +1,7 @@ +Fixed Flux +===================== +.. currentmodule:: pina.equation.zoo.fixed_flux + +.. automodule:: pina._src.equation.zoo.fixed_flux + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/fixed_gradient.rst b/docs/source/_rst/equation/zoo/fixed_gradient.rst new file mode 100644 index 000000000..f8da5dea8 --- /dev/null +++ b/docs/source/_rst/equation/zoo/fixed_gradient.rst @@ -0,0 +1,7 @@ +Fixed Gradient +===================== +.. currentmodule:: pina.equation.zoo.fixed_gradient + +.. automodule:: pina._src.equation.zoo.fixed_gradient + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/fixed_laplacian.rst b/docs/source/_rst/equation/zoo/fixed_laplacian.rst new file mode 100644 index 000000000..3123918a6 --- /dev/null +++ b/docs/source/_rst/equation/zoo/fixed_laplacian.rst @@ -0,0 +1,7 @@ +Fixed Laplacian +===================== +.. currentmodule:: pina.equation.zoo.fixed_laplacian + +.. automodule:: pina._src.equation.zoo.fixed_laplacian + :members: + :show-inheritance: diff --git a/docs/source/_rst/equation/zoo/fixed_value.rst b/docs/source/_rst/equation/zoo/fixed_value.rst new file mode 100644 index 000000000..29eaa0521 --- /dev/null +++ b/docs/source/_rst/equation/zoo/fixed_value.rst @@ -0,0 +1,7 @@ +Fixed Value +===================== +.. currentmodule:: pina.equation.zoo.fixed_value + +.. automodule:: pina._src.equation.zoo.fixed_value + :members: + :show-inheritance: diff --git a/pina/_src/equation/equation_factory.py b/pina/_src/equation/equation_factory.py deleted file mode 100644 index f52f108ab..000000000 --- a/pina/_src/equation/equation_factory.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Module for defining various general equations.""" - -from pina._src.equation.equation import Equation -from pina._src.core.operator import grad, div, laplacian - - -class FixedValue(Equation): # pylint: disable=R0903 - """ - Equation to enforce a fixed value. Can be used to enforce Dirichlet Boundary - conditions. - """ - - def __init__(self, value, components=None): - """ - Initialization of the :class:`FixedValue` class. - - :param float value: The fixed value to be enforced. - :param list[str] components: The name of the output variables for which - the fixed value condition is applied. It should be a subset of the - output labels. If ``None``, all output variables are considered. - Default is ``None``. - """ - - def equation(_, output_): - """ - Definition of the equation to enforce a fixed value. - - :param LabelTensor input_: The input points where the residual is - computed. - :param LabelTensor output_: The output tensor, potentially produced - by a :class:`torch.nn.Module` instance. - :return: The residual values of the equation. - :rtype: LabelTensor - """ - if components is None: - return output_ - value - return output_.extract(components) - value - - super().__init__(equation) - - -class FixedGradient(Equation): # pylint: disable=R0903 - """ - Equation to enforce a fixed gradient for a specific condition. - """ - - def __init__(self, value, components=None, d=None): - """ - Initialization of the :class:`FixedGradient` class. - - :param float value: The fixed value to be enforced to the gradient. - :param list[str] components: The name of the output variables for which - the fixed gradient condition is applied. It should be a subset of - the output labels. If ``None``, all output variables are considered. - Default is ``None``. - :param list[str] d: The name of the input variables on which the - gradient is computed. It should be a subset of the input labels. - If ``None``, all the input variables are considered. - Default is ``None``. - """ - - def equation(input_, output_): - """ - Definition of the equation to enforce a fixed gradient. - - :param LabelTensor input_: The input points where the residual is - computed. - :param LabelTensor output_: The output tensor, potentially produced - by a :class:`torch.nn.Module` instance. - :return: The residual values of the equation. - :rtype: LabelTensor - """ - return grad(output_, input_, components=components, d=d) - value - - super().__init__(equation) - - -class FixedFlux(Equation): # pylint: disable=R0903 - """ - Equation to enforce a fixed flux, or divergence, for a specific condition. - """ - - def __init__(self, value, components=None, d=None): - """ - Initialization of the :class:`FixedFlux` class. - - :param float value: The fixed value to be enforced to the flux. - :param list[str] components: The name of the output variables for which - the fixed flux condition is applied. It should be a subset of the - output labels. If ``None``, all output variables are considered. - Default is ``None``. - :param list[str] d: The name of the input variables on which the flux - is computed. It should be a subset of the input labels. If ``None``, - all the input variables are considered. Default is ``None``. - """ - - def equation(input_, output_): - """ - Definition of the equation to enforce a fixed flux. - - :param LabelTensor input_: The input points where the residual is - computed. - :param LabelTensor output_: The output tensor, potentially produced - by a :class:`torch.nn.Module` instance. - :return: The residual values of the equation. - :rtype: LabelTensor - """ - return div(output_, input_, components=components, d=d) - value - - super().__init__(equation) - - -class FixedLaplacian(Equation): # pylint: disable=R0903 - """ - Equation to enforce a fixed laplacian for a specific condition. - """ - - def __init__(self, value, components=None, d=None): - """ - Initialization of the :class:`FixedLaplacian` class. - - :param float value: The fixed value to be enforced to the laplacian. - :param list[str] components: The name of the output variables for which - the fixed laplace condition is applied. It should be a subset of the - output labels. If ``None``, all output variables are considered. - Default is ``None``. - :param list[str] d: The name of the input variables on which the - laplacian is computed. It should be a subset of the input labels. - If ``None``, all the input variables are considered. - Default is ``None``. - """ - - def equation(input_, output_): - """ - Definition of the equation to enforce a fixed laplacian. - - :param LabelTensor input_: The input points where the residual is - computed. - :param LabelTensor output_: The output tensor, potentially produced - by a :class:`torch.nn.Module` instance. - :return: The residual values of the equation. - :rtype: LabelTensor - """ - return ( - laplacian(output_, input_, components=components, d=d) - value - ) - - super().__init__(equation) - - -class Laplace(FixedLaplacian): # pylint: disable=R0903 - r""" - Equation to enforce a null laplacian for a specific condition. - The equation is defined as follows: - - .. math:: - - \Delta u = 0 - - """ - - def __init__(self, components=None, d=None): - """ - Initialization of the :class:`Laplace` class. - - :param list[str] components: The name of the output variables for which - the null laplace condition is applied. It should be a subset of the - output labels. If ``None``, all output variables are considered. - Default is ``None``. - :param list[str] d: The name of the input variables on which the - laplacian is computed. It should be a subset of the input labels. - If ``None``, all the input variables are considered. - Default is ``None``. - """ - super().__init__(0.0, components=components, d=d) diff --git a/pina/_src/equation/zoo/acoustic_wave_equation.py b/pina/_src/equation/zoo/acoustic_wave_equation.py index 45614cb5d..8a6d2bf07 100644 --- a/pina/_src/equation/zoo/acoustic_wave_equation.py +++ b/pina/_src/equation/zoo/acoustic_wave_equation.py @@ -5,7 +5,7 @@ from pina._src.core.utils import check_consistency -class AcousticWaveEquation(Equation): # pylint: disable=R0903 +class AcousticWaveEquation(Equation): r""" Implementation of the N-dimensional isotropic acoustic wave equation. The equation is defined as follows: diff --git a/pina/_src/equation/zoo/advection_equation.py b/pina/_src/equation/zoo/advection_equation.py index 73fb3de99..81e476bd5 100644 --- a/pina/_src/equation/zoo/advection_equation.py +++ b/pina/_src/equation/zoo/advection_equation.py @@ -6,7 +6,7 @@ from pina._src.core.utils import check_consistency -class AdvectionEquation(Equation): # pylint: disable=R0903 +class AdvectionEquation(Equation): r""" Implementation of the N-dimensional advection equation with constant velocity parameter. The equation is defined as follows: diff --git a/pina/_src/equation/zoo/allen_cahn_equation.py b/pina/_src/equation/zoo/allen_cahn_equation.py index 81c6c82b1..e7091add2 100644 --- a/pina/_src/equation/zoo/allen_cahn_equation.py +++ b/pina/_src/equation/zoo/allen_cahn_equation.py @@ -5,7 +5,7 @@ from pina._src.core.utils import check_consistency -class AllenCahnEquation(Equation): # pylint: disable=R0903 +class AllenCahnEquation(Equation): r""" Implementation of the N-dimensional Allen-Cahn equation, defined as follows: diff --git a/pina/_src/equation/zoo/diffusion_reaction_equation.py b/pina/_src/equation/zoo/diffusion_reaction_equation.py index 76768088a..4f276dd54 100644 --- a/pina/_src/equation/zoo/diffusion_reaction_equation.py +++ b/pina/_src/equation/zoo/diffusion_reaction_equation.py @@ -6,7 +6,7 @@ from pina._src.core.utils import check_consistency -class DiffusionReactionEquation(Equation): # pylint: disable=R0903 +class DiffusionReactionEquation(Equation): r""" Implementation of the N-dimensional Diffusion-Reaction equation, defined as follows: diff --git a/pina/_src/equation/zoo/fixed_flux.py b/pina/_src/equation/zoo/fixed_flux.py new file mode 100644 index 000000000..f63dd5a14 --- /dev/null +++ b/pina/_src/equation/zoo/fixed_flux.py @@ -0,0 +1,39 @@ +"""Module for defining the fixed flux equation.""" + +from pina._src.equation.equation import Equation +from pina._src.core.operator import div + + +class FixedFlux(Equation): + """ + Equation to enforce a fixed flux, or divergence, for a specific condition. + """ + + def __init__(self, value, components=None, d=None): + """ + Initialization of the :class:`FixedFlux` class. + + :param float value: The fixed value to be enforced to the flux. + :param list[str] components: The name of the output variables for which + the fixed flux condition is applied. It should be a subset of the + output labels. If ``None``, all output variables are considered. + Default is ``None``. + :param list[str] d: The name of the input variables on which the flux + is computed. It should be a subset of the input labels. If ``None``, + all the input variables are considered. Default is ``None``. + """ + + def equation(input_, output_): + """ + Definition of the equation to enforce a fixed flux. + + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. + :rtype: LabelTensor + """ + return div(output_, input_, components=components, d=d) - value + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/fixed_gradient.py b/pina/_src/equation/zoo/fixed_gradient.py new file mode 100644 index 000000000..e01b49c67 --- /dev/null +++ b/pina/_src/equation/zoo/fixed_gradient.py @@ -0,0 +1,40 @@ +"""Module for defining the fixed gradient equation.""" + +from pina._src.equation.equation import Equation +from pina._src.core.operator import grad + + +class FixedGradient(Equation): + """ + Equation to enforce a fixed gradient for a specific condition. + """ + + def __init__(self, value, components=None, d=None): + """ + Initialization of the :class:`FixedGradient` class. + + :param float value: The fixed value to be enforced to the gradient. + :param list[str] components: The name of the output variables for which + the fixed gradient condition is applied. It should be a subset of + the output labels. If ``None``, all output variables are considered. + Default is ``None``. + :param list[str] d: The name of the input variables on which the + gradient is computed. It should be a subset of the input labels. + If ``None``, all the input variables are considered. + Default is ``None``. + """ + + def equation(input_, output_): + """ + Definition of the equation to enforce a fixed gradient. + + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. + :rtype: LabelTensor + """ + return grad(output_, input_, components=components, d=d) - value + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/fixed_laplacian.py b/pina/_src/equation/zoo/fixed_laplacian.py new file mode 100644 index 000000000..6e18069ce --- /dev/null +++ b/pina/_src/equation/zoo/fixed_laplacian.py @@ -0,0 +1,54 @@ +"""Module for defining the fixed laplacian equation.""" + +import warnings +from pina._src.equation.equation import Equation +from pina._src.core.operator import laplacian + + +class FixedLaplacian(Equation): + """ + Equation to enforce a fixed laplacian for a specific condition. + """ + + def __init__(self, value, components=None, d=None): + """ + Initialization of the :class:`FixedLaplacian` class. + + :param float value: The fixed value to be enforced to the laplacian. + :param list[str] components: The name of the output variables for which + the fixed laplace condition is applied. It should be a subset of the + output labels. If ``None``, all output variables are considered. + Default is ``None``. + :param list[str] d: The name of the input variables on which the + laplacian is computed. It should be a subset of the input labels. + If ``None``, all the input variables are considered. + Default is ``None``. + """ + + def equation(input_, output_): + """ + Definition of the equation to enforce a fixed laplacian. + + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. + :rtype: LabelTensor + """ + return ( + laplacian(output_, input_, components=components, d=d) - value + ) + + super().__init__(equation) + + +# Back-compatibility with version 0.2, to be removed soon +class Laplace(FixedLaplacian): + def __init__(self, components=None, d=None): + warnings.warn( + "Laplace is deprecated, use FixedLaplacian with value=0.0 instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(0.0, components=components, d=d) diff --git a/pina/_src/equation/zoo/fixed_value.py b/pina/_src/equation/zoo/fixed_value.py new file mode 100644 index 000000000..d08da2a8b --- /dev/null +++ b/pina/_src/equation/zoo/fixed_value.py @@ -0,0 +1,38 @@ +"""Module for defining the fixed value equation.""" + +from pina._src.equation.equation import Equation + + +class FixedValue(Equation): + """ + Equation to enforce a fixed value. Can be used to enforce Dirichlet Boundary + conditions. + """ + + def __init__(self, value, components=None): + """ + Initialization of the :class:`FixedValue` class. + + :param float value: The fixed value to be enforced. + :param list[str] components: The name of the output variables for which + the fixed value condition is applied. It should be a subset of the + output labels. If ``None``, all output variables are considered. + Default is ``None``. + """ + + def equation(_, output_): + """ + Definition of the equation to enforce a fixed value. + + :param LabelTensor input_: The input points where the residual is + computed. + :param LabelTensor output_: The output tensor, potentially produced + by a :class:`torch.nn.Module` instance. + :return: The residual values of the equation. + :rtype: LabelTensor + """ + if components is None: + return output_ - value + return output_.extract(components) - value + + super().__init__(equation) diff --git a/pina/_src/equation/zoo/helmholtz_equation.py b/pina/_src/equation/zoo/helmholtz_equation.py index 3b628728a..57b353bf0 100644 --- a/pina/_src/equation/zoo/helmholtz_equation.py +++ b/pina/_src/equation/zoo/helmholtz_equation.py @@ -6,7 +6,7 @@ from pina._src.core.utils import check_consistency -class HelmholtzEquation(Equation): # pylint: disable=R0903 +class HelmholtzEquation(Equation): r""" Implementation of the Helmholtz equation, defined as follows: diff --git a/pina/_src/equation/zoo/poisson_equation.py b/pina/_src/equation/zoo/poisson_equation.py index 15713539f..2ab80ff33 100644 --- a/pina/_src/equation/zoo/poisson_equation.py +++ b/pina/_src/equation/zoo/poisson_equation.py @@ -6,7 +6,7 @@ from pina._src.core.utils import check_consistency -class PoissonEquation(Equation): # pylint: disable=R0903 +class PoissonEquation(Equation): r""" Implementation of the Poisson equation, defined as follows: diff --git a/pina/_src/problem/base_problem.py b/pina/_src/problem/base_problem.py index dc02b20ae..ec9fadc05 100644 --- a/pina/_src/problem/base_problem.py +++ b/pina/_src/problem/base_problem.py @@ -301,7 +301,7 @@ def are_all_domains_discretised(self): class AbstractProblem(BaseProblem): def __init__(self, *args, **kwargs): warnings.warn( - "AbstractProblem is deprecated, use BaseProblem instead", + "AbstractProblem is deprecated, use BaseProblem instead.", DeprecationWarning, stacklevel=2, ) diff --git a/pina/_src/problem/zoo/acoustic_wave_problem.py b/pina/_src/problem/zoo/acoustic_wave_problem.py index 5445d2455..302702caa 100644 --- a/pina/_src/problem/zoo/acoustic_wave_problem.py +++ b/pina/_src/problem/zoo/acoustic_wave_problem.py @@ -8,7 +8,8 @@ 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.equation_factory import FixedValue, FixedGradient +from pina._src.equation.zoo.fixed_value import FixedValue +from pina._src.equation.zoo.fixed_gradient import FixedGradient from pina._src.equation.zoo.acoustic_wave_equation import AcousticWaveEquation diff --git a/pina/_src/problem/zoo/diffusion_reaction_problem.py b/pina/_src/problem/zoo/diffusion_reaction_problem.py index 9b8870189..39de11dbc 100644 --- a/pina/_src/problem/zoo/diffusion_reaction_problem.py +++ b/pina/_src/problem/zoo/diffusion_reaction_problem.py @@ -3,7 +3,7 @@ import torch from pina._src.condition.condition import Condition from pina._src.equation.equation import Equation -from pina._src.equation.equation_factory import FixedValue +from pina._src.equation.zoo.fixed_value import FixedValue from pina._src.problem.spatial_problem import SpatialProblem from pina._src.problem.time_dependent_problem import TimeDependentProblem from pina._src.core.utils import check_consistency diff --git a/pina/_src/problem/zoo/helmholtz_problem.py b/pina/_src/problem/zoo/helmholtz_problem.py index 6e59a24c9..601e40b3a 100644 --- a/pina/_src/problem/zoo/helmholtz_problem.py +++ b/pina/_src/problem/zoo/helmholtz_problem.py @@ -2,7 +2,7 @@ import torch from pina._src.condition.condition import Condition -from pina._src.equation.equation_factory import FixedValue +from pina._src.equation.zoo.fixed_value import FixedValue from pina._src.equation.zoo.helmholtz_equation import HelmholtzEquation from pina._src.problem.spatial_problem import SpatialProblem from pina._src.core.utils import check_consistency diff --git a/pina/_src/problem/zoo/inverse_poisson_problem.py b/pina/_src/problem/zoo/inverse_poisson_problem.py index f0865d4cb..c16735408 100644 --- a/pina/_src/problem/zoo/inverse_poisson_problem.py +++ b/pina/_src/problem/zoo/inverse_poisson_problem.py @@ -9,7 +9,7 @@ from pina._src.domain.cartesian_domain import CartesianDomain from pina._src.problem.inverse_problem import InverseProblem from pina._src.problem.spatial_problem import SpatialProblem -from pina._src.equation.equation_factory import FixedValue +from pina._src.equation.zoo.fixed_value import FixedValue from pina._src.condition.condition import Condition from pina._src.core.label_tensor import LabelTensor from pina._src.equation.equation import Equation diff --git a/pina/_src/problem/zoo/poisson_problem.py b/pina/_src/problem/zoo/poisson_problem.py index b92fbce87..50b80ad2d 100644 --- a/pina/_src/problem/zoo/poisson_problem.py +++ b/pina/_src/problem/zoo/poisson_problem.py @@ -2,7 +2,7 @@ import torch -from pina._src.equation.equation_factory import FixedValue +from pina._src.equation.zoo.fixed_value import FixedValue from pina._src.domain.cartesian_domain import CartesianDomain from pina._src.problem.spatial_problem import SpatialProblem from pina._src.condition.condition import Condition diff --git a/pina/equation/__init__.py b/pina/equation/__init__.py index 91b803c54..aa926bb0e 100644 --- a/pina/equation/__init__.py +++ b/pina/equation/__init__.py @@ -3,8 +3,9 @@ This module provides a framework for defining differential equations, boundary conditions, and complex systems of equations. It includes -pre-defined physical models such as Poisson, Laplace, and Wave equations, -along with factories for common derivative-based constraints. +pre-defined physical models such as Poisson, Helmholtz, and Wave equations, +along with equations for common derivative-based constraints, such as +FixedValue, FixedGradient, FixedFlux, and FixedLaplacian. """ __all__ = [ @@ -12,21 +13,42 @@ "BaseEquation", "Equation", "SystemEquation", - "FixedValue", - "FixedGradient", - "FixedFlux", - "FixedLaplacian", - "Laplace", ] from pina._src.equation.equation_interface import EquationInterface from pina._src.equation.base_equation import BaseEquation from pina._src.equation.equation import Equation from pina._src.equation.system_equation import SystemEquation -from pina._src.equation.equation_factory import ( - FixedFlux, - FixedGradient, - FixedLaplacian, - FixedValue, - Laplace, -) + + +# Back-compatibility with version 0.2, to be removed soon +import warnings +import importlib + +_DEPRECATED_IMPORTS = { + "FixedValue": ".zoo", + "FixedGradient": ".zoo", + "FixedFlux": ".zoo", + "FixedLaplacian": ".zoo", + "Laplace": ".zoo", + "HelmholtzEquation": ".zoo", + "PoissonEquation": ".zoo", + "AcousticWaveEquation": ".zoo", + "AdvectionEquation": ".zoo", + "AllenCahnEquation": ".zoo", + "DiffusionReactionEquation": ".zoo", +} + + +def __getattr__(name): + if name in _DEPRECATED_IMPORTS: + + warnings.warn( + f"Importing '{name}' from 'equation' is deprecated; " + f"import it from 'equation.zoo' instead.", + DeprecationWarning, + stacklevel=2, + ) + + module = importlib.import_module(_DEPRECATED_IMPORTS[name], __name__) + return getattr(module, name) diff --git a/pina/equation/zoo.py b/pina/equation/zoo.py index daecc370d..140c836d7 100644 --- a/pina/equation/zoo.py +++ b/pina/equation/zoo.py @@ -4,7 +4,12 @@ "AdvectionEquation", "AllenCahnEquation", "DiffusionReactionEquation", + "FixedFlux", + "FixedGradient", + "FixedLaplacian", + "FixedValue", "HelmholtzEquation", + "Laplace", "PoissonEquation", "AcousticWaveEquation", ] @@ -17,3 +22,7 @@ ) from pina._src.equation.zoo.helmholtz_equation import HelmholtzEquation from pina._src.equation.zoo.poisson_equation import PoissonEquation +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 +from pina._src.equation.zoo.fixed_laplacian import FixedLaplacian, Laplace diff --git a/tests/test_condition/test_domain_equation_condition.py b/tests/test_condition/test_domain_equation_condition.py index 46bc89bc3..d2afbceae 100644 --- a/tests/test_condition/test_domain_equation_condition.py +++ b/tests/test_condition/test_domain_equation_condition.py @@ -1,7 +1,7 @@ import pytest from pina import Condition from pina.domain import CartesianDomain -from pina._src.equation.equation_factory import FixedValue +from pina.equation.zoo import FixedValue from pina.condition import DomainEquationCondition example_domain = CartesianDomain({"x": [0, 1], "y": [0, 1]}) diff --git a/tests/test_domain/test_ellipsoid_domain.py b/tests/test_domain/test_ellipsoid_domain.py index a39d3eca2..6e1920b7f 100644 --- a/tests/test_domain/test_ellipsoid_domain.py +++ b/tests/test_domain/test_ellipsoid_domain.py @@ -92,11 +92,11 @@ def test_update(dict, sample_surface): ) domain_2 = EllipsoidDomain( ellipsoid_dict={"new_var": [0, 1], "x": 1}, - sample_surface=sample_surface + sample_surface=sample_surface, ) domain_3 = EllipsoidDomain( ellipsoid_dict=dict | {"new_var": [0, 1], "x": 1}, - sample_surface=sample_surface + sample_surface=sample_surface, ) # Update domain_1 with domain_2 diff --git a/tests/test_equation/test_equation_factory.py b/tests/test_equation/test_equation_factory.py deleted file mode 100644 index 3a931bd5f..000000000 --- a/tests/test_equation/test_equation_factory.py +++ /dev/null @@ -1,70 +0,0 @@ -from pina.equation import FixedValue, FixedGradient, FixedFlux, FixedLaplacian -from pina import LabelTensor -import torch -import pytest - -# Define input and output values -pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) -u = torch.pow(pts, 2) -u.labels = ["u", "v", "w"] - - -@pytest.mark.parametrize("value", [0, 10, -7.5]) -@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) -def test_fixed_value(value, components): - - # Constructor - equation = FixedValue(value=value, components=components) - - # Residual - residual = equation.residual(pts, u) - len_c = len(components) if components is not None else u.shape[1] - assert residual.shape == (pts.shape[0], len_c) - - -@pytest.mark.parametrize("value", [0, 10, -7.5]) -@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) -@pytest.mark.parametrize("d", [None, "x", ["x", "y"]]) -def test_fixed_gradient(value, components, d): - - # Constructor - equation = FixedGradient(value=value, components=components, d=d) - - # Residual - residual = equation.residual(pts, u) - len_c = len(components) if components is not None else u.shape[1] - len_d = len(d) if d is not None else pts.shape[1] - assert residual.shape == (pts.shape[0], len_c * len_d) - - -@pytest.mark.parametrize("value", [0, 10, -7.5]) -@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) -@pytest.mark.parametrize("d", [None, "x", ["x", "y"]]) -def test_fixed_flux(value, components, d): - - # Divergence requires components and d to be of the same length - len_c = len(components) if components is not None else u.shape[1] - len_d = len(d) if d is not None else pts.shape[1] - if len_c != len_d: - return - - # Constructor - equation = FixedFlux(value=value, components=components, d=d) - - # Residual - residual = equation.residual(pts, u) - assert residual.shape == (pts.shape[0], 1) - - -@pytest.mark.parametrize("value", [0, 10, -7.5]) -@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) -@pytest.mark.parametrize("d", [None, "x", ["x", "y"]]) -def test_fixed_laplacian(value, components, d): - - # Constructor - equation = FixedLaplacian(value=value, components=components, d=d) - - # Residual - residual = equation.residual(pts, u) - len_c = len(components) if components is not None else u.shape[1] - assert residual.shape == (pts.shape[0], len_c) diff --git a/tests/test_equation/test_system_equation.py b/tests/test_equation/test_system_equation.py index 294c30d56..7fbc7baae 100644 --- a/tests/test_equation/test_system_equation.py +++ b/tests/test_equation/test_system_equation.py @@ -1,4 +1,5 @@ -from pina.equation import SystemEquation, FixedValue, FixedGradient +from pina.equation import SystemEquation +from pina.equation.zoo import FixedValue, FixedGradient from pina.operator import grad, laplacian from pina import LabelTensor import torch diff --git a/tests/test_equation_zoo/test_fixed_flux.py b/tests/test_equation_zoo/test_fixed_flux.py new file mode 100644 index 000000000..a8080b353 --- /dev/null +++ b/tests/test_equation_zoo/test_fixed_flux.py @@ -0,0 +1,28 @@ +from pina.equation.zoo import FixedFlux +from pina import LabelTensor +import torch +import pytest + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("value", [0, 10, -7.5]) +@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) +@pytest.mark.parametrize("d", [None, "x", ["x", "y"]]) +def test_fixed_flux(value, components, d): + + # Divergence requires components and d to be of the same length + len_c = len(components) if components is not None else u.shape[1] + len_d = len(d) if d is not None else pts.shape[1] + if len_c != len_d: + return + + # Constructor + equation = FixedFlux(value=value, components=components, d=d) + + # Residual + residual = equation.residual(pts, u) + assert residual.shape == (pts.shape[0], 1) diff --git a/tests/test_equation_zoo/test_fixed_gradient.py b/tests/test_equation_zoo/test_fixed_gradient.py new file mode 100644 index 000000000..627cf7b25 --- /dev/null +++ b/tests/test_equation_zoo/test_fixed_gradient.py @@ -0,0 +1,24 @@ +from pina.equation.zoo import FixedGradient +from pina import LabelTensor +import torch +import pytest + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("value", [0, 10, -7.5]) +@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) +@pytest.mark.parametrize("d", [None, "x", ["x", "y"]]) +def test_fixed_gradient(value, components, d): + + # Constructor + equation = FixedGradient(value=value, components=components, d=d) + + # Residual + residual = equation.residual(pts, u) + len_c = len(components) if components is not None else u.shape[1] + len_d = len(d) if d is not None else pts.shape[1] + assert residual.shape == (pts.shape[0], len_c * len_d) diff --git a/tests/test_equation_zoo/test_fixed_laplacian.py b/tests/test_equation_zoo/test_fixed_laplacian.py new file mode 100644 index 000000000..f88e89c29 --- /dev/null +++ b/tests/test_equation_zoo/test_fixed_laplacian.py @@ -0,0 +1,23 @@ +from pina.equation.zoo import FixedLaplacian +from pina import LabelTensor +import torch +import pytest + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("value", [0, 10, -7.5]) +@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) +@pytest.mark.parametrize("d", [None, "x", ["x", "y"]]) +def test_fixed_laplacian(value, components, d): + + # Constructor + equation = FixedLaplacian(value=value, components=components, d=d) + + # Residual + residual = equation.residual(pts, u) + len_c = len(components) if components is not None else u.shape[1] + assert residual.shape == (pts.shape[0], len_c) diff --git a/tests/test_equation_zoo/test_fixed_value.py b/tests/test_equation_zoo/test_fixed_value.py new file mode 100644 index 000000000..e75de115a --- /dev/null +++ b/tests/test_equation_zoo/test_fixed_value.py @@ -0,0 +1,22 @@ +from pina.equation.zoo import FixedValue +from pina import LabelTensor +import torch +import pytest + +# Define input and output values +pts = LabelTensor(torch.rand(10, 3, requires_grad=True), labels=["x", "y", "t"]) +u = torch.pow(pts, 2) +u.labels = ["u", "v", "w"] + + +@pytest.mark.parametrize("value", [0, 10, -7.5]) +@pytest.mark.parametrize("components", [None, "u", ["u", "w"]]) +def test_fixed_value(value, components): + + # Constructor + equation = FixedValue(value=value, components=components) + + # Residual + residual = equation.residual(pts, u) + len_c = len(components) if components is not None else u.shape[1] + assert residual.shape == (pts.shape[0], len_c)