From 2ca08b5236b33c868a8dc14075bee1c0c1bfecad Mon Sep 17 00:00:00 2001 From: Nicola Demo Date: Tue, 18 Apr 2023 15:00:26 +0200 Subject: [PATCH] Docs (#81) * clean `condition` module * add docs --- docs/source/_rst/abstractproblem.rst | 3 +- docs/source/_rst/code.rst | 32 +++++- docs/source/_rst/condition.rst | 4 +- docs/source/_rst/location.rst | 10 ++ docs/source/_rst/multifeedforward.rst | 6 +- docs/source/_rst/parametricproblem.rst | 6 +- docs/source/_rst/spatialproblem.rst | 3 +- docs/source/_rst/timedepproblem.rst | 3 +- pina/condition.py | 8 ++ pina/model/multi_feed_forward.py | 13 +-- pina/operators.py | 135 +++++++++++++------------ pina/pinn.py | 16 +-- pina/plotter.py | 62 +++++------- pina/problem/parametric_problem.py | 10 +- pina/problem/spatial_problem.py | 7 +- pina/problem/timedep_problem.py | 12 +-- tests/test_condition.py | 2 +- tests/test_pinn.py | 24 +++-- 18 files changed, 198 insertions(+), 158 deletions(-) create mode 100644 docs/source/_rst/location.rst diff --git a/docs/source/_rst/abstractproblem.rst b/docs/source/_rst/abstractproblem.rst index f6a0873..770533e 100644 --- a/docs/source/_rst/abstractproblem.rst +++ b/docs/source/_rst/abstractproblem.rst @@ -1,5 +1,5 @@ AbstractProblem -=========== +=============== .. currentmodule:: pina.problem.abstract_problem .. automodule:: pina.problem.abstract_problem @@ -9,4 +9,3 @@ AbstractProblem :private-members: :undoc-members: :show-inheritance: - :noindex: diff --git a/docs/source/_rst/code.rst b/docs/source/_rst/code.rst index feb61db..6663d8f 100644 --- a/docs/source/_rst/code.rst +++ b/docs/source/_rst/code.rst @@ -7,14 +7,36 @@ Code Documentation PINN LabelTensor Span + Operators + Plotter + Condition + Location + +Model +----- + +.. toctree:: + :maxdepth: 3 + FeedForward DeepONet - ContinuousConv MultiFeedForward + +Layers +------ + +.. toctree:: + :maxdepth: 3 + + ContinuousConv + +Problem +------- + +.. toctree:: + :maxdepth: 3 + AbstractProblem SpatialProblem TimeDependentProblem - Operators - Plotter - PINN - Condition + ParametricProblem diff --git a/docs/source/_rst/condition.rst b/docs/source/_rst/condition.rst index fe834b7..8a554d1 100644 --- a/docs/source/_rst/condition.rst +++ b/docs/source/_rst/condition.rst @@ -7,6 +7,4 @@ Condition .. autoclass:: Condition :members: :private-members: - :undoc-members: - :show-inheritance: - :noindex: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/_rst/location.rst b/docs/source/_rst/location.rst new file mode 100644 index 0000000..dfba991 --- /dev/null +++ b/docs/source/_rst/location.rst @@ -0,0 +1,10 @@ +Location +========= +.. currentmodule:: pina.location + +.. automodule:: pina.location + +.. autoclass:: Location + :members: + :private-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/_rst/multifeedforward.rst b/docs/source/_rst/multifeedforward.rst index 30a3e06..faa9c33 100644 --- a/docs/source/_rst/multifeedforward.rst +++ b/docs/source/_rst/multifeedforward.rst @@ -1,5 +1,5 @@ MultiFeedForward -=========== +================ .. currentmodule:: pina.model.multi_feed_forward .. automodule:: pina.model.multi_feed_forward @@ -7,6 +7,4 @@ MultiFeedForward .. autoclass:: MultiFeedForward :members: :private-members: - :undoc-members: - :show-inheritance: - :noindex: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/_rst/parametricproblem.rst b/docs/source/_rst/parametricproblem.rst index bd8dab8..84eac3f 100644 --- a/docs/source/_rst/parametricproblem.rst +++ b/docs/source/_rst/parametricproblem.rst @@ -1,5 +1,5 @@ ParametricProblem -=========== +================= .. currentmodule:: pina.problem.parametric_problem .. automodule:: pina.problem.parametric_problem @@ -7,6 +7,4 @@ ParametricProblem .. autoclass:: ParametricProblem :members: :private-members: - :undoc-members: - :show-inheritance: - :noindex: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/_rst/spatialproblem.rst b/docs/source/_rst/spatialproblem.rst index 896892f..74c1fba 100644 --- a/docs/source/_rst/spatialproblem.rst +++ b/docs/source/_rst/spatialproblem.rst @@ -1,5 +1,5 @@ SpatialProblem -=========== +============== .. currentmodule:: pina.problem.spatial_problem .. automodule:: pina.problem.spatial_problem @@ -9,4 +9,3 @@ SpatialProblem :private-members: :undoc-members: :show-inheritance: - :noindex: diff --git a/docs/source/_rst/timedepproblem.rst b/docs/source/_rst/timedepproblem.rst index e96c344..2f81119 100644 --- a/docs/source/_rst/timedepproblem.rst +++ b/docs/source/_rst/timedepproblem.rst @@ -1,5 +1,5 @@ TimeDependentProblem -=========== +==================== .. currentmodule:: pina.problem.timedep_problem .. automodule:: pina.problem.timedep_problem @@ -9,4 +9,3 @@ TimeDependentProblem :private-members: :undoc-members: :show-inheritance: - :noindex: diff --git a/pina/condition.py b/pina/condition.py index e9f0679..335438c 100644 --- a/pina/condition.py +++ b/pina/condition.py @@ -82,5 +82,13 @@ class Condition: if not self._dictvalue_isinstance(kwargs, 'location', Location): raise TypeError('`location` must be a Location.') + if 'function' in kwargs: + kwargs['function'] = [kwargs['function']] + + for i, func in enumerate(kwargs['function']): + if not callable(func): + raise TypeError( + f'`function[{i}]` must be a callable function.') + for key, value in kwargs.items(): setattr(self, key, value) \ No newline at end of file diff --git a/pina/model/multi_feed_forward.py b/pina/model/multi_feed_forward.py index d8d8faa..2e40860 100644 --- a/pina/model/multi_feed_forward.py +++ b/pina/model/multi_feed_forward.py @@ -6,16 +6,17 @@ from .feed_forward import FeedForward class MultiFeedForward(torch.nn.Module): """ + This model allows to create a network with multiple FeedForward combined + together. The user has to define the `forward` method choosing how to + combine the different FeedForward networks. + :param dict dff_dict: dictionary of FeedForward networks. """ - def __init__(self, dff_dict): - ''' - dff_dict: dict of FeedForward objects - ''' + def __init__(self, ffn_dict): super().__init__() - if not isinstance(dff_dict, dict): + if not isinstance(ffn_dict, dict): raise TypeError - for name, constructor_args in dff_dict.items(): + for name, constructor_args in ffn_dict.items(): setattr(self, name, FeedForward(**constructor_args)) diff --git a/pina/operators.py b/pina/operators.py index a591108..61df1a6 100644 --- a/pina/operators.py +++ b/pina/operators.py @@ -6,36 +6,39 @@ from pina.label_tensor import LabelTensor def grad(output_, input_, components=None, d=None): """ - Perform gradient operation. The operator works for - vectorial and scalar functions, with multiple input - coordinates. + Perform gradient operation. The operator works for vectorial and scalar + functions, with multiple input coordinates. - :param output_: output of the PINN, i.e. function values. - :type output_: LabelTensor - :param input_: input of the PINN, i.e. function coordinates. - :type input_: LabelTensor - :param components: function components to apply the operator, - defaults to None. - :type components: list(str), optional - :param d: coordinates of function components to be differentiated, - defaults to None. - :type d: list(str), optional + :param LabelTensor output_: the output tensor onto which computing the + gradient. + :param LabelTensor input_: the input tensor with respect to which computing + the gradient. + :param list(str) components: the name of the output variables to calculate + the gradient for. It should be a subset of the output labels. If None, + all the output variables are considered. Default is None. + :param list(str) d: the name of the input variables on which the gradient is + calculated. d should be a subset of the input labels. If None, all the + input variables are considered. Default is None. + + :return: the gradient tensor. + :rtype: LabelTensor """ def grad_scalar_output(output_, input_, d): """ - Perform gradient operation for a scalar function. + Perform gradient operation for a scalar output. + + :param LabelTensor output_: the output tensor onto which computing the + gradient. It has to be a column tensor. + :param LabelTensor input_: the input tensor with respect to which + computing the gradient. + :param list(str) d: the name of the input variables on which the + gradient is calculated. d should be a subset of the input labels. If + None, all the input variables are considered. Default is None. - :param output_: output of the PINN, i.e. function values. - :type output_: LabelTensor - :param input_: input of the PINN, i.e. function coordinates. - :type input_: LabelTensor - :param d: coordinates of function components to be differentiated, - defaults to None. - :type d: list(str), optional :raises RuntimeError: a vectorial function is passed. :raises RuntimeError: missing derivative labels. - :return: function gradients. + :return: the gradient tensor. :rtype: LabelTensor """ @@ -93,24 +96,25 @@ def grad(output_, input_, components=None, d=None): def div(output_, input_, components=None, d=None): """ - Perform divergence operation. The operator works for - vectorial functions, with multiple input coordinates. + Perform divergence operation. The operator works for vectorial functions, + with multiple input coordinates. + + :param LabelTensor output_: the output tensor onto which computing the + divergence. + :param LabelTensor input_: the input tensor with respect to which computing + the divergence. + :param list(str) components: the name of the output variables to calculate + the divergence for. It should be a subset of the output labels. If None, + all the output variables are considered. Default is None. + :param list(str) d: the name of the input variables on which the divergence + is calculated. d should be a subset of the input labels. If None, all + the input variables are considered. Default is None. - :param output_: output of the PINN, i.e. function values. - :type output_: LabelTensor - :param input_: input of the PINN, i.e. function coordinates. - :type input_: LabelTensor - :param components: function components to apply the operator, - defaults to None. - :type components: list(str), optional - :param d: coordinates of function components to be differentiated, - defaults to None. - :type d: list(str), optional :raises TypeError: div operator works only for LabelTensor. :raises ValueError: div operator works only for vector fields. :raises ValueError: div operator must derive all components with respect to all coordinates. - :return: Function divergence. + :return: the divergenge tensor. :rtype: LabelTensor """ if not isinstance(input_, LabelTensor): @@ -143,27 +147,24 @@ def div(output_, input_, components=None, d=None): def nabla(output_, input_, components=None, d=None, method='std'): """ - Perform nabla (laplace) operation. The operator works for - vectorial and scalar functions, with multiple input - coordinates. + Perform nabla (laplace) operator. The operator works for vectorial and + scalar functions, with multiple input coordinates. - :param output_: output of the PINN, i.e. function values. - :type output_: LabelTensor - :param input_: input of the PINN, i.e. function coordinates. - :type input_: LabelTensor - :param components: function components to apply the operator, - defaults to None. - :type components: list(str), optional - :param d: coordinates of function components to be differentiated, - defaults to None. - :type d: list(str), optional - :param method: used method to calculate nabla, defaults to 'std'. - :type method: str, optional including 'divgrad' where first gradient - and later divergece operator are applied. + :param LabelTensor output_: the output tensor onto which computing the + nabla. + :param LabelTensor input_: the input tensor with respect to which computing + the nabla. + :param list(str) components: the name of the output variables to calculate + the nabla for. It should be a subset of the output labels. If None, + all the output variables are considered. Default is None. + :param list(str) d: the name of the input variables on which the nabla + is calculated. d should be a subset of the input labels. If None, all + the input variables are considered. Default is None. + :param str method: used method to calculate nabla, defaults to 'std'. :raises ValueError: for vectorial field derivative with respect to all coordinates must be performed. :raises NotImplementedError: 'divgrad' not implemented as method. - :return: Function nabla. + :return: The tensor containing the result of the nabla operator. :rtype: LabelTensor """ if d is None: @@ -212,22 +213,22 @@ def nabla(output_, input_, components=None, d=None, method='std'): def advection(output_, input_, velocity_field, components=None, d=None): """ - Perform advection operation. The operator works for - vectorial functions, with multiple input coordinates. + Perform advection operation. The operator works for vectorial functions, + with multiple input coordinates. - :param output_: output of the PINN, i.e. function values. - :type output_: LabelTensor - :param input_: input of the PINN, i.e. function coordinates. - :type input_: LabelTensor - :param velocity_field: field used for multiplying the gradient. - :type velocity_field: str - :param components: function components to apply the operator, - defaults to None. - :type components: list(str), optional - :param d: coordinates of function components to be differentiated, - defaults to None. - :type d: list(str), optional - :return: Function advection. + :param LabelTensor output_: the output tensor onto which computing the + nabla. + :param LabelTensor input_: the input tensor with respect to which computing + the nabla. + :param str velocity_field: the name of the output variables which is used + as velocity field. It should be a subset of the output labels. + :param list(str) components: the name of the output variables to calculate + the nabla for. It should be a subset of the output labels. If None, + all the output variables are considered. Default is None. + :param list(str) d: the name of the input variables on which the nabla + is calculated. d should be a subset of the input labels. If None, all + the input variables are considered. Default is None. + :return: the tensor containing the result of the advection operator. :rtype: LabelTensor """ if d is None: diff --git a/pina/pinn.py b/pina/pinn.py index f94cab8..b24cdcc 100644 --- a/pina/pinn.py +++ b/pina/pinn.py @@ -26,21 +26,22 @@ class PINN(object): device='cpu', error_norm='mse'): ''' - :param Problem problem: the formualation of the problem. + :param AbstractProblem problem: the formualation of the problem. :param torch.nn.Module model: the neural network model to use. - :param torch.optim optimizer: the neural network optimizer to use; - default is `torch.optim.Adam`. + :param torch.optim.Optimizer optimizer: the neural network optimizer to + use; default is `torch.optim.Adam`. :param dict optimizer_kwargs: Optimizer constructor keyword args. :param float lr: the learning rate; default is 0.001. - :param torch.optim.lr_scheduler._LRScheduler lr_scheduler_type: Learning rate scheduler. + :param torch.optim.LRScheduler lr_scheduler_type: Learning + rate scheduler. :param dict lr_scheduler_kwargs: LR scheduler constructor keyword args. :param float regularizer: the coefficient for L2 regularizer term. :param type dtype: the data type to use for the model. Valid option are `torch.float32` and `torch.float64` (`torch.float16` only on GPU); default is `torch.float64`. - :param string device: the device used for training; default 'cpu' + :param str device: the device used for training; default 'cpu' option include 'cuda' if cuda is available. - :param string/int error_norm: the loss function used as minimizer, + :param (str, int) error_norm: the loss function used as minimizer, default mean square error 'mse'. If string options include mean error 'me' and mean square error 'mse'. If int, the p-norm is calculated where p is specifined by the int input. @@ -161,6 +162,9 @@ class PINN(object): def span_pts(self, *args, **kwargs): """ + Generate a set of points to span the `Location` of all the conditions of + the problem. + >>> pinn.span_pts(n=10, mode='grid') >>> pinn.span_pts(n=10, mode='grid', location=['bound1']) >>> pinn.span_pts(n=10, mode='grid', variables=['x']) diff --git a/pina/plotter.py b/pina/plotter.py index a94ce72..39102b9 100644 --- a/pina/plotter.py +++ b/pina/plotter.py @@ -1,14 +1,9 @@ """ Module for plotting. """ -import matplotlib -# matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt import numpy as np import torch from pina import LabelTensor -from pina import PINN -from .problem import SpatialProblem, TimeDependentProblem -#from pina.tdproblem1d import TimeDepProblem1D class Plotter: @@ -20,11 +15,14 @@ class Plotter: """ Plot a sample of solution. - :param pinn: the PINN object. - :type pinn: PINN - :param variables: pinn variable domains: spatial or temporal, - defaults to None. - :type variables: str, optional + :param PINN pinn: the PINN object. + :param list(str) variables: variables to plot. If None, all variables + are plotted. If 'spatial', only spatial variables are plotted. If + 'temporal', only temporal variables are plotted. Defaults to None. + + .. todo:: + - Add support for 3D plots. + - Fix support for more complex problems. :Example: >>> plotter = Plotter() @@ -134,24 +132,22 @@ class Plotter: def plot(self, pinn, components=None, fixed_variables={}, method='contourf', res=256, filename=None, **kwargs): """ - Plot sample of PINN output. + Plot sample of PINN output. - :param pinn: the PINN object. - :type pinn: PINN - :param components: function components to plot, defaults to None. - :type components: list['str'], optional - :param fixed_variables: function variables to be kept fixed during - plotting passed as a dict where the dict-key is the variable - and the dict-value is the value to be kept fixed, defaults to {}. - :type fixed_variables: dict, optional - :param method: matplotlib method to plot the solution, - defaults to 'contourf'. - :type method: str, optional - :param res: number of points used for plotting in each axis, - defaults to 256. - :type res: int, optional - :param filename: file name to save the plot, defaults to None - :type filename: str, optional + :param PINN pinn: the PINN object. + :param list(str) components: the output variable to plot. If None, all + the output variables of the problem are selected. Default value is + None. + :param dict fixed_variables: a dictionary with all the variables that + should be kept fixed during the plot. The keys of the dictionary + are the variables name whereas the values are the corresponding + values of the variables. Defaults is `dict()`. + :param {'contourf', 'pcolor'} method: the matplotlib method to use for + plotting the solution. Default is 'contourf'. + :param int res: the resolution, aka the number of points used for + plotting in each axis. Default is 256. + :param str filename: the file name to save the plot. If None, the plot + is shown using the setted matplotlib frontend. Default is None. """ if components is None: components = [pinn.problem.output_variables] @@ -192,14 +188,12 @@ class Plotter: def plot_loss(self, pinn, label=None, log_scale=True): """ - Plot the loss function values during traininig. + Plot the loss function values during traininig. - :param pinn: the PINN object. - :type pinn: PINN - :param label: matplolib label, defaults to None - :type label: str, optional - :param log_scale: use of log scale in plotting, defaults to True. - :type log_scale: bool, optional + :param PINN pinn: the PINN object. + :param str label: the label to use in the legend, defaults to None. + :param bool log_scale: If True, the y axis is in log scale. Default is + True. """ if not label: diff --git a/pina/problem/parametric_problem.py b/pina/problem/parametric_problem.py index 430ee32..7e1d5a2 100644 --- a/pina/problem/parametric_problem.py +++ b/pina/problem/parametric_problem.py @@ -18,24 +18,24 @@ class ParametricProblem(AbstractProblem): >>> from pina.operators import grad >>> from pina import Condition, Span >>> import torch - + >>> >>> class ParametricODE(SpatialProblem, ParametricProblem): - + >>> >>> output_variables = ['u'] >>> spatial_domain = Span({'x': [0, 1]}) >>> parameter_domain = Span({'alpha': {1, 10}}) - + >>> >>> def ode_equation(input_, output_): >>> u_x = grad(output_, input_, components=['u'], d=['x']) >>> u = output_.extract(['u']) >>> alpha = input_.extract(['alpha']) >>> return alpha * u_x - u - + >>> >>> def initial_condition(input_, output_): >>> value = 1.0 >>> u = output_.extract(['u']) >>> return u - value - + >>> >>> conditions = { >>> 'x0': Condition(Span({'x': 0, 'alpha':[1, 10]}), initial_condition), >>> 'D': Condition(Span({'x': [0, 1], 'alpha':[1, 10]}), ode_equation)} diff --git a/pina/problem/spatial_problem.py b/pina/problem/spatial_problem.py index be3d607..001373b 100644 --- a/pina/problem/spatial_problem.py +++ b/pina/problem/spatial_problem.py @@ -16,22 +16,19 @@ class SpatialProblem(AbstractProblem): >>> from pina.operators import grad >>> from pina import Condition, Span >>> import torch - >>> class SimpleODE(SpatialProblem): - >>> output_variables = ['u'] >>> spatial_domain = Span({'x': [0, 1]}) - >>> def ode_equation(input_, output_): >>> u_x = grad(output_, input_, components=['u'], d=['x']) >>> u = output_.extract(['u']) >>> return u_x - u - + >>> >>> def initial_condition(input_, output_): >>> value = 1.0 >>> u = output_.extract(['u']) >>> return u - value - + >>> >>> conditions = { >>> 'x0': Condition(Span({'x': 0.}), initial_condition), >>> 'D': Condition(Span({'x': [0, 1]}), ode_equation)} diff --git a/pina/problem/timedep_problem.py b/pina/problem/timedep_problem.py index 8870204..36f2a30 100644 --- a/pina/problem/timedep_problem.py +++ b/pina/problem/timedep_problem.py @@ -16,29 +16,29 @@ class TimeDependentProblem(AbstractProblem): >>> from pina.operators import grad, nabla >>> from pina import Condition, Span >>> import torch - + >>> >>> class Wave(TimeDependentSpatialProblem): - + >>> >>> output_variables = ['u'] >>> spatial_domain = Span({'x': [0, 3]}) >>> temporal_domain = Span({'t': [0, 1]}) - + >>> >>> def wave_equation(input_, output_): >>> u_t = grad(output_, input_, components=['u'], d=['t']) >>> u_tt = grad(u_t, input_, components=['dudt'], d=['t']) >>> nabla_u = nabla(output_, input_, components=['u'], d=['x']) >>> return nabla_u - u_tt - + >>> >>> def nil_dirichlet(input_, output_): >>> value = 0.0 >>> return output_.extract(['u']) - value - + >>> >>> def initial_condition(input_, output_): >>> u_expected = (-3*torch.sin(2*torch.pi*input_.extract(['x'])) >>> + 5*torch.sin(8/3*torch.pi*input_.extract(['x']))) >>> u = output_.extract(['u']) >>> return u - u_expected - + >>> >>> conditions = { >>> 't0': Condition(Span({'x': [0, 3], 't':0}), initial_condition), >>> 'gamma1': Condition(Span({'x':0, 't':[0, 1]}), nil_dirichlet), diff --git a/tests/test_condition.py b/tests/test_condition.py index 56ae665..db3b828 100644 --- a/tests/test_condition.py +++ b/tests/test_condition.py @@ -39,4 +39,4 @@ def test_init_inputfunc(): with pytest.raises(TypeError): Condition(input_points=3., function='example') with pytest.raises(TypeError): - Condition(input_points=example_domain, funtion=example_output_pts) \ No newline at end of file + Condition(input_points=example_domain, function=example_output_pts) \ No newline at end of file diff --git a/tests/test_pinn.py b/tests/test_pinn.py index 12714bb..0935c37 100644 --- a/tests/test_pinn.py +++ b/tests/test_pinn.py @@ -24,12 +24,24 @@ class Poisson(SpatialProblem): return output_.extract(['u']) - value conditions = { - 'gamma1': Condition(Span({'x': [0, 1], 'y': 1}), nil_dirichlet), - 'gamma2': Condition(Span({'x': [0, 1], 'y': 0}), nil_dirichlet), - 'gamma3': Condition(Span({'x': 1, 'y': [0, 1]}), nil_dirichlet), - 'gamma4': Condition(Span({'x': 0, 'y': [0, 1]}), nil_dirichlet), - 'D': Condition(Span({'x': [0, 1], 'y': [0, 1]}), laplace_equation), - 'data': Condition(in_, out_) + 'gamma1': Condition( + location=Span({'x': [0, 1], 'y': 1}), + function=nil_dirichlet), + 'gamma2': Condition( + location=Span({'x': [0, 1], 'y': 0}), + function=nil_dirichlet), + 'gamma3': Condition( + location=Span({'x': 1, 'y': [0, 1]}), + function=nil_dirichlet), + 'gamma4': Condition( + location=Span({'x': 0, 'y': [0, 1]}), + function=nil_dirichlet), + 'D': Condition( + location=Span({'x': [0, 1], 'y': [0, 1]}), + function=laplace_equation), + 'data': Condition( + input_points=in_, + output_points=out_) } def poisson_sol(self, pts):