From 311b4c82cf88411a4c7db3404decd827b6f386c6 Mon Sep 17 00:00:00 2001 From: ndem0 Date: Fri, 1 Mar 2024 02:44:06 +0000 Subject: [PATCH] deploy: ee5988cd3180bb774d7ea4daebee8b950a4904db --- .buildinfo | 2 +- _LICENSE.html | 14 +- _cite.html | 14 +- _images/tutorial_19_0.png | Bin 0 -> 135579 bytes _images/tutorial_5_1.png | Bin 0 -> 27909 bytes _modules/index.html | 15 +- .../adaptive_refinment_callbacks.html | 43 +- .../pina/callbacks/optimizer_callbacks.html | 41 +- .../pina/callbacks/processing_callbacks.html | 23 +- _modules/pina/condition.html | 54 ++- _modules/pina/equation/equation.html | 25 +- _modules/pina/equation/equation_factory.html | 15 +- .../pina/equation/equation_interface.html | 15 +- _modules/pina/equation/system_equation.html | 34 +- _modules/pina/geometry/cartesian.html | 58 +-- _modules/pina/geometry/difference_domain.html | 25 +- _modules/pina/geometry/ellipsoid.html | 52 ++- _modules/pina/geometry/exclusion_domain.html | 23 +- .../pina/geometry/intersection_domain.html | 25 +- _modules/pina/geometry/location.html | 14 +- .../pina/geometry/operation_interface.html | 25 +- _modules/pina/geometry/simplex.html | 46 +- _modules/pina/geometry/union_domain.html | 24 +- _modules/pina/label_tensor.html | 85 ++-- _modules/pina/loss.html | 32 +- _modules/pina/model/base_no.html | 260 ++++++++++++ _modules/pina/model/deeponet.html | 104 +++-- _modules/pina/model/feed_forward.html | 106 +++-- _modules/pina/model/fno.html | 252 +++++++---- .../pina/model/layers/convolution_2d.html | 149 ++++--- _modules/pina/model/layers/fourier.html | 87 ++-- _modules/pina/model/layers/residual.html | 36 +- _modules/pina/model/layers/spectral.html | 234 +++++++---- _modules/pina/model/multi_feed_forward.html | 17 +- _modules/pina/model/network.html | 26 +- _modules/pina/operators.html | 79 ++-- _modules/pina/plotter.html | 235 ++++++----- _modules/pina/problem/abstract_problem.html | 94 +++-- _modules/pina/problem/parametric_problem.html | 15 +- _modules/pina/problem/spatial_problem.html | 15 +- _modules/pina/problem/timedep_problem.html | 15 +- _modules/pina/solvers/garom.html | 134 ++++-- _modules/pina/solvers/pinn.html | 107 +++-- _modules/pina/solvers/solver.html | 78 ++-- _modules/pina/solvers/supervised.html | 80 ++-- _modules/pina/trainer.html | 43 +- _rst/_code.html | 18 +- _rst/_contributing.html | 14 +- _rst/_installation.html | 14 +- _rst/_tutorial.html | 16 +- .../adaptive_refinment_callbacks.html | 14 +- _rst/callbacks/optimizer_callbacks.html | 16 +- _rst/callbacks/processing_callbacks.html | 14 +- _rst/condition.html | 14 +- _rst/equations.html | 14 +- _rst/geometry/cartesian.html | 14 +- _rst/geometry/difference_domain.html | 16 +- _rst/geometry/ellipsoid.html | 14 +- _rst/geometry/exclusion_domain.html | 16 +- _rst/geometry/intersection_domain.html | 16 +- _rst/geometry/location.html | 16 +- _rst/geometry/operation_interface.html | 16 +- _rst/geometry/simplex.html | 14 +- _rst/geometry/union_domain.html | 14 +- _rst/label_tensor.html | 28 +- _rst/layers/convolution.html | 28 +- _rst/layers/enhanced_linear.html | 28 +- _rst/layers/fourier.html | 38 +- _rst/layers/residual.html | 22 +- _rst/layers/spectral.html | 32 +- _rst/loss/loss_interface.html | 20 +- _rst/loss/lploss.html | 20 +- _rst/loss/powerloss.html | 20 +- _rst/models/base_no.html | 250 +++++++++++ _rst/models/deeponet.html | 30 +- _rst/models/fnn.html | 28 +- _rst/models/fnn_residual.html | 24 +- _rst/models/fno.html | 76 ++-- _rst/models/fourier_kernel.html | 216 ++++++++++ _rst/models/mionet.html | 30 +- _rst/models/multifeedforward.html | 20 +- _rst/models/network.html | 30 +- _rst/operators.html | 14 +- _rst/plotter.html | 26 +- _rst/problem/abstractproblem.html | 14 +- _rst/problem/parametricproblem.html | 14 +- _rst/problem/spatialproblem.html | 14 +- _rst/problem/timedepproblem.html | 14 +- _rst/solvers/garom.html | 28 +- _rst/solvers/pinn.html | 30 +- _rst/solvers/solver_interface.html | 45 +- _rst/solvers/supervised.html | 30 +- _rst/trainer.html | 14 +- _rst/tutorials/tutorial1/tutorial.html | 14 +- _rst/tutorials/tutorial2/tutorial.html | 14 +- _rst/tutorials/tutorial3/tutorial.html | 14 +- _rst/tutorials/tutorial4/tutorial.html | 19 +- _rst/tutorials/tutorial5/tutorial.html | 14 +- _rst/tutorials/tutorial6/tutorial.html | 14 +- _rst/tutorials/tutorial7/tutorial.html | 14 +- _rst/tutorials/tutorial8/tutorial.html | 395 ++++++++++++++++++ _sources/_rst/_code.rst.txt | 2 + _sources/_rst/_tutorial.rst.txt | 2 +- _sources/_rst/models/base_no.rst.txt | 7 + _sources/_rst/models/fourier_kernel.rst.txt | 7 + .../_rst/tutorials/tutorial8/tutorial.rst.txt | 299 +++++++++++++ _static/documentation_options.js | 2 +- _team.html | 18 +- genindex.html | 39 +- index.html | 14 +- objects.inv | Bin 2661 -> 2809 bytes py-modindex.html | 14 +- search.html | 14 +- searchindex.js | 2 +- 114 files changed, 3696 insertions(+), 1545 deletions(-) create mode 100644 _images/tutorial_19_0.png create mode 100644 _images/tutorial_5_1.png create mode 100644 _modules/pina/model/base_no.html create mode 100644 _rst/models/base_no.html create mode 100644 _rst/models/fourier_kernel.html create mode 100644 _rst/tutorials/tutorial8/tutorial.html create mode 100644 _sources/_rst/models/base_no.rst.txt create mode 100644 _sources/_rst/models/fourier_kernel.rst.txt create mode 100644 _sources/_rst/tutorials/tutorial8/tutorial.rst.txt diff --git a/.buildinfo b/.buildinfo index 5fee18e6..b73df63f 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: a765572ea3a7b0e12f8c5177dc95c950 +config: e773860857a5b1a8ff889f437af7e263 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/_LICENSE.html b/_LICENSE.html index af5276e7..7eef76bb 100644 --- a/_LICENSE.html +++ b/_LICENSE.html @@ -1,11 +1,13 @@ - + - License — PINA 0.1 documentation + License — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
- 0.1 + 0.1.0.post2403
@@ -120,8 +122,8 @@

License -

© Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

© Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

diff --git a/_cite.html b/_cite.html index 1c970b41..bcdcd7bb 100644 --- a/_cite.html +++ b/_cite.html @@ -1,11 +1,13 @@ - + - Cite PINA — PINA 0.1 documentation + Cite PINA — PINA 0.1.0.post2403 documentation - + + + @@ -36,7 +38,7 @@
- 0.1 + 0.1.0.post2403
@@ -116,8 +118,8 @@

Cite PINA -

© Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

© Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

diff --git a/_images/tutorial_19_0.png b/_images/tutorial_19_0.png new file mode 100644 index 0000000000000000000000000000000000000000..81485c718e53b26919bbdecb751fdd1ff6515114 GIT binary patch literal 135579 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 literal 0 HcmV?d00001 diff --git a/_images/tutorial_5_1.png b/_images/tutorial_5_1.png new file mode 100644 index 0000000000000000000000000000000000000000..00ee2a0018955ca7cf6735232a86349bfd7d1c67 GIT binary patch literal 27909 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
- 0.1 + 0.1.0.post2403
@@ -105,6 +107,7 @@

All modules for which code is available

  • pina.geometry.union_domain
  • pina.label_tensor
  • pina.loss
  • +
  • pina.model.base_no
  • pina.model.deeponet
  • pina.model.feed_forward
  • pina.model.fno
  • @@ -134,8 +137,8 @@

    All modules for which code is available


    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/callbacks/adaptive_refinment_callbacks.html b/_modules/pina/callbacks/adaptive_refinment_callbacks.html index 454131ff..8df26ef7 100644 --- a/_modules/pina/callbacks/adaptive_refinment_callbacks.html +++ b/_modules/pina/callbacks/adaptive_refinment_callbacks.html @@ -1,11 +1,13 @@ - + - pina.callbacks.adaptive_refinment_callbacks — PINA 0.1 documentation + pina.callbacks.adaptive_refinment_callbacks — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -87,7 +89,7 @@

    Source code for pina.callbacks.adaptive_refinment_callbacks

    -'''PINA Callbacks Implementations'''
    +"""PINA Callbacks Implementations"""
     
     # from lightning.pytorch.callbacks import Callback
     from pytorch_lightning.callbacks import Callback
    @@ -97,18 +99,17 @@ 

    Source code for pina.callbacks.adaptive_refinment_callbacks

    [docs]class R3Refinement(Callback): - def __init__(self, sample_every): """ PINA Implementation of an R3 Refinement Callback. This callback implements the R3 (Retain-Resample-Release) routine for sampling new points based on adaptive search. - The algorithm incrementally accumulates collocation points in regions of high PDE residuals, and releases those + The algorithm incrementally accumulates collocation points in regions of high PDE residuals, and releases those with low residuals. Points are sampled uniformly in all regions where sampling is needed. .. seealso:: - Original Reference: Daw, Arka, et al. *Mitigating Propagation Failures in Physics-informed Neural Networks + Original Reference: Daw, Arka, et al. *Mitigating Propagation Failures in Physics-informed Neural Networks using Retain-Resample-Release (R3) Sampling. (2023)*. DOI: `10.48550/arXiv.2207.02338 <https://doi.org/10.48550/arXiv.2207.02338>`_ @@ -168,7 +169,7 @@

    Source code for pina.callbacks.adaptive_refinment_callbacks

    # !!!!!! From now everything is performed on CPU !!!!!! # average loss - avg = (tot_loss.mean()).to('cpu') + avg = (tot_loss.mean()).to("cpu") # points to keep old_pts = {} @@ -179,25 +180,29 @@

    Source code for pina.callbacks.adaptive_refinment_callbacks

    pts = pts.cpu().detach() residuals = res_loss[location].cpu() mask = (residuals > avg).flatten() - if any(mask): # if there are residuals greater than averge we append them - pts = pts[mask] # TODO masking remove labels + if any( + mask + ): # if there are residuals greater than averge we append them + pts = pts[mask] # TODO masking remove labels pts.labels = labels old_pts[location] = pts tot_points += len(pts) # extract new points to sample uniformally for each location n_points = (self._tot_pop_numb - tot_points) // len( - self._sampling_locations) + self._sampling_locations + ) remainder = (self._tot_pop_numb - tot_points) % len( - self._sampling_locations) + self._sampling_locations + ) n_uniform_points = [n_points] * len(self._sampling_locations) n_uniform_points[-1] += remainder # sample new points for numb_pts, loc in zip(n_uniform_points, self._sampling_locations): - trainer._model.problem.discretise_domain(numb_pts, - 'random', - locations=[loc]) + trainer._model.problem.discretise_domain( + numb_pts, "random", locations=[loc] + ) # adding previous population points trainer._model.problem.add_points(old_pts) @@ -222,7 +227,7 @@

    Source code for pina.callbacks.adaptive_refinment_callbacks

    locations = [] for condition_name in problem.conditions: condition = problem.conditions[condition_name] - if hasattr(condition, 'location'): + if hasattr(condition, "location"): locations.append(condition_name) self._sampling_locations = locations @@ -257,8 +262,8 @@

    Source code for pina.callbacks.adaptive_refinment_callbacks

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/callbacks/optimizer_callbacks.html b/_modules/pina/callbacks/optimizer_callbacks.html index 882e319e..dd9fcc85 100644 --- a/_modules/pina/callbacks/optimizer_callbacks.html +++ b/_modules/pina/callbacks/optimizer_callbacks.html @@ -1,11 +1,13 @@ - + - pina.callbacks.optimizer_callbacks — PINA 0.1 documentation + pina.callbacks.optimizer_callbacks — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -87,7 +89,7 @@

    Source code for pina.callbacks.optimizer_callbacks

    -'''PINA Callbacks Implementations'''
    +"""PINA Callbacks Implementations"""
     
     from pytorch_lightning.callbacks import Callback
     import torch
    @@ -103,7 +105,7 @@ 

    Source code for pina.callbacks.optimizer_callbacks

    This callback allows for switching between different optimizers during training, enabling the exploration of multiple optimization strategies without the need to stop training. - :param new_optimizers: The model optimizers to switch to. Can be a single + :param new_optimizers: The model optimizers to switch to. Can be a single :class:`torch.optim.Optimizer` or a list of them for multiple model solvers. :type new_optimizers: torch.optim.Optimizer | list :param new_optimizers_kwargs: The keyword arguments for the new optimizers. Can be a single dictionary @@ -112,7 +114,7 @@

    Source code for pina.callbacks.optimizer_callbacks

    :param epoch_switch: The epoch at which to switch to the new optimizer. :type epoch_switch: int - :raises ValueError: If `epoch_switch` is less than 1 or if there is a mismatch in the number of + :raises ValueError: If `epoch_switch` is less than 1 or if there is a mismatch in the number of optimizers and their corresponding keyword argument dictionaries. Example: @@ -128,7 +130,7 @@

    Source code for pina.callbacks.optimizer_callbacks

    check_consistency(epoch_switch, int) if epoch_switch < 1: - raise ValueError('epoch_switch must be greater than one.') + raise ValueError("epoch_switch must be greater than one.") if not isinstance(new_optimizers, list): new_optimizers = [new_optimizers] @@ -137,10 +139,12 @@

    Source code for pina.callbacks.optimizer_callbacks

    len_optimizer_kwargs = len(new_optimizers_kwargs) if len_optimizer_kwargs != len_optimizer: - raise ValueError('You must define one dictionary of keyword' - ' arguments for each optimizers.' - f' Got {len_optimizer} optimizers, and' - f' {len_optimizer_kwargs} dicitionaries') + raise ValueError( + "You must define one dictionary of keyword" + " arguments for each optimizers." + f" Got {len_optimizer} optimizers, and" + f" {len_optimizer_kwargs} dicitionaries" + ) # save new optimizers self._new_optimizers = new_optimizers @@ -161,10 +165,13 @@

    Source code for pina.callbacks.optimizer_callbacks

    if trainer.current_epoch == self._epoch_switch: optims = [] for idx, (optim, optim_kwargs) in enumerate( - zip(self._new_optimizers, self._new_optimizers_kwargs)): + zip(self._new_optimizers, self._new_optimizers_kwargs) + ): optims.append( - optim(trainer._model.models[idx].parameters(), - **optim_kwargs)) + optim( + trainer._model.models[idx].parameters(), **optim_kwargs + ) + ) trainer.optimizers = optims
    @@ -176,8 +183,8 @@

    Source code for pina.callbacks.optimizer_callbacks

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/callbacks/processing_callbacks.html b/_modules/pina/callbacks/processing_callbacks.html index 5351977e..826a16b6 100644 --- a/_modules/pina/callbacks/processing_callbacks.html +++ b/_modules/pina/callbacks/processing_callbacks.html @@ -1,11 +1,13 @@ - + - pina.callbacks.processing_callbacks — PINA 0.1 documentation + pina.callbacks.processing_callbacks — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -87,7 +89,7 @@

    Source code for pina.callbacks.processing_callbacks

    -'''PINA Callbacks Implementations'''
    +"""PINA Callbacks Implementations"""
     
     from pytorch_lightning.callbacks import Callback
     import torch
    @@ -95,7 +97,7 @@ 

    Source code for pina.callbacks.processing_callbacks

    [docs]class MetricTracker(Callback): - + def __init__(self): """ PINA Implementation of a Lightning Callback for Metric Tracking. @@ -128,8 +130,9 @@

    Source code for pina.callbacks.processing_callbacks

    :return: None :rtype: None """ - self._collection.append(copy.deepcopy( - trainer.logged_metrics)) # track them
    + self._collection.append( + copy.deepcopy(trainer.logged_metrics) + ) # track them
    @property def metrics(self): @@ -154,8 +157,8 @@

    Source code for pina.callbacks.processing_callbacks

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/condition.html b/_modules/pina/condition.html index 57c4f72d..119be3ae 100644 --- a/_modules/pina/condition.html +++ b/_modules/pina/condition.html @@ -1,11 +1,13 @@ - + - pina.condition — PINA 0.1 documentation + pina.condition — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.condition

     """ Condition module. """
    +
     from .label_tensor import LabelTensor
     from .geometry import Location
     from .equation.equation import Equation
    @@ -140,7 +143,11 @@ 

    Source code for pina.condition

         """
     
         __slots__ = [
    -        'input_points', 'output_points', 'location', 'equation', 'data_weight'
    +        "input_points",
    +        "output_points",
    +        "location",
    +        "equation",
    +        "data_weight",
         ]
     
         def _dictvalue_isinstance(self, dict_, key_, class_):
    @@ -154,27 +161,28 @@ 

    Source code for pina.condition

             """
             Constructor for the `Condition` class.
             """
    -        self.data_weight = kwargs.pop('data_weight', 1.0)
    +        self.data_weight = kwargs.pop("data_weight", 1.0)
     
             if len(args) != 0:
                 raise ValueError(
    -                'Condition takes only the following keyword arguments: {`input_points`, `output_points`, `location`, `function`, `data_weight`}.'
    +                f"Condition takes only the following keyword arguments: {Condition.__slots__}."
                 )
     
    -        if (sorted(kwargs.keys()) != sorted(['input_points', 'output_points'])
    -                and sorted(kwargs.keys()) != sorted(['location', 'equation'])
    -                and sorted(kwargs.keys()) != sorted(
    -                    ['input_points', 'equation'])):
    -            raise ValueError(f'Invalid keyword arguments {kwargs.keys()}.')
    -
    -        if not self._dictvalue_isinstance(kwargs, 'input_points', LabelTensor):
    -            raise TypeError('`input_points` must be a torch.Tensor.')
    -        if not self._dictvalue_isinstance(kwargs, 'output_points', LabelTensor):
    -            raise TypeError('`output_points` must be a torch.Tensor.')
    -        if not self._dictvalue_isinstance(kwargs, 'location', Location):
    -            raise TypeError('`location` must be a Location.')
    -        if not self._dictvalue_isinstance(kwargs, 'equation', Equation):
    -            raise TypeError('`equation` must be a Equation.')
    +        if (
    +            sorted(kwargs.keys()) != sorted(["input_points", "output_points"])
    +            and sorted(kwargs.keys()) != sorted(["location", "equation"])
    +            and sorted(kwargs.keys()) != sorted(["input_points", "equation"])
    +        ):
    +            raise ValueError(f"Invalid keyword arguments {kwargs.keys()}.")
    +
    +        if not self._dictvalue_isinstance(kwargs, "input_points", LabelTensor):
    +            raise TypeError("`input_points` must be a torch.Tensor.")
    +        if not self._dictvalue_isinstance(kwargs, "output_points", LabelTensor):
    +            raise TypeError("`output_points` must be a torch.Tensor.")
    +        if not self._dictvalue_isinstance(kwargs, "location", Location):
    +            raise TypeError("`location` must be a Location.")
    +        if not self._dictvalue_isinstance(kwargs, "equation", Equation):
    +            raise TypeError("`equation` must be a Equation.")
     
             for key, value in kwargs.items():
                 setattr(self, key, value)
    @@ -187,8 +195,8 @@

    Source code for pina.condition

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/equation/equation.html b/_modules/pina/equation/equation.html index 4e572d41..cde26391 100644 --- a/_modules/pina/equation/equation.html +++ b/_modules/pina/equation/equation.html @@ -1,11 +1,13 @@ - + - pina.equation.equation — PINA 0.1 documentation + pina.equation.equation — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.equation.equation

     """ Module for Equation. """
    +
     from .equation_interface import EquationInterface
     
     
    @@ -104,12 +107,14 @@ 

    Source code for pina.equation.equation

             :type equation: Callable
             """
             if not callable(equation):
    -            raise ValueError('equation must be a callable function.'
    -                             'Expected a callable function, got '
    -                             f'{equation}')
    +            raise ValueError(
    +                "equation must be a callable function."
    +                "Expected a callable function, got "
    +                f"{equation}"
    +            )
             self.__equation = equation
     
    -
    [docs] def residual(self, input_, output_, params_ = None): +
    [docs] def residual(self, input_, output_, params_=None): """ Residual computation of the equation. @@ -141,8 +146,8 @@

    Source code for pina.equation.equation

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/equation/equation_factory.html b/_modules/pina/equation/equation_factory.html index 0b388fb8..01132fc1 100644 --- a/_modules/pina/equation/equation_factory.html +++ b/_modules/pina/equation/equation_factory.html @@ -1,11 +1,13 @@ - + - pina.equation.equation_factory — PINA 0.1 documentation + pina.equation.equation_factory — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.equation.equation_factory

     """ Module """
    +
     from .equation import Equation
     from ..operators import grad, div, laplacian
     
    @@ -200,8 +203,8 @@ 

    Source code for pina.equation.equation_factory

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/equation/equation_interface.html b/_modules/pina/equation/equation_interface.html index 5e04794c..d2929263 100644 --- a/_modules/pina/equation/equation_interface.html +++ b/_modules/pina/equation/equation_interface.html @@ -1,11 +1,13 @@ - + - pina.equation.equation_interface — PINA 0.1 documentation + pina.equation.equation_interface — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.equation.equation_interface

     """ Module for EquationInterface class """
    +
     from abc import ABCMeta, abstractmethod
     
     
    @@ -123,8 +126,8 @@ 

    Source code for pina.equation.equation_interface


    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/equation/system_equation.html b/_modules/pina/equation/system_equation.html index 5a3ef98b..81bcf26d 100644 --- a/_modules/pina/equation/system_equation.html +++ b/_modules/pina/equation/system_equation.html @@ -1,11 +1,13 @@ - + - pina.equation.system_equation — PINA 0.1 documentation + pina.equation.system_equation — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.equation.system_equation

     """ Module for SystemEquation. """
    +
     import torch
     from .equation import Equation
     from ..utils import check_consistency
    @@ -95,7 +98,7 @@ 

    Source code for pina.equation.system_equation

    [docs]class SystemEquation(Equation): - def __init__(self, list_equation, reduction='mean'): + def __init__(self, list_equation, reduction="mean"): """ System of Equation class for specifing any system of equations in PINA. @@ -122,15 +125,16 @@

    Source code for pina.equation.system_equation

    self.equations.append(Equation(equation)) # possible reduction - if reduction == 'mean': + if reduction == "mean": self.reduction = torch.mean - elif reduction == 'sum': + elif reduction == "sum": self.reduction = torch.sum - elif (reduction == 'none') or callable(reduction): + elif (reduction == "none") or callable(reduction): self.reduction = reduction else: raise NotImplementedError( - 'Only mean and sum reductions implemented.') + "Only mean and sum reductions implemented." + )
    [docs] def residual(self, input_, output_, params_=None): """ @@ -153,9 +157,13 @@

    Source code for pina.equation.system_equation

    :rtype: LabelTensor """ residual = torch.hstack( - [equation.residual(input_, output_, params_) for equation in self.equations]) + [ + equation.residual(input_, output_, params_) + for equation in self.equations + ] + ) - if self.reduction == 'none': + if self.reduction == "none": return residual return self.reduction(residual, dim=-1)
    @@ -168,8 +176,8 @@

    Source code for pina.equation.system_equation

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/cartesian.html b/_modules/pina/geometry/cartesian.html index 5b308466..893c3906 100644 --- a/_modules/pina/geometry/cartesian.html +++ b/_modules/pina/geometry/cartesian.html @@ -1,11 +1,13 @@ - + - pina.geometry.cartesian — PINA 0.1 documentation + pina.geometry.cartesian — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -161,17 +163,17 @@

    Source code for pina.geometry.cartesian

             :rtype: torch.Tensor
             """
             dim = bounds.shape[0]
    -        if mode in ['chebyshev', 'grid'] and dim != 1:
    -            raise RuntimeError('Something wrong in Span...')
    +        if mode in ["chebyshev", "grid"] and dim != 1:
    +            raise RuntimeError("Something wrong in Span...")
     
    -        if mode == 'random':
    +        if mode == "random":
                 pts = torch.rand(size=(n, dim))
    -        elif mode == 'chebyshev':
    -            pts = chebyshev_roots(n).mul(.5).add(.5).reshape(-1, 1)
    -        elif mode == 'grid':
    +        elif mode == "chebyshev":
    +            pts = chebyshev_roots(n).mul(0.5).add(0.5).reshape(-1, 1)
    +        elif mode == "grid":
                 pts = torch.linspace(0, 1, n).reshape(-1, 1)
             # elif mode == 'lh' or mode == 'latin':
    -        elif mode in ['lh', 'latin']:
    +        elif mode in ["lh", "latin"]:
                 pts = torch_lhs(n, dim)
     
             pts *= bounds[:, 1] - bounds[:, 0]
    @@ -179,7 +181,7 @@ 

    Source code for pina.geometry.cartesian

     
             return pts
     
    -
    [docs] def sample(self, n, mode='random', variables='all'): +
    [docs] def sample(self, n, mode="random", variables="all"): """Sample routine. :param n: Number of points to sample, see Note below @@ -234,7 +236,7 @@

    Source code for pina.geometry.cartesian

             """
     
             def _1d_sampler(n, mode, variables):
    -            """ Sample independentely the variables and cross the results"""
    +            """Sample independentely the variables and cross the results"""
                 tmp = []
                 for variable in variables:
                     if variable in self.range_.keys():
    @@ -247,17 +249,18 @@ 

    Source code for pina.geometry.cartesian

     
                 result = tmp[0]
                 for i in tmp[1:]:
    -                result = result.append(i, mode='cross')
    +                result = result.append(i, mode="cross")
     
                 for variable in variables:
                     if variable in self.fixed_.keys():
                         value = self.fixed_[variable]
    -                    pts_variable = torch.tensor([[value]
    -                                                 ]).repeat(result.shape[0], 1)
    +                    pts_variable = torch.tensor([[value]]).repeat(
    +                        result.shape[0], 1
    +                    )
                         pts_variable = pts_variable.as_subclass(LabelTensor)
                         pts_variable.labels = [variable]
     
    -                    result = result.append(pts_variable, mode='std')
    +                    result = result.append(pts_variable, mode="std")
     
                 return result
     
    @@ -286,12 +289,13 @@ 

    Source code for pina.geometry.cartesian

                 for variable in variables:
                     if variable in self.fixed_.keys():
                         value = self.fixed_[variable]
    -                    pts_variable = torch.tensor([[value]
    -                                                 ]).repeat(result.shape[0], 1)
    +                    pts_variable = torch.tensor([[value]]).repeat(
    +                        result.shape[0], 1
    +                    )
                         pts_variable = pts_variable.as_subclass(LabelTensor)
                         pts_variable.labels = [variable]
     
    -                    result = result.append(pts_variable, mode='std')
    +                    result = result.append(pts_variable, mode="std")
                 return result
     
             def _single_points_sample(n, variables):
    @@ -315,22 +319,22 @@ 

    Source code for pina.geometry.cartesian

     
                 result = tmp[0]
                 for i in tmp[1:]:
    -                result = result.append(i, mode='std')
    +                result = result.append(i, mode="std")
     
                 return result
     
             if self.fixed_ and (not self.range_):
                 return _single_points_sample(n, variables)
     
    -        if variables == 'all':
    +        if variables == "all":
                 variables = list(self.range_.keys()) + list(self.fixed_.keys())
     
    -        if mode in ['grid', 'chebyshev']:
    +        if mode in ["grid", "chebyshev"]:
                 return _1d_sampler(n, mode, variables)
    -        elif mode in ['random', 'lh', 'latin']:
    +        elif mode in ["random", "lh", "latin"]:
                 return _Nd_sampler(n, mode, variables)
             else:
    -            raise ValueError(f'mode={mode} is not valid.')
    + raise ValueError(f"mode={mode} is not valid.")
    [docs] def is_inside(self, point, check_border=False): """Check if a point is inside the ellipsoid. @@ -371,8 +375,8 @@

    Source code for pina.geometry.cartesian

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/difference_domain.html b/_modules/pina/geometry/difference_domain.html index c0b954d3..2a5e7ec9 100644 --- a/_modules/pina/geometry/difference_domain.html +++ b/_modules/pina/geometry/difference_domain.html @@ -1,11 +1,13 @@ - + - pina.geometry.difference_domain — PINA 0.1 documentation + pina.geometry.difference_domain — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -109,7 +111,7 @@

    Source code for pina.geometry.difference_domain

    < the dimension of the geometry space. :param list geometries: A list of geometries from ``pina.geometry`` - such as ``EllipsoidDomain`` or ``CartesianDomain``. The first + such as ``EllipsoidDomain`` or ``CartesianDomain``. The first geometry in the list is the geometry from which points are sampled. The rest of the geometries are the geometries that are excluded from the first geometry to find the difference. @@ -128,7 +130,7 @@

    Source code for pina.geometry.difference_domain

    < Check if a point is inside the ``Difference`` domain. :param point: Point to be checked. - :type point: torch.Tensor + :type point: torch.Tensor :param bool check_border: If ``True``, the border is considered inside. :return: ``True`` if the point is inside the Exclusion domain, ``False`` otherwise. :rtype: bool @@ -138,7 +140,7 @@

    Source code for pina.geometry.difference_domain

    < return False return self.geometries[0].is_inside(point, check_border)
    -
    [docs] def sample(self, n, mode='random', variables='all'): +
    [docs] def sample(self, n, mode="random", variables="all"): """ Sample routine for ``Difference`` domain. @@ -166,9 +168,10 @@

    Source code for pina.geometry.difference_domain

    < 5 """ - if mode != 'random': + if mode != "random": raise NotImplementedError( - f'{mode} is not a valid mode for sampling.') + f"{mode} is not a valid mode for sampling." + ) sampled = [] @@ -198,8 +201,8 @@

    Source code for pina.geometry.difference_domain

    <
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/ellipsoid.html b/_modules/pina/geometry/ellipsoid.html index f9d4f06e..30096819 100644 --- a/_modules/pina/geometry/ellipsoid.html +++ b/_modules/pina/geometry/ellipsoid.html @@ -1,11 +1,13 @@ - + - pina.geometry.ellipsoid — PINA 0.1 documentation + pina.geometry.ellipsoid — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -174,7 +176,7 @@

    Source code for pina.geometry.ellipsoid

     
             .. note::
                 When ``sample_surface`` in the ``__init()__``
    -            is set to ``True``, then the method only checks 
    +            is set to ``True``, then the method only checks
                 points on the surface, and not inside the domain.
     
             :param point: Point to be checked.
    @@ -192,7 +194,7 @@ 

    Source code for pina.geometry.ellipsoid

             # get axis ellipse as tensors
             list_dict_vals = list(self._axis.values())
             tmp = torch.tensor(list_dict_vals, dtype=torch.float)
    -        ax_sq = LabelTensor(tmp.reshape(1, -1)**2, self.variables)
    +        ax_sq = LabelTensor(tmp.reshape(1, -1) ** 2, self.variables)
     
             # get centers ellipse as tensors
             list_dict_vals = list(self._centers.values())
    @@ -200,16 +202,18 @@ 

    Source code for pina.geometry.ellipsoid

             centers = LabelTensor(tmp.reshape(1, -1), self.variables)
     
             if not all([i in ax_sq.labels for i in point.labels]):
    -            raise ValueError('point labels different from constructor'
    -                             f' dictionary labels. Got {point.labels},'
    -                             f' expected {ax_sq.labels}.')
    +            raise ValueError(
    +                "point labels different from constructor"
    +                f" dictionary labels. Got {point.labels},"
    +                f" expected {ax_sq.labels}."
    +            )
     
             # point square + shift center
             point_sq = (point - centers).pow(2)
             point_sq.labels = point.labels
     
             # calculate ellispoid equation
    -        eqn = torch.sum(point_sq.extract(ax_sq.labels) / ax_sq) - 1.
    +        eqn = torch.sum(point_sq.extract(ax_sq.labels) / ax_sq) - 1.0
     
             # if we have sampled only the surface, we check that the
             # point is inside the surface border only
    @@ -249,8 +253,9 @@ 

    Source code for pina.geometry.ellipsoid

             dim = len(variables)
     
             # get values center
    -        pairs_center = [(k, v) for k, v in self._centers.items()
    -                        if k in variables]
    +        pairs_center = [
    +            (k, v) for k, v in self._centers.items() if k in variables
    +        ]
             _, values_center = map(list, zip(*pairs_center))
             values_center = torch.tensor(values_center)
     
    @@ -260,7 +265,7 @@ 

    Source code for pina.geometry.ellipsoid

             values_axis = torch.tensor(values_axis)
     
             # Sample in the unit sphere
    -        if mode == 'random':
    +        if mode == "random":
                 # 1. Sample n points from the surface of a unit sphere
                 # 2. Scale each dimension using torch.rand()
                 #    (a random number between 0-1) so that it lies within
    @@ -281,7 +286,7 @@ 

    Source code for pina.geometry.ellipsoid

     
             return pts
     
    -
    [docs] def sample(self, n, mode='random', variables='all'): +
    [docs] def sample(self, n, mode="random", variables="all"): """Sample routine. :param int n: Number of points to sample in the shape. @@ -327,12 +332,13 @@

    Source code for pina.geometry.ellipsoid

                 for variable in variables:
                     if variable in self.fixed_.keys():
                         value = self.fixed_[variable]
    -                    pts_variable = torch.tensor([[value]
    -                                                 ]).repeat(result.shape[0], 1)
    +                    pts_variable = torch.tensor([[value]]).repeat(
    +                        result.shape[0], 1
    +                    )
                         pts_variable = pts_variable.as_subclass(LabelTensor)
                         pts_variable.labels = [variable]
     
    -                    result = result.append(pts_variable, mode='std')
    +                    result = result.append(pts_variable, mode="std")
                 return result
     
             def _single_points_sample(n, variables):
    @@ -356,20 +362,20 @@ 

    Source code for pina.geometry.ellipsoid

     
                 result = tmp[0]
                 for i in tmp[1:]:
    -                result = result.append(i, mode='std')
    +                result = result.append(i, mode="std")
     
                 return result
     
             if self.fixed_ and (not self.range_):
                 return _single_points_sample(n, variables)
     
    -        if variables == 'all':
    +        if variables == "all":
                 variables = list(self.range_.keys()) + list(self.fixed_.keys())
     
    -        if mode in ['random']:
    +        if mode in ["random"]:
                 return _Nd_sampler(n, mode, variables)
             else:
    -            raise NotImplementedError(f'mode={mode} is not implemented.')
    + raise NotImplementedError(f"mode={mode} is not implemented.")
    @@ -379,8 +385,8 @@

    Source code for pina.geometry.ellipsoid

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/exclusion_domain.html b/_modules/pina/geometry/exclusion_domain.html index d2093606..c43ad680 100644 --- a/_modules/pina/geometry/exclusion_domain.html +++ b/_modules/pina/geometry/exclusion_domain.html @@ -1,11 +1,13 @@ - + - pina.geometry.exclusion_domain — PINA 0.1 documentation + pina.geometry.exclusion_domain — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -126,7 +128,7 @@

    Source code for pina.geometry.exclusion_domain

    Check if a point is inside the ``Exclusion`` domain. :param point: Point to be checked. - :type point: torch.Tensor + :type point: torch.Tensor :param bool check_border: If ``True``, the border is considered inside. :return: ``True`` if the point is inside the Exclusion domain, ``False`` otherwise. :rtype: bool @@ -137,7 +139,7 @@

    Source code for pina.geometry.exclusion_domain

    flag += 1 return flag == 1

    -
    [docs] def sample(self, n, mode='random', variables='all'): +
    [docs] def sample(self, n, mode="random", variables="all"): """ Sample routine for ``Exclusion`` domain. @@ -165,9 +167,10 @@

    Source code for pina.geometry.exclusion_domain

    5 """ - if mode != 'random': + if mode != "random": raise NotImplementedError( - f'{mode} is not a valid mode for sampling.') + f"{mode} is not a valid mode for sampling." + ) sampled = [] @@ -203,8 +206,8 @@

    Source code for pina.geometry.exclusion_domain

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/intersection_domain.html b/_modules/pina/geometry/intersection_domain.html index f09adb24..38046422 100644 --- a/_modules/pina/geometry/intersection_domain.html +++ b/_modules/pina/geometry/intersection_domain.html @@ -1,11 +1,13 @@ - + - pina.geometry.intersection_domain — PINA 0.1 documentation + pina.geometry.intersection_domain — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -109,7 +111,7 @@

    Source code for pina.geometry.intersection_domain

    with :math:`x` a point in :math:`\mathbb{R}^N` and :math:`N` the dimension of the geometry space. - :param list geometries: A list of geometries from ``pina.geometry`` + :param list geometries: A list of geometries from ``pina.geometry`` such as ``EllipsoidDomain`` or ``CartesianDomain``. The intersection will be taken between all the geometries in the list. The resulting geometry will be the intersection of all the geometries in the list. @@ -128,7 +130,7 @@

    Source code for pina.geometry.intersection_domain

    Check if a point is inside the ``Intersection`` domain. :param point: Point to be checked. - :type point: torch.Tensor + :type point: torch.Tensor :param bool check_border: If ``True``, the border is considered inside. :return: ``True`` if the point is inside the Intersection domain, ``False`` otherwise. :rtype: bool @@ -139,7 +141,7 @@

    Source code for pina.geometry.intersection_domain

    flag += 1 return flag == len(self.geometries)
    -
    [docs] def sample(self, n, mode='random', variables='all'): +
    [docs] def sample(self, n, mode="random", variables="all"): """ Sample routine for ``Intersection`` domain. @@ -167,9 +169,10 @@

    Source code for pina.geometry.intersection_domain

    5 """ - if mode != 'random': + if mode != "random": raise NotImplementedError( - f'{mode} is not a valid mode for sampling.') + f"{mode} is not a valid mode for sampling." + ) sampled = [] @@ -204,8 +207,8 @@

    Source code for pina.geometry.intersection_domain

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/location.html b/_modules/pina/geometry/location.html index 19c07dcb..154b98cf 100644 --- a/_modules/pina/geometry/location.html +++ b/_modules/pina/geometry/location.html @@ -1,11 +1,13 @@ - + - pina.geometry.location — PINA 0.1 documentation + pina.geometry.location — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -127,8 +129,8 @@

    Source code for pina.geometry.location

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/operation_interface.html b/_modules/pina/geometry/operation_interface.html index 78eb93a0..b1952645 100644 --- a/_modules/pina/geometry/operation_interface.html +++ b/_modules/pina/geometry/operation_interface.html @@ -1,11 +1,13 @@ - + - pina.geometry.operation_interface — PINA 0.1 documentation + pina.geometry.operation_interface — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -115,7 +117,7 @@

    Source code for pina.geometry.operation_interface

    @property def geometries(self): - """ + """ The geometries to perform set operation. """ return self._geometries @@ -129,15 +131,15 @@

    Source code for pina.geometry.operation_interface

    :rtype: list[str] """ return self.geometries[0].variables - -
    [docs] @ abstractmethod + +
    [docs] @abstractmethod def is_inside(self, point, check_border=False): """ Check if a point is inside the resulting domain after a set operation is applied. :param point: Point to be checked. - :type point: torch.Tensor + :type point: torch.Tensor :param bool check_border: If ``True``, the border is considered inside. :return: ``True`` if the point is inside the Intersection domain, ``False`` otherwise. :rtype: bool @@ -153,7 +155,8 @@

    Source code for pina.geometry.operation_interface

    for geometry in geometries: if geometry.variables != geometries[0].variables: raise NotImplementedError( - f'The geometries need to have same dimensions and labels.')
    + f"The geometries need to have same dimensions and labels." + )
    @@ -163,8 +166,8 @@

    Source code for pina.geometry.operation_interface

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/simplex.html b/_modules/pina/geometry/simplex.html index 49d873ed..28b5daf9 100644 --- a/_modules/pina/geometry/simplex.html +++ b/_modules/pina/geometry/simplex.html @@ -1,11 +1,13 @@ - + - pina.geometry.simplex — PINA 0.1 documentation + pina.geometry.simplex — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -183,7 +185,7 @@

    Source code for pina.geometry.simplex

                 # respective coord bounded by the lowest and highest values
                 span_dict[coord] = [
                     float(sorted_vertices[0][i]),
    -                float(sorted_vertices[-1][i])
    +                float(sorted_vertices[-1][i]),
                 ]
     
             return CartesianDomain(span_dict)
    @@ -209,16 +211,19 @@ 

    Source code for pina.geometry.simplex

             """
     
             if not all(label in self.variables for label in point.labels):
    -            raise ValueError("Point labels different from constructor"
    -                             f" dictionary labels. Got {point.labels},"
    -                             f" expected {self.variables}.")
    +            raise ValueError(
    +                "Point labels different from constructor"
    +                f" dictionary labels. Got {point.labels},"
    +                f" expected {self.variables}."
    +            )
     
             point_shift = point - self._vertices_matrix[-1]
             point_shift = point_shift.tensor.reshape(-1, 1)
     
             # compute barycentric coordinates
    -        lambda_ = torch.linalg.solve(self._vectors_shifted * 1.0,
    -                                     point_shift * 1.0)
    +        lambda_ = torch.linalg.solve(
    +            self._vectors_shifted * 1.0, point_shift * 1.0
    +        )
             lambda_1 = 1.0 - torch.sum(lambda_)
             lambdas = torch.vstack([lambda_, lambda_1])
     
    @@ -226,8 +231,9 @@ 

    Source code for pina.geometry.simplex

             if not check_border:
                 return all(torch.gt(lambdas, 0.0)) and all(torch.lt(lambdas, 1.0))
     
    -        return all(torch.ge(lambdas, 0)) and (any(torch.eq(lambdas, 0))
    -                                              or any(torch.eq(lambdas, 1)))
    + return all(torch.ge(lambdas, 0)) and ( + any(torch.eq(lambdas, 0)) or any(torch.eq(lambdas, 1)) + )
    def _sample_interior_randomly(self, n, variables): """ @@ -252,9 +258,9 @@

    Source code for pina.geometry.simplex

     
             sampled_points = []
             while len(sampled_points) < n:
    -            sampled_point = self._cartesian_bound.sample(n=1,
    -                                                         mode="random",
    -                                                         variables=variables)
    +            sampled_point = self._cartesian_bound.sample(
    +                n=1, mode="random", variables=variables
    +            )
     
                 if self.is_inside(sampled_point, self._sample_surface):
                     sampled_points.append(sampled_point)
    @@ -285,9 +291,9 @@ 

    Source code for pina.geometry.simplex

                 # extract number of vertices
                 number_of_vertices = self._vertices_matrix.shape[0]
                 # extract idx lambda to set to zero randomly
    -            idx_lambda = torch.randint(low=0,
    -                                       high=number_of_vertices,
    -                                       size=(1, ))
    +            idx_lambda = torch.randint(
    +                low=0, high=number_of_vertices, size=(1,)
    +            )
                 # build lambda vector
                 # 1. sampling [1, 2)
                 lambdas = torch.rand((number_of_vertices, 1))
    @@ -335,8 +341,8 @@ 

    Source code for pina.geometry.simplex

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/geometry/union_domain.html b/_modules/pina/geometry/union_domain.html index 0be8e2f5..7cdc17d6 100644 --- a/_modules/pina/geometry/union_domain.html +++ b/_modules/pina/geometry/union_domain.html @@ -1,11 +1,13 @@ - + - pina.geometry.union_domain — PINA 0.1 documentation + pina.geometry.union_domain — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -109,7 +111,7 @@

    Source code for pina.geometry.union_domain

             with :math:`x` a point in :math:`\mathbb{R}^N` and :math:`N`
             the dimension of the geometry space.
     
    -        :param list geometries: A list of geometries from ``pina.geometry`` 
    +        :param list geometries: A list of geometries from ``pina.geometry``
                 such as ``EllipsoidDomain`` or ``CartesianDomain``.
     
             :Example:
    @@ -139,7 +141,7 @@ 

    Source code for pina.geometry.union_domain

                     return True
             return False
    -
    [docs] def sample(self, n, mode='random', variables='all'): +
    [docs] def sample(self, n, mode="random", variables="all"): """ Sample routine for ``Union`` domain. @@ -182,8 +184,10 @@

    Source code for pina.geometry.union_domain

                 # different than zero. Notice that len(geometries) is
                 # always smaller than remaider.
                 sampled_points.append(
    -                geometry.sample(num_points + int(i < remainder), mode,
    -                                variables))
    +                geometry.sample(
    +                    num_points + int(i < remainder), mode, variables
    +                )
    +            )
                 # in case number of sampled points is smaller than the number of geometries
                 if len(sampled_points) >= n:
                     break
    @@ -198,8 +202,8 @@ 

    Source code for pina.geometry.union_domain

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/label_tensor.html b/_modules/pina/label_tensor.html index 0d8c2f34..50d5353a 100644 --- a/_modules/pina/label_tensor.html +++ b/_modules/pina/label_tensor.html @@ -1,11 +1,13 @@ - + - pina.label_tensor — PINA 0.1 documentation + pina.label_tensor — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.label_tensor

     """ Module for LabelTensor """
    +
     from typing import Any
     import torch
     from torch import Tensor
    @@ -101,7 +104,7 @@ 

    Source code for pina.label_tensor

             return super().__new__(cls, x, *args, **kwargs)
     
         def __init__(self, x, labels):
    -        '''
    +        """
             Construct a `LabelTensor` by passing a tensor and a list of column
             labels. Such labels uniquely identify the columns of the tensor,
             allowing for an easier manipulation.
    @@ -153,7 +156,7 @@ 

    Source code for pina.label_tensor

                         [0.9427, 0.5819],
                         [0.9518, 0.1025],
                         [0.8066, 0.9615]])
    -        '''
    +        """
             if x.ndim == 1:
                 x = x.reshape(-1, 1)
     
    @@ -161,10 +164,12 @@ 

    Source code for pina.label_tensor

                 labels = [labels]
     
             if len(labels) != x.shape[-1]:
    -            raise ValueError('the tensor has not the same number of columns of '
    -                             'the passed labels.')
    +            raise ValueError(
    +                "the tensor has not the same number of columns of "
    +                "the passed labels."
    +            )
             self._labels = labels
    -    
    +
         @property
         def labels(self):
             """Property decorator for labels
    @@ -177,8 +182,10 @@ 

    Source code for pina.label_tensor

         @labels.setter
         def labels(self, labels):
             if len(labels) != self.shape[self.ndim - 1]:  # small check
    -            raise ValueError('The tensor has not the same number of columns of '
    -                             'the passed labels.')
    +            raise ValueError(
    +                "The tensor has not the same number of columns of "
    +                "the passed labels."
    +            )
     
             self._labels = labels  # assign the label
     
    @@ -198,7 +205,7 @@ 

    Source code for pina.label_tensor

     
             all_labels = [label for lt in label_tensors for label in lt.labels]
             if set(all_labels) != set(label_tensors[0].labels):
    -            raise RuntimeError('The tensors to stack have different labels')
    +            raise RuntimeError("The tensors to stack have different labels")
     
             labels = label_tensors[0].labels
             tensors = [lt.extract(labels) for lt in label_tensors]
    @@ -212,7 +219,7 @@ 

    Source code for pina.label_tensor

             :return: A copy of the tensor.
             :rtype: LabelTensor
             """
    -        # # used before merging 
    +        # # used before merging
             # try:
             #     out = LabelTensor(super().clone(*args, **kwargs), self.labels)
             # except:
    @@ -273,14 +280,15 @@ 

    Source code for pina.label_tensor

                 pass
             else:
                 raise TypeError(
    -                '`label_to_extract` should be a str, or a str iterator')
    +                "`label_to_extract` should be a str, or a str iterator"
    +            )
     
             indeces = []
             for f in label_to_extract:
                 try:
                     indeces.append(self.labels.index(f))
                 except ValueError:
    -                raise ValueError(f'`{f}` not in the labels list')
    +                raise ValueError(f"`{f}` not in the labels list")
     
             new_data = super(Tensor, self.T).__getitem__(indeces).T
             new_labels = [self.labels[idx] for idx in indeces]
    @@ -292,17 +300,16 @@ 

    Source code for pina.label_tensor

     
     
    [docs] def detach(self): detached = super().detach() - if hasattr(self, '_labels'): + if hasattr(self, "_labels"): detached._labels = self._labels return detached
    - -
    [docs] def requires_grad_(self, mode = True): +
    [docs] def requires_grad_(self, mode=True): lt = super().requires_grad_(mode) lt.labels = self.labels return lt
    -
    [docs] def append(self, lt, mode='std'): +
    [docs] def append(self, lt, mode="std"): """ Return a copy of the merged tensors. @@ -312,22 +319,23 @@

    Source code for pina.label_tensor

             :rtype: LabelTensor
             """
             if set(self.labels).intersection(lt.labels):
    -            raise RuntimeError('The tensors to merge have common labels')
    +            raise RuntimeError("The tensors to merge have common labels")
     
             new_labels = self.labels + lt.labels
    -        if mode == 'std':
    +        if mode == "std":
                 new_tensor = torch.cat((self, lt), dim=1)
    -        elif mode == 'first':
    +        elif mode == "first":
                 raise NotImplementedError
    -        elif mode == 'cross':
    +        elif mode == "cross":
                 tensor1 = self
                 tensor2 = lt
                 n1 = tensor1.shape[0]
                 n2 = tensor2.shape[0]
     
                 tensor1 = LabelTensor(tensor1.repeat(n2, 1), labels=tensor1.labels)
    -            tensor2 = LabelTensor(tensor2.repeat_interleave(n1, dim=0),
    -                                  labels=tensor2.labels)
    +            tensor2 = LabelTensor(
    +                tensor2.repeat_interleave(n1, dim=0), labels=tensor2.labels
    +            )
                 new_tensor = torch.cat((tensor1, tensor2), dim=1)
     
             new_tensor = new_tensor.as_subclass(LabelTensor)
    @@ -339,34 +347,37 @@ 

    Source code for pina.label_tensor

             Return a copy of the selected tensor.
             """
     
    -        if isinstance(index, str) or (isinstance(index, (tuple, list))and all(isinstance(a, str) for a in index)):
    +        if isinstance(index, str) or (
    +            isinstance(index, (tuple, list))
    +            and all(isinstance(a, str) for a in index)
    +        ):
                 return self.extract(index)
     
             selected_lt = super(Tensor, self).__getitem__(index)
    -        
    +
             try:
                 len_index = len(index)
             except TypeError:
                 len_index = 1
    -        
    +
             if isinstance(index, int) or len_index == 1:
                 if selected_lt.ndim == 1:
                     selected_lt = selected_lt.reshape(1, -1)
    -            if hasattr(self, 'labels'):
    +            if hasattr(self, "labels"):
                     selected_lt.labels = self.labels
             elif len_index == 2:
                 if selected_lt.ndim == 1:
                     selected_lt = selected_lt.reshape(-1, 1)
    -            if hasattr(self, 'labels'):
    +            if hasattr(self, "labels"):
                     if isinstance(index[1], list):
                         selected_lt.labels = [self.labels[i] for i in index[1]]
                     else:
                         selected_lt.labels = self.labels[index[1]]
             else:
                 selected_lt.labels = self.labels
    -                    
    +
             return selected_lt
    -    
    +
         @property
         def tensor(self):
             return self.as_subclass(Tensor)
    @@ -375,10 +386,10 @@ 

    Source code for pina.label_tensor

             return super().__len__()
     
         def __str__(self):
    -        if hasattr(self, 'labels'):
    -            s = f'labels({str(self.labels)})\n'
    +        if hasattr(self, "labels"):
    +            s = f"labels({str(self.labels)})\n"
             else:
    -            s = 'no labels\n'
    +            s = "no labels\n"
             s += super().__str__()
             return s
    @@ -390,8 +401,8 @@

    Source code for pina.label_tensor

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/loss.html b/_modules/pina/loss.html index 44382863..aed17588 100644 --- a/_modules/pina/loss.html +++ b/_modules/pina/loss.html @@ -1,11 +1,13 @@ - + - pina.loss — PINA 0.1 documentation + pina.loss — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -94,7 +96,7 @@

    Source code for pina.loss

     import torch
     from .utils import check_consistency
     
    -__all__ = ['LossInterface', 'LpLoss', 'PowerLoss']
    +__all__ = ["LossInterface", "LpLoss", "PowerLoss"]
     
     
     
    [docs]class LossInterface(_Loss, metaclass=ABCMeta): @@ -103,10 +105,10 @@

    Source code for pina.loss

         should be inheritied from this class.
         """
     
    -    def __init__(self, reduction='mean'):
    +    def __init__(self, reduction="mean"):
             """
             :param str reduction: Specifies the reduction to apply to the output:
    -            ``none`` | ``mean`` | ``sum``. When ``none``: no reduction 
    +            ``none`` | ``mean`` | ``sum``. When ``none``: no reduction
                 will be applied, ``mean``: the sum of the output will be divided
                 by the number of elements in the output, ``sum``: the output will
                 be summed. Note: ``size_average`` and ``reduce`` are in the
    @@ -130,7 +132,7 @@ 

    Source code for pina.loss

             """Simple helper function to check reduction
     
             :param reduction: Specifies the reduction to apply to the output:
    -            ``none`` | ``mean`` | ``sum``. When ``none``: no reduction 
    +            ``none`` | ``mean`` | ``sum``. When ``none``: no reduction
                 will be applied, ``mean``: the sum of the output will be divided
                 by the number of elements in the output, ``sum``: the output will
                 be summed. Note: ``size_average`` and ``reduce`` are in the
    @@ -190,14 +192,14 @@ 

    Source code for pina.loss

         The division by :math:`n` can be avoided if one sets ``reduction`` to ``sum``.
         """
     
    -    def __init__(self, p=2, reduction='mean', relative=False):
    +    def __init__(self, p=2, reduction="mean", relative=False):
             """
             :param int p: Degree of Lp norm. It specifies the type of norm to
                 be calculated. See `list of possible orders in torch linalg
                 <https://pytorch.org/docs/stable/generated/torch.linalg.norm.html#torch.linalg.norm>`_ to
                 for possible degrees. Default 2 (euclidean norm).
             :param str reduction: Specifies the reduction to apply to the output:
    -            ``none`` | ``mean`` | ``sum``. ``none``: no reduction 
    +            ``none`` | ``mean`` | ``sum``. ``none``: no reduction
                 will be applied, ``mean``: the sum of the output will be divided
                 by the number of elements in the output, ``sum``: the output will
                 be summed.
    @@ -206,7 +208,7 @@ 

    Source code for pina.loss

             super().__init__(reduction=reduction)
     
             # check consistency
    -        check_consistency(p, (str,int,float))
    +        check_consistency(p, (str, int, float))
             check_consistency(relative, bool)
     
             self.p = p
    @@ -263,14 +265,14 @@ 

    Source code for pina.loss

         The division by :math:`n` can be avoided if one sets ``reduction`` to ``sum``.
         """
     
    -    def __init__(self, p=2, reduction='mean', relative=False):
    +    def __init__(self, p=2, reduction="mean", relative=False):
             """
             :param int p: Degree of Lp norm. It specifies the type of norm to
                 be calculated. See `list of possible orders in torch linalg
                 <https://pytorch.org/docs/stable/generated/torch.linalg.norm.html#torch.linalg.norm>`_ to
                 see the possible degrees. Default 2 (euclidean norm).
             :param str reduction: Specifies the reduction to apply to the output:
    -            ``none`` | ``mean`` | ``sum``. When ``none``: no reduction 
    +            ``none`` | ``mean`` | ``sum``. When ``none``: no reduction
                 will be applied, ``mean``: the sum of the output will be divided
                 by the number of elements in the output, ``sum``: the output will
                 be summed.
    @@ -305,8 +307,8 @@ 

    Source code for pina.loss

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/base_no.html b/_modules/pina/model/base_no.html new file mode 100644 index 00000000..a35f643c --- /dev/null +++ b/_modules/pina/model/base_no.html @@ -0,0 +1,260 @@ + + + + + + pina.model.base_no — PINA 0.1.0.post2403 documentation + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    Source code for pina.model.base_no

    +"""
    +Kernel Neural Operator Module.
    +"""
    +
    +import torch
    +from pina.utils import check_consistency
    +
    +
    +
    [docs]class KernelNeuralOperator(torch.nn.Module): + r""" + Base class for composing Neural Operators with integral kernels. + + This is a base class for composing neural operators with multiple + integral kernels. All neural operator models defined in PINA inherit + from this class. The structure is inspired by the work of Kovachki, N. + et al. see Figure 2 of the reference for extra details. The Neural + Operators inheriting from this class can be written as: + + .. math:: + G_\theta := P \circ K_m \circ \cdot \circ K_1 \circ L + + where: + + * :math:`G_\theta: \mathcal{A}\subset \mathbb{R}^{\rm{in}} \rightarrow + \mathcal{D}\subset \mathbb{R}^{\rm{out}}` is the neural operator + approximation of the unknown real operator :math:`G`, that is + :math:`G \approx G_\theta` + * :math:`L: \mathcal{A}\subset \mathbb{R}^{\rm{in}} \rightarrow + \mathbb{R}^{\rm{emb}}` is a lifting operator mapping the input + from its domain :math:`\mathcal{A}\subset \mathbb{R}^{\rm{in}}` + to its embedding dimension :math:`\mathbb{R}^{\rm{emb}}` + * :math:`\{K_i : \mathbb{R}^{\rm{emb}} \rightarrow + \mathbb{R}^{\rm{emb}} \}_{i=1}^m` are :math:`m` integral kernels + mapping each hidden representation to the next one. + * :math:`P : \mathbb{R}^{\rm{emb}} \rightarrow \mathcal{D}\subset + \mathbb{R}^{\rm{out}}` is a projection operator mapping the hidden + representation to the output function. + + .. seealso:: + + **Original reference**: Kovachki, N., Li, Z., Liu, B., + Azizzadenesheli, K., Bhattacharya, K., Stuart, A., & Anandkumar, A. + (2023). *Neural operator: Learning maps between function + spaces with applications to PDEs*. Journal of Machine Learning + Research, 24(89), 1-97. + """ + + def __init__(self, lifting_operator, integral_kernels, projection_operator): + """ + :param torch.nn.Module lifting_operator: The lifting operator + mapping the input to its hidden dimension. + :param torch.nn.Module integral_kernels: List of integral kernels + mapping each hidden representation to the next one. + :param torch.nn.Module projection_operator: The projection operator + mapping the hidden representation to the output function. + """ + + super().__init__() + + self._lifting_operator = lifting_operator + self._integral_kernels = integral_kernels + self._projection_operator = projection_operator + + @property + def lifting_operator(self): + """ + The lifting operator property. + """ + return self._lifting_operator + + @lifting_operator.setter + def lifting_operator(self, value): + """ + The lifting operator setter + + :param torch.nn.Module value: The lifting operator torch module. + """ + check_consistency(value, torch.nn.Module) + self._lifting_operator = value + + @property + def projection_operator(self): + """ + The projection operator property. + """ + return self._projection_operator + + @projection_operator.setter + def projection_operator(self, value): + """ + The projection operator setter + + :param torch.nn.Module value: The projection operator torch module. + """ + check_consistency(value, torch.nn.Module) + self._projection_operator = value + + @property + def integral_kernels(self): + """ + The integral kernels operator property. + """ + return self._integral_kernels + + @integral_kernels.setter + def integral_kernels(self, value): + """ + The integral kernels operator setter + + :param torch.nn.Module value: The integral kernels operator torch + module. + """ + check_consistency(value, torch.nn.Module) + self._integral_kernels = value + +
    [docs] def forward(self, x): + r""" + Forward computation for Base Neural Operator. It performs a + lifting of the input by the ``lifting_operator``. + Then different layers integral kernels are applied using + ``integral_kernels``. Finally the output is projected + to the final dimensionality by the ``projection_operator``. + + :param torch.Tensor x: The input tensor for performing the + computation. It expects a tensor :math:`B \times N \times D`, + where :math:`B` is the batch_size, :math:`N` the number of points + in the mesh, :math:`D` the dimension of the problem. In particular + :math:`D` is the number of spatial/paramtric/temporal variables + plus the field variables. For example for 2D problems with 2 + output\ variables :math:`D=4`. + :return: The output tensor obtained from the NO. + :rtype: torch.Tensor + """ + x = self.lifting_operator(x) + x = self.integral_kernels(x) + x = self.projection_operator(x) + return x
    +
    + +
    +
    +
    + +
    + +
    +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024. +

    +
    + + Built with Sphinx using a + theme + provided by Read the Docs. + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/_modules/pina/model/deeponet.html b/_modules/pina/model/deeponet.html index 91149417..e8fb1517 100644 --- a/_modules/pina/model/deeponet.html +++ b/_modules/pina/model/deeponet.html @@ -1,11 +1,13 @@ - + - pina.model.deeponet — PINA 0.1 documentation + pina.model.deeponet — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.model.deeponet

     """Module for DeepONet model"""
    +
     import torch
     import torch.nn as nn
     from ..utils import check_consistency, is_function
    @@ -113,12 +116,14 @@ 

    Source code for pina.model.deeponet

     
         """
     
    -    def __init__(self,
    -                 networks,
    -                 aggregator="*",
    -                 reduction="+",
    -                 scale=True,
    -                 translation=True):
    +    def __init__(
    +        self,
    +        networks,
    +        aggregator="*",
    +        reduction="+",
    +        scale=True,
    +        translation=True,
    +    ):
             """
             :param dict networks: The neural networks to use as
                 models. The ``dict`` takes as key a neural network, and
    @@ -210,8 +215,9 @@ 

    Source code for pina.model.deeponet

                 shapes.append(key(input_).shape[-1])
     
             if not all(map(lambda x: x == shapes[0], shapes)):
    -            raise ValueError('The passed networks have not the same '
    -                             'output dimension.')
    +            raise ValueError(
    +                "The passed networks have not the same " "output dimension."
    +            )
     
             # assign trunk and branch net with their input indeces
             self.models = torch.nn.ModuleList(networks.keys())
    @@ -222,10 +228,16 @@ 

    Source code for pina.model.deeponet

             self._init_reduction(reduction=reduction)
     
             # scale and translation
    -        self._scale = torch.nn.Parameter(torch.tensor(
    -            [1.0])) if scale else torch.tensor([1.0])
    -        self._trasl = torch.nn.Parameter(torch.tensor(
    -            [1.0])) if translation else torch.tensor([1.0])
    +        self._scale = (
    +            torch.nn.Parameter(torch.tensor([1.0]))
    +            if scale
    +            else torch.tensor([1.0])
    +        )
    +        self._trasl = (
    +            torch.nn.Parameter(torch.tensor([1.0]))
    +            if translation
    +            else torch.tensor([1.0])
    +        )
     
         @staticmethod
         def _symbol_functions(**kwargs):
    @@ -269,16 +281,18 @@ 

    Source code for pina.model.deeponet

                     return x.extract(indeces)
                 except AttributeError:
                     raise RuntimeError(
    -                    'Not possible to extract input variables from tensor.'
    -                    ' Ensure that the passed tensor is a LabelTensor or'
    -                    ' pass list of integers to extract variables. For'
    -                    ' more information refer to warning in the documentation.')
    +                    "Not possible to extract input variables from tensor."
    +                    " Ensure that the passed tensor is a LabelTensor or"
    +                    " pass list of integers to extract variables. For"
    +                    " more information refer to warning in the documentation."
    +                )
             elif isinstance(indeces[0], int):
                 return x[..., indeces]
             else:
                 raise RuntimeError(
    -                'Not able to extract right indeces for tensor.'
    -                ' For more information refer to warning in the documentation.')
    +                "Not able to extract right indeces for tensor."
    +                " For more information refer to warning in the documentation."
    +            )
     
     
    [docs] def forward(self, x): """ @@ -286,7 +300,7 @@

    Source code for pina.model.deeponet

     
             :param LabelTensor or torch.Tensor x: The input tensor for the forward call.
             :return: The output computed by the DeepONet model.
    -        :rtype: LabelTensor or torch.Tensor 
    +        :rtype: LabelTensor or torch.Tensor
             """
     
             # forward pass
    @@ -356,7 +370,7 @@ 

    Source code for pina.model.deeponet

     
         DeepONet is a general architecture for learning Operators. Unlike
         traditional machine learning methods DeepONet is designed to map
    -    entire functions to other functions. It can be trained both with 
    +    entire functions to other functions. It can be trained both with
         Physics Informed or Supervised learning strategies.
     
         .. seealso::
    @@ -369,15 +383,17 @@ 

    Source code for pina.model.deeponet

     
         """
     
    -    def __init__(self,
    -                 branch_net,
    -                 trunk_net,
    -                 input_indeces_branch_net,
    -                 input_indeces_trunk_net,
    -                 aggregator="*",
    -                 reduction="+",
    -                 scale=True,
    -                 translation=True):
    +    def __init__(
    +        self,
    +        branch_net,
    +        trunk_net,
    +        input_indeces_branch_net,
    +        input_indeces_trunk_net,
    +        aggregator="*",
    +        reduction="+",
    +        scale=True,
    +        translation=True,
    +    ):
             """
             :param torch.nn.Module branch_net: The neural network to use as branch
                 model. It has to take as input a :py:obj:`pina.label_tensor.LabelTensor`
    @@ -452,14 +468,15 @@ 

    Source code for pina.model.deeponet

             """
             networks = {
                 branch_net: input_indeces_branch_net,
    -            trunk_net: input_indeces_trunk_net
    +            trunk_net: input_indeces_trunk_net,
             }
    -        super().__init__(networks=networks,
    -                         aggregator=aggregator,
    -                         reduction=reduction,
    -                         scale=scale,
    -                         translation=translation)
    -        
    +        super().__init__(
    +            networks=networks,
    +            aggregator=aggregator,
    +            reduction=reduction,
    +            scale=scale,
    +            translation=translation,
    +        )
     
     
    [docs] def forward(self, x): """ @@ -467,11 +484,10 @@

    Source code for pina.model.deeponet

     
             :param LabelTensor or torch.Tensor x: The input tensor for the forward call.
             :return: The output computed by the DeepONet model.
    -        :rtype: LabelTensor or torch.Tensor 
    +        :rtype: LabelTensor or torch.Tensor
             """
             return super().forward(x)
    - @property def branch_net(self): """ @@ -494,8 +510,8 @@

    Source code for pina.model.deeponet

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/feed_forward.html b/_modules/pina/model/feed_forward.html index 05d5e00c..a9d2a9fa 100644 --- a/_modules/pina/model/feed_forward.html +++ b/_modules/pina/model/feed_forward.html @@ -1,11 +1,13 @@ - + - pina.model.feed_forward — PINA 0.1 documentation + pina.model.feed_forward — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.model.feed_forward

     """Module for FeedForward model"""
    +
     import torch
     import torch.nn as nn
     from ..utils import check_consistency
    @@ -118,24 +121,25 @@ 

    Source code for pina.model.feed_forward

         :param bool bias: If ``True`` the MLP will consider some bias.
         """
     
    -    def __init__(self,
    -                 input_dimensions,
    -                 output_dimensions,
    -                 inner_size=20,
    -                 n_layers=2,
    -                 func=nn.Tanh,
    -                 layers=None,
    -                 bias=True):
    -        """
    -        """
    +    def __init__(
    +        self,
    +        input_dimensions,
    +        output_dimensions,
    +        inner_size=20,
    +        n_layers=2,
    +        func=nn.Tanh,
    +        layers=None,
    +        bias=True,
    +    ):
    +        """ """
             super().__init__()
     
             if not isinstance(input_dimensions, int):
    -            raise ValueError('input_dimensions expected to be int.')
    +            raise ValueError("input_dimensions expected to be int.")
             self.input_dimension = input_dimensions
     
             if not isinstance(output_dimensions, int):
    -            raise ValueError('output_dimensions expected to be int.')
    +            raise ValueError("output_dimensions expected to be int.")
             self.output_dimension = output_dimensions
             if layers is None:
                 layers = [inner_size] * n_layers
    @@ -147,7 +151,8 @@ 

    Source code for pina.model.feed_forward

             self.layers = []
             for i in range(len(tmp_layers) - 1):
                 self.layers.append(
    -                nn.Linear(tmp_layers[i], tmp_layers[i + 1], bias=bias))
    +                nn.Linear(tmp_layers[i], tmp_layers[i + 1], bias=bias)
    +            )
     
             if isinstance(func, list):
                 self.functions = func
    @@ -155,7 +160,7 @@ 

    Source code for pina.model.feed_forward

                 self.functions = [func for _ in range(len(self.layers) - 1)]
     
             if len(self.layers) != len(self.functions) + 1:
    -            raise RuntimeError('uncosistent number of layers and functions')
    +            raise RuntimeError("uncosistent number of layers and functions")
     
             unique_list = []
             for layer, func in zip(self.layers[:-1], self.functions):
    @@ -186,7 +191,7 @@ 

    Source code for pina.model.feed_forward

     
         .. seealso::
     
    -        **Original reference**: Wang, Sifan, Yujun Teng, and Paris Perdikaris. 
    +        **Original reference**: Wang, Sifan, Yujun Teng, and Paris Perdikaris.
             *Understanding and mitigating gradient flow pathologies in physics-informed
             neural networks*. SIAM Journal on Scientific Computing 43.5 (2021): A3055-A3081.
             DOI: `10.1137/20M1318043
    @@ -213,16 +218,17 @@ 

    Source code for pina.model.feed_forward

             dimension must be the same as ``inner_size``.
         """
     
    -    def __init__(self,
    -                 input_dimensions,
    -                 output_dimensions,
    -                 inner_size=20,
    -                 n_layers=2,
    -                 func=nn.Tanh,
    -                 bias=True,
    -                 transformer_nets=None):
    -        """
    -        """
    +    def __init__(
    +        self,
    +        input_dimensions,
    +        output_dimensions,
    +        inner_size=20,
    +        n_layers=2,
    +        func=nn.Tanh,
    +        bias=True,
    +        transformer_nets=None,
    +    ):
    +        """ """
             super().__init__()
     
             # check type consistency
    @@ -237,35 +243,42 @@ 

    Source code for pina.model.feed_forward

             if transformer_nets is None:
                 transformer_nets = [
                     EnhancedLinear(
    -                    nn.Linear(in_features=input_dimensions,
    -                              out_features=inner_size), nn.Tanh()),
    +                    nn.Linear(
    +                        in_features=input_dimensions, out_features=inner_size
    +                    ),
    +                    nn.Tanh(),
    +                ),
                     EnhancedLinear(
    -                    nn.Linear(in_features=input_dimensions,
    -                              out_features=inner_size), nn.Tanh())
    +                    nn.Linear(
    +                        in_features=input_dimensions, out_features=inner_size
    +                    ),
    +                    nn.Tanh(),
    +                ),
                 ]
             elif isinstance(transformer_nets, (list, tuple)):
                 if len(transformer_nets) != 2:
                     raise ValueError(
    -                    'transformer_nets needs to be a list of len two.')
    +                    "transformer_nets needs to be a list of len two."
    +                )
                 for net in transformer_nets:
                     if not isinstance(net, nn.Module):
                         raise ValueError(
    -                        'transformer_nets needs to be a list of torch.nn.Module.'
    +                        "transformer_nets needs to be a list of torch.nn.Module."
                         )
                     x = torch.rand(10, input_dimensions)
                     try:
                         out = net(x)
                     except RuntimeError:
                         raise ValueError(
    -                        'transformer network input incompatible with input_dimensions.'
    +                        "transformer network input incompatible with input_dimensions."
                         )
                     if out.shape[-1] != inner_size:
                         raise ValueError(
    -                        'transformer network output incompatible with inner_size.'
    +                        "transformer network output incompatible with inner_size."
                         )
             else:
                 RuntimeError(
    -                'Runtime error for transformer nets, check official documentation.'
    +                "Runtime error for transformer nets, check official documentation."
                 )
     
             # assign variables
    @@ -282,10 +295,11 @@ 

    Source code for pina.model.feed_forward

             self.layers = []
             for i in range(len(tmp_layers) - 1):
                 self.layers.append(
    -                nn.Linear(tmp_layers[i], tmp_layers[i + 1], bias=bias))
    -        self.last_layer = nn.Linear(tmp_layers[len(tmp_layers) - 1],
    -                                    output_dimensions,
    -                                    bias=bias)
    +                nn.Linear(tmp_layers[i], tmp_layers[i + 1], bias=bias)
    +            )
    +        self.last_layer = nn.Linear(
    +            tmp_layers[len(tmp_layers) - 1], output_dimensions, bias=bias
    +        )
     
             if isinstance(func, list):
                 self.functions = func()
    @@ -293,7 +307,7 @@ 

    Source code for pina.model.feed_forward

                 self.functions = [func() for _ in range(len(self.layers))]
     
             if len(self.layers) != len(self.functions):
    -            raise RuntimeError('uncosistent number of layers and functions')
    +            raise RuntimeError("uncosistent number of layers and functions")
     
             unique_list = []
             for layer, func in zip(self.layers, self.functions):
    @@ -317,7 +331,7 @@ 

    Source code for pina.model.feed_forward

             # skip connections pass
             for layer in self.inner_layers.children():
                 x = layer(x)
    -            x = (1. - x) * input_[0] + x * input_[1]
    +            x = (1.0 - x) * input_[0] + x * input_[1]
     
             # last layer
             return self.last_layer(x)
    @@ -330,8 +344,8 @@

    Source code for pina.model.feed_forward

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/fno.html b/_modules/pina/model/fno.html index 739294cf..7ec538ef 100644 --- a/_modules/pina/model/fno.html +++ b/_modules/pina/model/fno.html @@ -1,11 +1,13 @@ - + - pina.model.fno — PINA 0.1 documentation + pina.model.fno — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -87,69 +89,84 @@

    Source code for pina.model.fno

    -import torch
    +"""
    +Fourier Neural Operator Module.
    +"""
    +
    +import torch
     import torch.nn as nn
    -from ..utils import check_consistency
    -from .layers.fourier import FourierBlock1D, FourierBlock2D, FourierBlock3D
     from pina import LabelTensor
     import warnings
    +from ..utils import check_consistency
    +from .layers.fourier import FourierBlock1D, FourierBlock2D, FourierBlock3D
    +from .base_no import KernelNeuralOperator
     
     
    -
    [docs]class FNO(torch.nn.Module): +
    [docs]class FourierIntegralKernel(torch.nn.Module): """ - The PINA implementation of Fourier Neural Operator network. + Implementation of Fourier Integral Kernel network. - Fourier Neural Operator (FNO) is a general architecture for learning Operators. - Unlike traditional machine learning methods FNO is designed to map - entire functions to other functions. It can be trained both with - Supervised learning strategies. FNO does global convolution by performing the - operation on the Fourier space. + This class implements the Fourier Integral Kernel network, which is a + PINA implementation of Fourier Neural Operator kernel network. + It performs global convolution by operating in the Fourier space. .. seealso:: - **Original reference**: Li, Z., Kovachki, N., Azizzadenesheli, K., Liu, B., - Bhattacharya, K., Stuart, A., & Anandkumar, A. (2020). *Fourier neural operator for - parametric partial differential equations*. + **Original reference**: Li, Z., Kovachki, N., Azizzadenesheli, + K., Liu, B., Bhattacharya, K., Stuart, A., & Anandkumar, A. + (2020). *Fourier neural operator for parametric partial + differential equations*. DOI: `arXiv preprint arXiv:2010.08895. <https://arxiv.org/abs/2010.08895>`_ """ - def __init__(self, - lifting_net, - projecting_net, - n_modes, - dimensions=3, - padding=8, - padding_type="constant", - inner_size=20, - n_layers=2, - func=nn.Tanh, - layers=None): + def __init__( + self, + input_numb_fields, + output_numb_fields, + n_modes, + dimensions=3, + padding=8, + padding_type="constant", + inner_size=20, + n_layers=2, + func=nn.Tanh, + layers=None, + ): + """ + :param int input_numb_fields: Number of input fields. + :param int output_numb_fields: Number of output fields. + :param int | list[int] n_modes: Number of modes. + :param int dimensions: Number of dimensions (1, 2, or 3). + :param int padding: Padding size, defaults to 8. + :param str padding_type: Type of padding, defaults to "constant". + :param int inner_size: Inner size, defaults to 20. + :param int n_layers: Number of layers, defaults to 2. + :param torch.nn.Module func: Activation function, defaults to nn.Tanh. + :param list[int] layers: List of layer sizes, defaults to None. + """ super().__init__() # check type consistency - check_consistency(lifting_net, nn.Module) - check_consistency(projecting_net, nn.Module) check_consistency(dimensions, int) check_consistency(padding, int) check_consistency(padding_type, str) check_consistency(inner_size, int) check_consistency(n_layers, int) check_consistency(func, nn.Module, subclass=True) + if layers is not None: if isinstance(layers, (tuple, list)): check_consistency(layers, int) else: - raise ValueError('layers must be tuple or list of int.') + raise ValueError("layers must be tuple or list of int.") if not isinstance(n_modes, (list, tuple, int)): raise ValueError( - 'n_modes must be a int or list or tuple of valid modes.' - ' More information on the official documentation.') + "n_modes must be a int or list or tuple of valid modes." + " More information on the official documentation." + ) - # assign variables - # TODO check input lifting net and input projecting net - self._lifting_net = lifting_net - self._projecting_net = projecting_net + # assign padding self._padding = padding # initialize fourier layer for each dimension @@ -160,9 +177,9 @@

    Source code for pina.model.fno

             elif dimensions == 3:
                 fourier_layer = FourierBlock3D
             else:
    -            raise NotImplementedError('FNO implemented only for 1D/2D/3D data.')
    +            raise NotImplementedError("FNO implemented only for 1D/2D/3D data.")
     
    -        # Here we build the FNO by stacking Fourier Blocks
    +        # Here we build the FNO kernels by stacking Fourier Blocks
     
             # 1. Assign output dimensions for each FNO layer
             if layers is None:
    @@ -172,37 +189,39 @@ 

    Source code for pina.model.fno

             if isinstance(func, list):
                 if len(layers) != len(func):
                     raise RuntimeError(
    -                    'Uncosistent number of layers and functions.')
    -            self._functions = func
    +                    "Uncosistent number of layers and functions."
    +                )
    +            _functions = func
             else:
    -            self._functions = [func for _ in range(len(layers))]
    +            _functions = [func for _ in range(len(layers) - 1)]
    +        _functions.append(torch.nn.Identity)
     
             # 3. Assign modes functions for each FNO layer
             if isinstance(n_modes, list):
    -            if all(isinstance(i, list)
    -                   for i in n_modes) and len(layers) != len(n_modes):
    +            if all(isinstance(i, list) for i in n_modes) and len(layers) != len(
    +                n_modes
    +            ):
                     raise RuntimeError(
    -                    'Uncosistent number of layers and functions.')
    +                    "Uncosistent number of layers and functions."
    +                )
                 elif all(isinstance(i, int) for i in n_modes):
                     n_modes = [n_modes] * len(layers)
             else:
                 n_modes = [n_modes] * len(layers)
     
             # 4. Build the FNO network
    -        tmp_layers = layers.copy()
    -        first_parameter = next(lifting_net.parameters())
    -        input_shape = first_parameter.size()
    -        out_feats = lifting_net(torch.rand(size=input_shape)).shape[-1]
    -        tmp_layers.insert(0, out_feats)
    -
    -        self._layers = []
    -        for i in range(len(tmp_layers) - 1):
    -            self._layers.append(
    -                fourier_layer(input_numb_fields=tmp_layers[i],
    -                              output_numb_fields=tmp_layers[i + 1],
    -                              n_modes=n_modes[i],
    -                              activation=self._functions[i]))
    -        self._layers = nn.Sequential(*self._layers)
    +        _layers = []
    +        tmp_layers = [input_numb_fields] + layers + [output_numb_fields]
    +        for i in range(len(layers)):
    +            _layers.append(
    +                fourier_layer(
    +                    input_numb_fields=tmp_layers[i],
    +                    output_numb_fields=tmp_layers[i + 1],
    +                    n_modes=n_modes[i],
    +                    activation=_functions[i],
    +                )
    +            )
    +        self._layers = nn.Sequential(*_layers)
     
             # 5. Padding values for spectral conv
             if isinstance(padding, int):
    @@ -213,28 +232,29 @@ 

    Source code for pina.model.fno

                 val for pair in zip([0] * dimensions, padding) for val in pair
             ]
     
    -
    [docs] def forward(self, x): +
    [docs] def forward(self, x): """ Forward computation for Fourier Neural Operator. It performs a lifting of the input by the ``lifting_net``. Then different layers of Fourier Blocks are applied. Finally the output is projected to the final dimensionality by the ``projecting_net``. - :param torch.Tensor x: The input tensor for fourier block, depending on - ``dimension`` in the initialization. In particular it is expected + :param torch.Tensor x: The input tensor for fourier block, + depending on ``dimension`` in the initialization. + In particular it is expected: + * 1D tensors: ``[batch, X, channels]`` * 2D tensors: ``[batch, X, Y, channels]`` * 3D tensors: ``[batch, X, Y, Z, channels]`` - :return: The output tensor obtained from the FNO. + :return: The output tensor obtained from the kernels convolution. :rtype: torch.Tensor """ - if isinstance(x, LabelTensor): #TODO remove when Network is fixed - warnings.warn('LabelTensor passed as input is not allowed, casting LabelTensor to Torch.Tensor') + if isinstance(x, LabelTensor): # TODO remove when Network is fixed + warnings.warn( + "LabelTensor passed as input is not allowed," + " casting LabelTensor to Torch.Tensor" + ) x = x.as_subclass(torch.Tensor) - - # lifting the input in higher dimensional space - x = self._lifting_net(x) - # permuting the input [batch, channels, x, y, ...] permutation_idx = [0, x.ndim - 1, *[i for i in range(1, x.ndim - 1)]] x = x.permute(permutation_idx) @@ -253,8 +273,94 @@

    Source code for pina.model.fno

             permutation_idx = [0, *[i for i in range(2, x.ndim)], 1]
             x = x.permute(permutation_idx)
     
    -        # apply projecting operator and return
    -        return self._projecting_net(x)
    + return x
    + + +
    [docs]class FNO(KernelNeuralOperator): + """ + The PINA implementation of Fourier Neural Operator network. + + Fourier Neural Operator (FNO) is a general architecture for + learning Operators. Unlike traditional machine learning methods + FNO is designed to map entire functions to other functions. It + can be trained with Supervised learning strategies. FNO does global + convolution by performing the operation on the Fourier space. + + .. seealso:: + + **Original reference**: Li, Z., Kovachki, N., Azizzadenesheli, + K., Liu, B., Bhattacharya, K., Stuart, A., & Anandkumar, A. + (2020). *Fourier neural operator for parametric partial + differential equations*. + DOI: `arXiv preprint arXiv:2010.08895. + <https://arxiv.org/abs/2010.08895>`_ + """ + + def __init__( + self, + lifting_net, + projecting_net, + n_modes, + dimensions=3, + padding=8, + padding_type="constant", + inner_size=20, + n_layers=2, + func=nn.Tanh, + layers=None, + ): + """ + :param torch.nn.Module lifting_net: The neural network for lifting + the input. + :param torch.nn.Module projecting_net: The neural network for + projecting the output. + :param int | list[int] n_modes: Number of modes. + :param int dimensions: Number of dimensions (1, 2, or 3). + :param int padding: Padding size, defaults to 8. + :param str padding_type: Type of padding, defaults to `constant`. + :param int inner_size: Inner size, defaults to 20. + :param int n_layers: Number of layers, defaults to 2. + :param torch.nn.Module func: Activation function, defaults to nn.Tanh. + :param list[int] layers: List of layer sizes, defaults to None. + """ + lifting_operator_out = lifting_net( + torch.rand(size=next(lifting_net.parameters()).size()) + ).shape[-1] + super().__init__( + lifting_operator=lifting_net, + projection_operator=projecting_net, + integral_kernels=FourierIntegralKernel( + input_numb_fields=lifting_operator_out, + output_numb_fields=next(projecting_net.parameters()).size(), + n_modes=n_modes, + dimensions=dimensions, + padding=padding, + padding_type=padding_type, + inner_size=inner_size, + n_layers=n_layers, + func=func, + layers=layers, + ), + ) + +
    [docs] def forward(self, x): + """ + Forward computation for Fourier Neural Operator. It performs a + lifting of the input by the ``lifting_net``. Then different layers + of Fourier Blocks are applied. Finally the output is projected + to the final dimensionality by the ``projecting_net``. + + :param torch.Tensor x: The input tensor for fourier block, + depending on ``dimension`` in the initialization. In + particular it is expected: + + * 1D tensors: ``[batch, X, channels]`` + * 2D tensors: ``[batch, X, Y, channels]`` + * 3D tensors: ``[batch, X, Y, Z, channels]`` + :return: The output tensor obtained from FNO. + :rtype: torch.Tensor + """ + return super().forward(x)
    @@ -264,8 +370,8 @@

    Source code for pina.model.fno

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/layers/convolution_2d.html b/_modules/pina/model/layers/convolution_2d.html index 67963eae..6bdb95d0 100644 --- a/_modules/pina/model/layers/convolution_2d.html +++ b/_modules/pina/model/layers/convolution_2d.html @@ -1,11 +1,13 @@ - + - pina.model.layers.convolution_2d — PINA 0.1 documentation + pina.model.layers.convolution_2d — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.model.layers.convolution_2d

     """Module for Continuous Convolution class"""
    +
     from .convolution import BaseContinuousConv
     from .utils_convolution import check_point, map_points_
     from .integral import Integral
    @@ -120,14 +123,16 @@ 

    Source code for pina.model.layers.convolution_2d

    """ - def __init__(self, - input_numb_field, - output_numb_field, - filter_dim, - stride, - model=None, - optimize=False, - no_overlap=False): + def __init__( + self, + input_numb_field, + output_numb_field, + filter_dim, + stride, + model=None, + optimize=False, + no_overlap=False, + ): """ :param input_numb_field: Number of fields :math:`N_{in}` in the input. :type input_numb_field: int @@ -201,16 +206,18 @@

    Source code for pina.model.layers.convolution_2d

    ) """ - super().__init__(input_numb_field=input_numb_field, - output_numb_field=output_numb_field, - filter_dim=filter_dim, - stride=stride, - model=model, - optimize=optimize, - no_overlap=no_overlap) + super().__init__( + input_numb_field=input_numb_field, + output_numb_field=output_numb_field, + filter_dim=filter_dim, + stride=stride, + model=model, + optimize=optimize, + no_overlap=no_overlap, + ) # integral routine - self._integral = Integral('discrete') + self._integral = Integral("discrete") # create the network self._net = self._spawn_networks(model) @@ -235,15 +242,18 @@

    Source code for pina.model.layers.convolution_2d

    nets.append(tmp) else: if not isinstance(model, object): - raise ValueError("Expected a python class inheriting" - " from torch.nn.Module") + raise ValueError( + "Expected a python class inheriting" " from torch.nn.Module" + ) for _ in range(self._input_numb_field * self._output_numb_field): tmp = model() if not isinstance(tmp, torch.nn.Module): - raise ValueError("The python class must be inherited from" - " torch.nn.Module. See the docstring for" - " an example.") + raise ValueError( + "The python class must be inherited from" + " torch.nn.Module. See the docstring for" + " an example." + ) nets.append(tmp) return torch.nn.ModuleList(nets) @@ -321,11 +331,17 @@

    Source code for pina.model.layers.convolution_2d

    number_points = len(self._stride) # initialize the grid - grid = torch.zeros(size=(X.shape[0], self._output_numb_field, - number_points, filter_dim + 1), - device=X.device, - dtype=X.dtype) - grid[..., :-1] = (self._stride + self._dim * 0.5) + grid = torch.zeros( + size=( + X.shape[0], + self._output_numb_field, + number_points, + filter_dim + 1, + ), + device=X.device, + dtype=X.dtype, + ) + grid[..., :-1] = self._stride + self._dim * 0.5 # saving the grid self._grid = grid.detach() @@ -358,14 +374,14 @@

    Source code for pina.model.layers.convolution_2d

    """ # choose the type of convolution - if type == 'forward': + if type == "forward": return self._make_grid_forward(X) - elif type == 'inverse': + elif type == "inverse": self._make_grid_transpose(X) else: raise TypeError - def _initialize_convolution(self, X, type='forward'): + def _initialize_convolution(self, X, type="forward"): """ Private method to intialize the convolution. The convolution is initialized by setting a grid and @@ -396,10 +412,10 @@

    Source code for pina.model.layers.convolution_2d

    # initialize convolution if self.training: # we choose what to do based on optimization - self._choose_initialization(X, type='forward') + self._choose_initialization(X, type="forward") else: # we always initialize on testing - self._initialize_convolution(X, 'forward') + self._initialize_convolution(X, "forward") # create convolutional array conv = self._grid.clone().detach() @@ -411,7 +427,8 @@

    Source code for pina.model.layers.convolution_2d

    # extract mapped points stacked_input, indeces_channels = self._extract_mapped_points( - batch_idx, self._index, x) + batch_idx, self._index, x + ) # compute the convolution @@ -428,9 +445,11 @@

    Source code for pina.model.layers.convolution_2d

    # calculate filter value staked_output = net(single_channel_input[..., :-1]) # perform integral for all strides in one field - integral = self._integral(staked_output, - single_channel_input[..., -1], - indeces_channels[idx]) + integral = self._integral( + staked_output, + single_channel_input[..., -1], + indeces_channels[idx], + ) res_tmp.append(integral) # stacking integral results @@ -438,9 +457,9 @@

    Source code for pina.model.layers.convolution_2d

    # sum filters (for each input fields) in groups # for different ouput fields - conv[batch_idx, ..., - -1] = res_tmp.reshape(self._output_numb_field, - self._input_numb_field, -1).sum(1) + conv[batch_idx, ..., -1] = res_tmp.reshape( + self._output_numb_field, self._input_numb_field, -1 + ).sum(1) return conv
    [docs] def transpose_no_overlap(self, integrals, X): @@ -471,10 +490,10 @@

    Source code for pina.model.layers.convolution_2d

    # initialize convolution if self.training: # we choose what to do based on optimization - self._choose_initialization(X, type='inverse') + self._choose_initialization(X, type="inverse") else: # we always initialize on testing - self._initialize_convolution(X, 'inverse') + self._initialize_convolution(X, "inverse") # initialize grid X = self._grid_transpose.clone().detach() @@ -487,7 +506,8 @@

    Source code for pina.model.layers.convolution_2d

    # extract mapped points stacked_input, indeces_channels = self._extract_mapped_points( - batch_idx, self._index, x) + batch_idx, self._index, x + ) # compute the transpose convolution @@ -503,8 +523,9 @@

    Source code for pina.model.layers.convolution_2d

    # extract input for each field single_channel_input = stacked_input[idx] rep_idx = torch.tensor(indeces_channels[idx]) - integral = integrals[batch_idx, - idx_in, :].repeat_interleave(rep_idx) + integral = integrals[batch_idx, idx_in, :].repeat_interleave( + rep_idx + ) # extract filter net = self._net[idx_conv] # perform transpose convolution for all strides in one field @@ -515,9 +536,11 @@

    Source code for pina.model.layers.convolution_2d

    # stacking integral results and sum # filters (for each input fields) in groups # for different output fields - res_tmp = torch.stack(res_tmp).reshape(self._input_numb_field, - self._output_numb_field, - -1).sum(0) + res_tmp = ( + torch.stack(res_tmp) + .reshape(self._input_numb_field, self._output_numb_field, -1) + .sum(0) + ) conv_transposed[batch_idx, ..., -1] = res_tmp return conv_transposed
    @@ -549,10 +572,10 @@

    Source code for pina.model.layers.convolution_2d

    # initialize convolution if self.training: # we choose what to do based on optimization - self._choose_initialization(X, type='inverse') + self._choose_initialization(X, type="inverse") else: # we always initialize on testing - self._initialize_convolution(X, 'inverse') + self._initialize_convolution(X, "inverse") # initialize grid X = self._grid_transpose.clone().detach() @@ -568,11 +591,14 @@

    Source code for pina.model.layers.convolution_2d

    # accumulator for the convolution on different batches accumulator_batch = torch.zeros( - size=(self._grid_transpose.shape[1], - self._grid_transpose.shape[2]), + size=( + self._grid_transpose.shape[1], + self._grid_transpose.shape[2], + ), requires_grad=True, device=X.device, - dtype=X.dtype).clone() + dtype=X.dtype, + ).clone() for stride_idx, current_stride in enumerate(self._stride): # indeces of points falling into filter range @@ -611,9 +637,10 @@

    Source code for pina.model.layers.convolution_2d

    staked_output = net(nn_input_pts[idx_channel_out]) # perform integral for all strides in one field - integral = staked_output * integrals[batch_idx, - idx_channel_in, - stride_idx] + integral = ( + staked_output + * integrals[batch_idx, idx_channel_in, stride_idx] + ) # append results res_tmp.append(integral.flatten()) @@ -621,7 +648,7 @@

    Source code for pina.model.layers.convolution_2d

    channel_sum = [] start = 0 for _ in range(self._output_numb_field): - tmp = res_tmp[start:start + self._input_numb_field] + tmp = res_tmp[start : start + self._input_numb_field] tmp = torch.vstack(tmp).sum(dim=0) channel_sum.append(tmp) start += self._input_numb_field @@ -642,8 +669,8 @@

    Source code for pina.model.layers.convolution_2d


    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/layers/fourier.html b/_modules/pina/model/layers/fourier.html index f15258ea..c15edc9e 100644 --- a/_modules/pina/model/layers/fourier.html +++ b/_modules/pina/model/layers/fourier.html @@ -1,11 +1,13 @@ - + - pina.model.layers.fourier — PINA 0.1 documentation + pina.model.layers.fourier — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -91,14 +93,18 @@

    Source code for pina.model.layers.fourier

     import torch.nn as nn
     from ...utils import check_consistency
     
    -from pina.model.layers import SpectralConvBlock1D, SpectralConvBlock2D, SpectralConvBlock3D
    +from pina.model.layers import (
    +    SpectralConvBlock1D,
    +    SpectralConvBlock2D,
    +    SpectralConvBlock3D,
    +)
     
     
     
    [docs]class FourierBlock1D(nn.Module): """ Fourier block implementation for three dimensional input tensor. The combination of Fourier blocks - make up the Fourier Neural Operator + make up the Fourier Neural Operator .. seealso:: @@ -110,11 +116,13 @@

    Source code for pina.model.layers.fourier

     
         """
     
    -    def __init__(self,
    -                 input_numb_fields,
    -                 output_numb_fields,
    -                 n_modes,
    -                 activation=torch.nn.Tanh):
    +    def __init__(
    +        self,
    +        input_numb_fields,
    +        output_numb_fields,
    +        n_modes,
    +        activation=torch.nn.Tanh,
    +    ):
             super().__init__()
             """
             PINA implementation of Fourier block one dimension. The module computes
    @@ -140,17 +148,18 @@ 

    Source code for pina.model.layers.fourier

             self._spectral_conv = SpectralConvBlock1D(
                 input_numb_fields=input_numb_fields,
                 output_numb_fields=output_numb_fields,
    -            n_modes=n_modes)
    +            n_modes=n_modes,
    +        )
             self._activation = activation()
             self._linear = nn.Conv1d(input_numb_fields, output_numb_fields, 1)
     
     
    [docs] def forward(self, x): """ - Forward computation for Fourier Block. It performs a spectral + Forward computation for Fourier Block. It performs a spectral convolution and a linear transformation of the input and sum the results. - :param x: The input tensor for fourier block, expect of size + :param x: The input tensor for fourier block, expect of size ``[batch, input_numb_fields, x]``. :type x: torch.Tensor :return: The output tensor obtained from the @@ -164,7 +173,7 @@

    Source code for pina.model.layers.fourier

         """
         Fourier block implementation for two dimensional
         input tensor. The combination of Fourier blocks
    -    make up the Fourier Neural Operator    
    +    make up the Fourier Neural Operator
     
         .. seealso::
     
    @@ -176,18 +185,20 @@ 

    Source code for pina.model.layers.fourier

     
         """
     
    -    def __init__(self,
    -                 input_numb_fields,
    -                 output_numb_fields,
    -                 n_modes,
    -                 activation=torch.nn.Tanh):
    +    def __init__(
    +        self,
    +        input_numb_fields,
    +        output_numb_fields,
    +        n_modes,
    +        activation=torch.nn.Tanh,
    +    ):
             """
             PINA implementation of Fourier block two dimensions. The module computes
             the spectral convolution of the input with a linear kernel in the
             fourier space, and then it maps the input back to the physical
             space. The output is then added to a Linear tranformation of the
             input in the physical space. Finally an activation function is
    -        applied to the output. 
    +        applied to the output.
     
             The block expects an input of size ``[batch, input_numb_fields, Nx, Ny]``
             and returns an output of size ``[batch, output_numb_fields, Nx, Ny]``.
    @@ -207,17 +218,18 @@ 

    Source code for pina.model.layers.fourier

             self._spectral_conv = SpectralConvBlock2D(
                 input_numb_fields=input_numb_fields,
                 output_numb_fields=output_numb_fields,
    -            n_modes=n_modes)
    +            n_modes=n_modes,
    +        )
             self._activation = activation()
             self._linear = nn.Conv2d(input_numb_fields, output_numb_fields, 1)
     
     
    [docs] def forward(self, x): """ - Forward computation for Fourier Block. It performs a spectral + Forward computation for Fourier Block. It performs a spectral convolution and a linear transformation of the input and sum the results. - :param x: The input tensor for fourier block, expect of size + :param x: The input tensor for fourier block, expect of size ``[batch, input_numb_fields, x, y]``. :type x: torch.Tensor :return: The output tensor obtained from the @@ -231,7 +243,7 @@

    Source code for pina.model.layers.fourier

         """
         Fourier block implementation for three dimensional
         input tensor. The combination of Fourier blocks
    -    make up the Fourier Neural Operator  
    +    make up the Fourier Neural Operator
     
         .. seealso::
     
    @@ -243,18 +255,20 @@ 

    Source code for pina.model.layers.fourier

     
         """
     
    -    def __init__(self,
    -                 input_numb_fields,
    -                 output_numb_fields,
    -                 n_modes,
    -                 activation=torch.nn.Tanh):
    +    def __init__(
    +        self,
    +        input_numb_fields,
    +        output_numb_fields,
    +        n_modes,
    +        activation=torch.nn.Tanh,
    +    ):
             """
             PINA implementation of Fourier block three dimensions. The module computes
             the spectral convolution of the input with a linear kernel in the
             fourier space, and then it maps the input back to the physical
             space. The output is then added to a Linear tranformation of the
             input in the physical space. Finally an activation function is
    -        applied to the output. 
    +        applied to the output.
     
             The block expects an input of size ``[batch, input_numb_fields, Nx, Ny, Nz]``
             and returns an output of size ``[batch, output_numb_fields, Nx, Ny, Nz]``.
    @@ -275,17 +289,18 @@ 

    Source code for pina.model.layers.fourier

             self._spectral_conv = SpectralConvBlock3D(
                 input_numb_fields=input_numb_fields,
                 output_numb_fields=output_numb_fields,
    -            n_modes=n_modes)
    +            n_modes=n_modes,
    +        )
             self._activation = activation()
             self._linear = nn.Conv3d(input_numb_fields, output_numb_fields, 1)
     
     
    [docs] def forward(self, x): """ - Forward computation for Fourier Block. It performs a spectral + Forward computation for Fourier Block. It performs a spectral convolution and a linear transformation of the input and sum the results. - :param x: The input tensor for fourier block, expect of size + :param x: The input tensor for fourier block, expect of size ``[batch, input_numb_fields, x, y, z]``. :type x: torch.Tensor :return: The output tensor obtained from the @@ -302,8 +317,8 @@

    Source code for pina.model.layers.fourier

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/layers/residual.html b/_modules/pina/model/layers/residual.html index 95dc9c84..c9980bf2 100644 --- a/_modules/pina/model/layers/residual.html +++ b/_modules/pina/model/layers/residual.html @@ -1,11 +1,13 @@ - + - pina.model.layers.residual — PINA 0.1 documentation + pina.model.layers.residual — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -105,18 +107,20 @@

    Source code for pina.model.layers.residual

     
         """
     
    -    def __init__(self,
    -                 input_dim,
    -                 output_dim,
    -                 hidden_dim,
    -                 spectral_norm=False,
    -                 activation=torch.nn.ReLU()):
    +    def __init__(
    +        self,
    +        input_dim,
    +        output_dim,
    +        hidden_dim,
    +        spectral_norm=False,
    +        activation=torch.nn.ReLU(),
    +    ):
             """
             Initializes the ResidualBlock module.
     
             :param int input_dim: Dimension of the input to pass to the
                 feedforward linear layer.
    -        :param int output_dim: Dimension of the output from the 
    +        :param int output_dim: Dimension of the output from the
                 residual layer.
             :param int hidden_dim: Hidden dimension for mapping the input
                 (first block).
    @@ -171,6 +175,7 @@ 

    Source code for pina.model.layers.residual

     import torch
     import torch.nn as nn
     
    +
     
    [docs]class EnhancedLinear(torch.nn.Module): """ A wrapper class for enhancing a linear layer with activation and/or dropout. @@ -221,8 +226,9 @@

    Source code for pina.model.layers.residual

                 self._model = torch.nn.Sequential(layer, self._drop(dropout))
     
             elif (dropout is not None) and (activation is not None):
    -            self._model = torch.nn.Sequential(layer, activation,
    -                                              self._drop(dropout))
    +            self._model = torch.nn.Sequential(
    +                layer, activation, self._drop(dropout)
    +            )
     
     
    [docs] def forward(self, x): """ @@ -256,8 +262,8 @@

    Source code for pina.model.layers.residual

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/layers/spectral.html b/_modules/pina/model/layers/spectral.html index a129ff7a..ff8f01d4 100644 --- a/_modules/pina/model/layers/spectral.html +++ b/_modules/pina/model/layers/spectral.html @@ -1,11 +1,13 @@ - + - pina.model.layers.spectral — PINA 0.1 documentation + pina.model.layers.spectral — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -126,18 +128,23 @@

    Source code for pina.model.layers.spectral

             self._output_channels = output_numb_fields
     
             # scaling factor
    -        scale = (1. / (self._input_channels * self._output_channels))
    -        self._weights = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                        self._output_channels,
    -                                                        self._modes,
    -                                                        dtype=torch.cfloat))
    +        scale = 1.0 / (self._input_channels * self._output_channels)
    +        self._weights = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes,
    +                dtype=torch.cfloat,
    +            )
    +        )
     
         def _compute_mult1d(self, input, weights):
             """
             Compute the matrix multiplication of the input
             with the linear kernel weights.
     
    -        :param input: The input tensor, expect of size 
    +        :param input: The input tensor, expect of size
                 ``[batch, input_numb_fields, x]``.
             :type input: torch.Tensor
             :param weights: The kernel weights, expect of
    @@ -153,7 +160,7 @@ 

    Source code for pina.model.layers.spectral

             """
             Forward computation for Spectral Convolution.
     
    -        :param x: The input tensor, expect of size 
    +        :param x: The input tensor, expect of size
                 ``[batch, input_numb_fields, x]``.
             :type x: torch.Tensor
             :return: The output tensor obtained from the
    @@ -166,13 +173,16 @@ 

    Source code for pina.model.layers.spectral

             x_ft = torch.fft.rfft(x)
     
             # Multiply relevant Fourier modes
    -        out_ft = torch.zeros(batch_size,
    -                             self._output_channels,
    -                             x.size(-1) // 2 + 1,
    -                             device=x.device,
    -                             dtype=torch.cfloat)
    -        out_ft[:, :, :self._modes] = self._compute_mult1d(
    -            x_ft[:, :, :self._modes], self._weights)
    +        out_ft = torch.zeros(
    +            batch_size,
    +            self._output_channels,
    +            x.size(-1) // 2 + 1,
    +            device=x.device,
    +            dtype=torch.cfloat,
    +        )
    +        out_ft[:, :, : self._modes] = self._compute_mult1d(
    +            x_ft[:, :, : self._modes], self._weights
    +        )
     
             # Return to physical space
             return torch.fft.irfft(out_ft, n=x.size(-1))
    @@ -208,17 +218,19 @@

    Source code for pina.model.layers.spectral

             if isinstance(n_modes, (tuple, list)):
                 if len(n_modes) != 2:
                     raise ValueError(
    -                    'Expected n_modes to be a list or tuple of len two, '
    -                    'with each entry corresponding to the number of modes '
    -                    'for each dimension ')
    +                    "Expected n_modes to be a list or tuple of len two, "
    +                    "with each entry corresponding to the number of modes "
    +                    "for each dimension "
    +                )
             elif isinstance(n_modes, int):
                 n_modes = [n_modes] * 2
             else:
                 raise ValueError(
    -                'Expected n_modes to be a list or tuple of len two, '
    -                'with each entry corresponding to the number of modes '
    -                'for each dimension; or an int value representing the '
    -                'number of modes for all dimensions')
    +                "Expected n_modes to be a list or tuple of len two, "
    +                "with each entry corresponding to the number of modes "
    +                "for each dimension; or an int value representing the "
    +                "number of modes for all dimensions"
    +            )
     
             # assign variables
             self._modes = n_modes
    @@ -226,24 +238,34 @@ 

    Source code for pina.model.layers.spectral

             self._output_channels = output_numb_fields
     
             # scaling factor
    -        scale = (1. / (self._input_channels * self._output_channels))
    -        self._weights1 = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                         self._output_channels,
    -                                                         self._modes[0],
    -                                                         self._modes[1],
    -                                                         dtype=torch.cfloat))
    -        self._weights2 = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                         self._output_channels,
    -                                                         self._modes[0],
    -                                                         self._modes[1],
    -                                                         dtype=torch.cfloat))
    +        scale = 1.0 / (self._input_channels * self._output_channels)
    +        self._weights1 = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes[0],
    +                self._modes[1],
    +                dtype=torch.cfloat,
    +            )
    +        )
    +        self._weights2 = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes[0],
    +                self._modes[1],
    +                dtype=torch.cfloat,
    +            )
    +        )
     
         def _compute_mult2d(self, input, weights):
             """
             Compute the matrix multiplication of the input
             with the linear kernel weights.
     
    -        :param input: The input tensor, expect of size 
    +        :param input: The input tensor, expect of size
                 ``[batch, input_numb_fields, x, y]``.
             :type input: torch.Tensor
             :param weights: The kernel weights, expect of
    @@ -259,7 +281,7 @@ 

    Source code for pina.model.layers.spectral

             """
             Forward computation for Spectral Convolution.
     
    -        :param x: The input tensor, expect of size 
    +        :param x: The input tensor, expect of size
                 ``[batch, input_numb_fields, x, y]``.
             :type x: torch.Tensor
             :return: The output tensor obtained from the
    @@ -273,16 +295,22 @@ 

    Source code for pina.model.layers.spectral

             x_ft = torch.fft.rfft2(x)
     
             # Multiply relevant Fourier modes
    -        out_ft = torch.zeros(batch_size,
    -                             self._output_channels,
    -                             x.size(-2),
    -                             x.size(-1) // 2 + 1,
    -                             device=x.device,
    -                             dtype=torch.cfloat)
    -        out_ft[:, :, :self._modes[0], :self._modes[1]] = self._compute_mult2d(
    -            x_ft[:, :, :self._modes[0], :self._modes[1]], self._weights1)
    -        out_ft[:, :, -self._modes[0]:, :self._modes[1]:] = self._compute_mult2d(
    -            x_ft[:, :, -self._modes[0]:, :self._modes[1]], self._weights2)
    +        out_ft = torch.zeros(
    +            batch_size,
    +            self._output_channels,
    +            x.size(-2),
    +            x.size(-1) // 2 + 1,
    +            device=x.device,
    +            dtype=torch.cfloat,
    +        )
    +        out_ft[:, :, : self._modes[0], : self._modes[1]] = self._compute_mult2d(
    +            x_ft[:, :, : self._modes[0], : self._modes[1]], self._weights1
    +        )
    +        out_ft[:, :, -self._modes[0] :, : self._modes[1] :] = (
    +            self._compute_mult2d(
    +                x_ft[:, :, -self._modes[0] :, : self._modes[1]], self._weights2
    +            )
    +        )
     
             # Return to physical space
             return torch.fft.irfft2(out_ft, s=(x.size(-2), x.size(-1)))
    @@ -319,17 +347,19 @@

    Source code for pina.model.layers.spectral

             if isinstance(n_modes, (tuple, list)):
                 if len(n_modes) != 3:
                     raise ValueError(
    -                    'Expected n_modes to be a list or tuple of len three, '
    -                    'with each entry corresponding to the number of modes '
    -                    'for each dimension ')
    +                    "Expected n_modes to be a list or tuple of len three, "
    +                    "with each entry corresponding to the number of modes "
    +                    "for each dimension "
    +                )
             elif isinstance(n_modes, int):
                 n_modes = [n_modes] * 3
             else:
                 raise ValueError(
    -                'Expected n_modes to be a list or tuple of len three, '
    -                'with each entry corresponding to the number of modes '
    -                'for each dimension; or an int value representing the '
    -                'number of modes for all dimensions')
    +                "Expected n_modes to be a list or tuple of len three, "
    +                "with each entry corresponding to the number of modes "
    +                "for each dimension; or an int value representing the "
    +                "number of modes for all dimensions"
    +            )
     
             # assign variables
             self._modes = n_modes
    @@ -337,38 +367,58 @@ 

    Source code for pina.model.layers.spectral

             self._output_channels = output_numb_fields
     
             # scaling factor
    -        scale = (1. / (self._input_channels * self._output_channels))
    -        self._weights1 = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                         self._output_channels,
    -                                                         self._modes[0],
    -                                                         self._modes[1],
    -                                                         self._modes[2],
    -                                                         dtype=torch.cfloat))
    -        self._weights2 = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                         self._output_channels,
    -                                                         self._modes[0],
    -                                                         self._modes[1],
    -                                                         self._modes[2],
    -                                                         dtype=torch.cfloat))
    -        self._weights3 = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                         self._output_channels,
    -                                                         self._modes[0],
    -                                                         self._modes[1],
    -                                                         self._modes[2],
    -                                                         dtype=torch.cfloat))
    -        self._weights4 = nn.Parameter(scale * torch.rand(self._input_channels,
    -                                                         self._output_channels,
    -                                                         self._modes[0],
    -                                                         self._modes[1],
    -                                                         self._modes[2],
    -                                                         dtype=torch.cfloat))
    +        scale = 1.0 / (self._input_channels * self._output_channels)
    +        self._weights1 = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes[0],
    +                self._modes[1],
    +                self._modes[2],
    +                dtype=torch.cfloat,
    +            )
    +        )
    +        self._weights2 = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes[0],
    +                self._modes[1],
    +                self._modes[2],
    +                dtype=torch.cfloat,
    +            )
    +        )
    +        self._weights3 = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes[0],
    +                self._modes[1],
    +                self._modes[2],
    +                dtype=torch.cfloat,
    +            )
    +        )
    +        self._weights4 = nn.Parameter(
    +            scale
    +            * torch.rand(
    +                self._input_channels,
    +                self._output_channels,
    +                self._modes[0],
    +                self._modes[1],
    +                self._modes[2],
    +                dtype=torch.cfloat,
    +            )
    +        )
     
         def _compute_mult3d(self, input, weights):
             """
             Compute the matrix multiplication of the input
             with the linear kernel weights.
     
    -        :param input: The input tensor, expect of size 
    +        :param input: The input tensor, expect of size
                 ``[batch, input_numb_fields, x, y, z]``.
             :type input: torch.Tensor
             :param weights: The kernel weights, expect of
    @@ -384,7 +434,7 @@ 

    Source code for pina.model.layers.spectral

             """
             Forward computation for Spectral Convolution.
     
    -        :param x: The input tensor, expect of size 
    +        :param x: The input tensor, expect of size
                 ``[batch, input_numb_fields, x, y, z]``.
             :type x: torch.Tensor
             :return: The output tensor obtained from the
    @@ -398,13 +448,15 @@ 

    Source code for pina.model.layers.spectral

             x_ft = torch.fft.rfftn(x, dim=[-3, -2, -1])
     
             # Multiply relevant Fourier modes
    -        out_ft = torch.zeros(batch_size,
    -                             self._output_channels,
    -                             x.size(-3),
    -                             x.size(-2),
    -                             x.size(-1) // 2 + 1,
    -                             device=x.device,
    -                             dtype=torch.cfloat)
    +        out_ft = torch.zeros(
    +            batch_size,
    +            self._output_channels,
    +            x.size(-3),
    +            x.size(-2),
    +            x.size(-1) // 2 + 1,
    +            device=x.device,
    +            dtype=torch.cfloat,
    +        )
     
             slice0 = (
                 slice(None),
    @@ -453,8 +505,8 @@ 

    Source code for pina.model.layers.spectral

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/multi_feed_forward.html b/_modules/pina/model/multi_feed_forward.html index 9b4a1a7e..268ce891 100644 --- a/_modules/pina/model/multi_feed_forward.html +++ b/_modules/pina/model/multi_feed_forward.html @@ -1,11 +1,13 @@ - + - pina.model.multi_feed_forward — PINA 0.1 documentation + pina.model.multi_feed_forward — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.model.multi_feed_forward

     """Module for Multi FeedForward model"""
    +
     import torch
     
     from .feed_forward import FeedForward
    @@ -95,7 +98,7 @@ 

    Source code for pina.model.multi_feed_forward

    [docs]class MultiFeedForward(torch.nn.Module): """ - The PINA implementation of MultiFeedForward network. + The PINA implementation of MultiFeedForward network. This model allows to create a network with multiple FeedForward combined together. The user has to define the `forward` method choosing how to @@ -121,8 +124,8 @@

    Source code for pina.model.multi_feed_forward

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/model/network.html b/_modules/pina/model/network.html index f983b6f6..e1faa69d 100644 --- a/_modules/pina/model/network.html +++ b/_modules/pina/model/network.html @@ -1,11 +1,13 @@ - + - pina.model.network — PINA 0.1 documentation + pina.model.network — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -95,13 +97,15 @@

    Source code for pina.model.network

     
     
    [docs]class Network(torch.nn.Module): - def __init__(self, model, input_variables, output_variables, extra_features=None): + def __init__( + self, model, input_variables, output_variables, extra_features=None + ): """ Network class with standard forward method and possibility to pass extra features. This class is used internally in PINA to convert any :class:`torch.nn.Module` s in a PINA module. - + :param model: The torch model to convert in a PINA model. :type model: torch.nn.Module :param list(str) input_variables: The input variables of the :class:`AbstractProblem`, whose type depends on the @@ -146,7 +150,9 @@

    Source code for pina.model.network

             :return torch.Tensor: Output of the network.
             """
             # only labeltensors as input
    -        assert isinstance(x, LabelTensor), "Expected LabelTensor as input to the model."
    +        assert isinstance(
    +            x, LabelTensor
    +        ), "Expected LabelTensor as input to the model."
     
             # extract torch.Tensor from corresponding label
             # in case `input_variables = []` all points are used
    @@ -164,7 +170,7 @@ 

    Source code for pina.model.network

             output.labels = self._output_variables
     
             return output
    - + # TODO to remove in next releases (only used in GAROM solver)
    [docs] def forward_map(self, x): """ @@ -209,8 +215,8 @@

    Source code for pina.model.network

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/operators.html b/_modules/pina/operators.html index 49b90130..814a5e1f 100644 --- a/_modules/pina/operators.html +++ b/_modules/pina/operators.html @@ -1,11 +1,13 @@ - + - pina.operators — PINA 0.1 documentation + pina.operators — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -94,6 +96,7 @@

    Source code for pina.operators

     to which computing the operator, the name of the output variables to calculate the operator
     for (in case of multidimensional functions), and the variables name on which the operator is calculated.
     """
    +
     import torch
     
     from pina.label_tensor import LabelTensor
    @@ -138,24 +141,25 @@ 

    Source code for pina.operators

             """
     
             if len(output_.labels) != 1:
    -            raise RuntimeError('only scalar function can be differentiated')
    +            raise RuntimeError("only scalar function can be differentiated")
             if not all([di in input_.labels for di in d]):
    -            raise RuntimeError('derivative labels missing from input tensor')
    +            raise RuntimeError("derivative labels missing from input tensor")
     
             output_fieldname = output_.labels[0]
    -        gradients = torch.autograd.grad(output_,
    -                                        input_,
    -                                        grad_outputs=torch.ones(
    -                                            output_.size(),
    -                                            dtype=output_.dtype,
    -                                            device=output_.device),
    -                                        create_graph=True,
    -                                        retain_graph=True,
    -                                        allow_unused=True)[0]
    +        gradients = torch.autograd.grad(
    +            output_,
    +            input_,
    +            grad_outputs=torch.ones(
    +                output_.size(), dtype=output_.dtype, device=output_.device
    +            ),
    +            create_graph=True,
    +            retain_graph=True,
    +            allow_unused=True,
    +        )[0]
     
             gradients.labels = input_.labels
             gradients = gradients.extract(d)
    -        gradients.labels = [f'd{output_fieldname}d{i}' for i in d]
    +        gradients.labels = [f"d{output_fieldname}d{i}" for i in d]
     
             return gradients
     
    @@ -182,7 +186,8 @@ 

    Source code for pina.operators

                     gradients = grad_scalar_output(c_output, input_, d)
                 else:
                     gradients = gradients.append(
    -                    grad_scalar_output(c_output, input_, d))
    +                    grad_scalar_output(c_output, input_, d)
    +                )
         else:
             raise NotImplementedError
     
    @@ -222,7 +227,7 @@ 

    Source code for pina.operators

             components = output_.labels
     
         if output_.shape[1] < 2 or len(components) < 2:
    -        raise ValueError('div supported only for vector fields')
    +        raise ValueError("div supported only for vector fields")
     
         if len(components) != len(d):
             raise ValueError
    @@ -231,16 +236,16 @@ 

    Source code for pina.operators

         div = torch.zeros(input_.shape[0], 1, device=output_.device)
         labels = [None] * len(components)
         for i, (c, d) in enumerate(zip(components, d)):
    -        c_fields = f'd{c}d{d}'
    +        c_fields = f"d{c}d{d}"
             div[:, 0] += grad_output.extract(c_fields).sum(axis=1)
             labels[i] = c_fields
     
         div = div.as_subclass(LabelTensor)
    -    div.labels = ['+'.join(labels)]
    +    div.labels = ["+".join(labels)]
         return div
    -
    [docs]def laplacian(output_, input_, components=None, d=None, method='std'): +
    [docs]def laplacian(output_, input_, components=None, d=None, method="std"): """ Compute Laplace operator. The operator works for vectorial and scalar functions, with multiple input coordinates. @@ -271,26 +276,27 @@

    Source code for pina.operators

         if len(components) != len(d) and len(components) != 1:
             raise ValueError
     
    -    if method == 'divgrad':
    -        raise NotImplementedError('divgrad not implemented as method')
    +    if method == "divgrad":
    +        raise NotImplementedError("divgrad not implemented as method")
             # TODO fix
             # grad_output = grad(output_, input_, components, d)
             # result = div(grad_output, input_, d=d)
    -    elif method == 'std':
    +    elif method == "std":
     
             if len(components) == 1:
                 grad_output = grad(output_, input_, components=components, d=d)
                 result = torch.zeros(output_.shape[0], 1, device=output_.device)
                 for i, label in enumerate(grad_output.labels):
                     gg = grad(grad_output, input_, d=d, components=[label])
    -                result[:, 0] += super(torch.Tensor,
    -                                      gg.T).__getitem__(i)  # TODO improve
    -            labels = [f'dd{components[0]}']
    +                result[:, 0] += super(torch.Tensor, gg.T).__getitem__(
    +                    i
    +                )  # TODO improve
    +            labels = [f"dd{components[0]}"]
     
             else:
    -            result = torch.empty(input_.shape[0],
    -                                 len(components),
    -                                 device=output_.device)
    +            result = torch.empty(
    +                input_.shape[0], len(components), device=output_.device
    +            )
                 labels = [None] * len(components)
                 for idx, (ci, di) in enumerate(zip(components, d)):
     
    @@ -301,7 +307,7 @@ 

    Source code for pina.operators

     
                     grad_output = grad(output_, input_, components=ci, d=di)
                     result[:, idx] = grad(grad_output, input_, d=di).flatten()
    -                labels[idx] = f'dd{ci}dd{di}'
    +                labels[idx] = f"dd{ci}dd{di}"
     
         result = result.as_subclass(LabelTensor)
         result.labels = labels
    @@ -334,8 +340,11 @@ 

    Source code for pina.operators

         if components is None:
             components = output_.labels
     
    -    tmp = grad(output_, input_, components, d).reshape(-1, len(components),
    -                                                       len(d)).transpose(0, 1)
    +    tmp = (
    +        grad(output_, input_, components, d)
    +        .reshape(-1, len(components), len(d))
    +        .transpose(0, 1)
    +    )
     
         tmp *= output_.extract(velocity_field)
         return tmp.sum(dim=2).T
    @@ -348,8 +357,8 @@

    Source code for pina.operators

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/plotter.html b/_modules/pina/plotter.html index 2d546b51..c4ff6f0b 100644 --- a/_modules/pina/plotter.html +++ b/_modules/pina/plotter.html @@ -1,11 +1,13 @@ - + - pina.plotter — PINA 0.1 documentation + pina.plotter — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -100,14 +102,17 @@

    Source code for pina.plotter

         Implementation of a plotter class, for easy visualizations.
         """
     
    -
    [docs] def plot_samples(self, problem, variables=None, **kwargs): +
    [docs] def plot_samples(self, problem, variables=None, filename=None, **kwargs): """ Plot the training grid samples. - :param SolverInterface solver: The ``SolverInterface`` object. + :param AbstractProblem problem: The PINA problem from where to plot + the domain. :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. + :param str filename: The file name to save the plot. If None, the plot + is shown using the setted matplotlib frontend. Default is None. .. todo:: - Add support for 3D plots. @@ -120,49 +125,61 @@

    Source code for pina.plotter

     
             if variables is None:
                 variables = problem.domain.variables
    -        elif variables == 'spatial':
    +        elif variables == "spatial":
                 variables = problem.spatial_domain.variables
    -        elif variables == 'temporal':
    +        elif variables == "temporal":
                 variables = problem.temporal_domain.variables
     
             if len(variables) not in [1, 2, 3]:
    -            raise ValueError
    +            raise ValueError(
    +                "Samples can be plotted only in " "dimensions 1, 2 and 3."
    +            )
     
             fig = plt.figure()
    -        proj = '3d' if len(variables) == 3 else None
    +        proj = "3d" if len(variables) == 3 else None
             ax = fig.add_subplot(projection=proj)
             for location in problem.input_pts:
                 coords = problem.input_pts[location].extract(variables).T.detach()
    -            if coords.shape[0] == 1:  # 1D samples
    -                ax.plot(coords.flatten(),
    -                        torch.zeros(coords.flatten().shape),
    -                        '.',
    -                        label=location,
    -                        **kwargs)
    -            else:
    -                ax.plot(*coords, '.', label=location, **kwargs)
    +            if len(variables) == 1:  # 1D samples
    +                ax.plot(
    +                    coords.flatten(),
    +                    torch.zeros(coords.flatten().shape),
    +                    ".",
    +                    label=location,
    +                    **kwargs,
    +                )
    +            elif len(variables) == 2:
    +                ax.plot(*coords, ".", label=location, **kwargs)
    +            elif len(variables) == 3:
    +                ax.scatter(*coords, ".", label=location, **kwargs)
     
             ax.set_xlabel(variables[0])
             try:
                 ax.set_ylabel(variables[1])
    -        except:
    +        except (IndexError, AttributeError):
                 pass
     
             try:
                 ax.set_zlabel(variables[2])
    -        except:
    +        except (IndexError, AttributeError):
                 pass
     
             plt.legend()
    -        plt.show()
    + if filename: + plt.savefig(filename) + plt.close() + else: + plt.show()
    - def _1d_plot(self, pts, pred, method, truth_solution=None, **kwargs): + def _1d_plot(self, pts, pred, v, method, truth_solution=None, **kwargs): """Plot solution for one dimensional function :param pts: Points to plot the solution. :type pts: torch.Tensor :param pred: SolverInterface solution evaluated at 'pts'. :type pred: torch.Tensor + :param v: Fixed variables when plotting the solution. + :type v: torch.Tensor :param method: Not used, kept for code compatibility :type method: None :param truth_solution: Real solution evaluated at 'pts', @@ -171,33 +188,31 @@

    Source code for pina.plotter

             """
             fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 8))
     
    -        ax.plot(pts, pred.detach(), label='Neural Network solution', **kwargs)
    +        ax.plot(pts.extract(v), pred, label="Neural Network solution", **kwargs)
     
             if truth_solution:
    -            truth_output = truth_solution(pts).float()
    -            ax.plot(pts, truth_output.detach(), label='True solution', **kwargs)
    +            truth_output = truth_solution(pts).detach()
    +            ax.plot(
    +                pts.extract(v), truth_output, label="True solution", **kwargs
    +            )
     
             # TODO: pred is a torch.Tensor, so no labels is available
             #      extra variable for labels should be
             #      passed in the function arguments.
    -        # plt.ylabel(pred.labels[0]) 
    +        # plt.ylabel(pred.labels[0])
             plt.legend()
    -        plt.show()
    -
    -    def _2d_plot(self,
    -                 pts,
    -                 pred,
    -                 v,
    -                 res,
    -                 method,
    -                 truth_solution=None,
    -                 **kwargs):
    +
    +    def _2d_plot(
    +        self, pts, pred, v, res, method, truth_solution=None, **kwargs
    +    ):
             """Plot solution for two dimensional function
     
             :param pts: Points to plot the solution.
             :type pts: torch.Tensor
             :param pred: ``SolverInterface`` solution evaluated at 'pts'.
             :type pred: torch.Tensor
    +        :param v: Fixed variables when plotting the solution.
    +        :type v: torch.Tensor
             :param method: Matplotlib method to plot 2-dimensional data,
                 see https://matplotlib.org/stable/api/axes_api.html for
                 reference.
    @@ -207,48 +222,52 @@ 

    Source code for pina.plotter

             :type truth_solution: torch.Tensor, optional
             """
     
    -        grids = [p_.reshape(res, res) for p_ in pts.extract(v).cpu().T]
    +        grids = [p_.reshape(res, res) for p_ in pts.extract(v).T]
     
             pred_output = pred.reshape(res, res)
             if truth_solution:
    -            truth_output = truth_solution(pts).float().reshape(res, res).as_subclass(torch.Tensor)
    +            truth_output = (
    +                truth_solution(pts)
    +                .float()
    +                .reshape(res, res)
    +                .as_subclass(torch.Tensor)
    +            )
                 fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(16, 6))
     
    -            cb = getattr(ax[0], method)(*grids, pred_output.cpu().detach(),
    -                                        **kwargs)
    +            cb = getattr(ax[0], method)(*grids, pred_output, **kwargs)
                 fig.colorbar(cb, ax=ax[0])
    -            ax[0].title.set_text('Neural Network prediction')
    -            cb = getattr(ax[1], method)(*grids, truth_output.cpu().detach(),
    -                                        **kwargs)
    +            ax[0].title.set_text("Neural Network prediction")
    +            cb = getattr(ax[1], method)(*grids, truth_output, **kwargs)
                 fig.colorbar(cb, ax=ax[1])
    -            ax[1].title.set_text('True solution')
    -            cb = getattr(ax[2],
    -                         method)(*grids,
    -                                 (truth_output - pred_output).cpu().detach(),
    -                                 **kwargs)
    +            ax[1].title.set_text("True solution")
    +            cb = getattr(ax[2], method)(
    +                *grids, (truth_output - pred_output), **kwargs
    +            )
                 fig.colorbar(cb, ax=ax[2])
    -            ax[2].title.set_text('Residual')
    +            ax[2].title.set_text("Residual")
             else:
                 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 6))
    -            cb = getattr(ax, method)(*grids, pred_output.cpu().detach(),
    -                                     **kwargs)
    +            cb = getattr(ax, method)(*grids, pred_output, **kwargs)
                 fig.colorbar(cb, ax=ax)
    -            ax.title.set_text('Neural Network prediction')
    -
    -
    [docs] def plot(self, - solver, - components=None, - fixed_variables={}, - method='contourf', - res=256, - filename=None, - **kwargs): + ax.title.set_text("Neural Network prediction") + +
    [docs] def plot( + self, + solver, + components=None, + fixed_variables={}, + method="contourf", + res=256, + filename=None, + **kwargs, + ): """ Plot sample of SolverInterface output. :param SolverInterface solver: The ``SolverInterface`` object instance. - :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 str | list(str) components: The output variable(s) 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 @@ -264,15 +283,28 @@

    Source code for pina.plotter

     
             if components is None:
                 components = solver.problem.output_variables
    -        
    +
    +        if isinstance(components, str):
    +            components = [components]
    +
    +        if not isinstance(components, list):
    +            raise NotImplementedError(
    +                "Output variables must be passed"
    +                "as a string or a list of strings."
    +            )
    +
             if len(components) > 1:
    -            raise NotImplementedError('Multidimensional plots are not implemented, '
    -                                      'set components to an available components of the problem.')
    +            raise NotImplementedError(
    +                "Multidimensional plots are not implemented, "
    +                "set components to an available components of"
    +                " the problem."
    +            )
             v = [
    -            var for var in solver.problem.input_variables
    +            var
    +            for var in solver.problem.input_variables
                 if var not in fixed_variables.keys()
             ]
    -        pts = solver.problem.domain.sample(res, 'grid', variables=v)
    +        pts = solver.problem.domain.sample(res, "grid", variables=v)
     
             fixed_pts = torch.ones(pts.shape[0], len(fixed_variables))
             fixed_pts *= torch.tensor(list(fixed_variables.values()))
    @@ -282,29 +314,39 @@ 

    Source code for pina.plotter

             pts = pts.append(fixed_pts)
             pts = pts.to(device=solver.device)
     
    -        predicted_output = solver.forward(pts).extract(components).as_subclass(torch.Tensor)
    -        truth_solution = getattr(solver.problem, 'truth_solution', None)
    +        # computing soluting and sending to cpu
    +        predicted_output = solver.forward(pts).extract(components)
    +        predicted_output = (
    +            predicted_output.as_subclass(torch.Tensor).cpu().detach()
    +        )
    +        pts = pts.cpu()
    +        truth_solution = getattr(solver.problem, "truth_solution", None)
     
             if len(v) == 1:
    -            self._1d_plot(pts.extract(v), predicted_output, method, truth_solution,
    -                          **kwargs)
    +            self._1d_plot(
    +                pts, predicted_output, v, method, truth_solution, **kwargs
    +            )
             elif len(v) == 2:
    -            self._2d_plot(pts, predicted_output, v, res, method, truth_solution,
    -                          **kwargs)
    +            self._2d_plot(
    +                pts, predicted_output, v, res, method, truth_solution, **kwargs
    +            )
     
             plt.tight_layout()
             if filename:
                 plt.savefig(filename)
    +            plt.close()
             else:
                 plt.show()
    -
    [docs] def plot_loss(self, - trainer, - metrics=None, - logy=False, - logx=False, - filename=None, - **kwargs): +
    [docs] def plot_loss( + self, + trainer, + metrics=None, + logy=False, + logx=False, + filename=None, + **kwargs, + ): """ Plot the loss function values during traininig. @@ -322,45 +364,48 @@

    Source code for pina.plotter

     
             # check that MetricTracker has been used
             list_ = [
    -            idx for idx, s in enumerate(trainer.callbacks)
    +            idx
    +            for idx, s in enumerate(trainer.callbacks)
                 if isinstance(s, MetricTracker)
             ]
             if not bool(list_):
                 raise FileNotFoundError(
    -                'MetricTracker should be used as a callback during training to'
    -                ' use this method.')
    +                "MetricTracker should be used as a callback during training to"
    +                " use this method."
    +            )
     
             # extract trainer metrics
             trainer_metrics = trainer.callbacks[list_[0]].metrics
             if metrics is None:
    -            metrics = ['mean_loss']
    +            metrics = ["mean_loss"]
             elif not isinstance(metrics, list):
    -            raise ValueError('metrics must be class list.')
    +            raise ValueError("metrics must be class list.")
     
             # loop over metrics to plot
             for metric in metrics:
                 if metric not in trainer_metrics:
                     raise ValueError(
    -                    f'{metric} not a valid metric. Available metrics are {list(trainer_metrics.keys())}.'
    +                    f"{metric} not a valid metric. Available metrics are {list(trainer_metrics.keys())}."
                     )
                 loss = trainer_metrics[metric]
                 epochs = range(len(loss))
    -            plt.plot(epochs, loss, **kwargs)
    +            plt.plot(epochs, loss.cpu(), **kwargs)
     
             # plotting
    -        plt.xlabel('epoch')
    -        plt.ylabel('loss')
    +        plt.xlabel("epoch")
    +        plt.ylabel("loss")
             plt.legend()
     
             # log axis
             if logy:
    -            plt.yscale('log')
    +            plt.yscale("log")
             if logx:
    -            plt.xscale('log')
    +            plt.xscale("log")
     
             # saving in file
             if filename:
    -            plt.savefig(filename)
    + plt.savefig(filename) + plt.close()
    @@ -370,8 +415,8 @@

    Source code for pina.plotter

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/problem/abstract_problem.html b/_modules/pina/problem/abstract_problem.html index 557b270e..236bb7ff 100644 --- a/_modules/pina/problem/abstract_problem.html +++ b/_modules/pina/problem/abstract_problem.html @@ -1,11 +1,13 @@ - + - pina.problem.abstract_problem — PINA 0.1 documentation + pina.problem.abstract_problem — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.problem.abstract_problem

     """ Module for AbstractProblem class """
    +
     from abc import ABCMeta, abstractmethod
     from ..utils import merge_tensors, check_consistency
     import torch
    @@ -129,13 +132,13 @@ 

    Source code for pina.problem.abstract_problem

    """ variables = [] - if hasattr(self, 'spatial_variables'): + if hasattr(self, "spatial_variables"): variables += self.spatial_variables - if hasattr(self, 'temporal_variable'): + if hasattr(self, "temporal_variable"): variables += self.temporal_variable - if hasattr(self, 'parameters'): + if hasattr(self, "parameters"): variables += self.parameters - if hasattr(self, 'custom_variables'): + if hasattr(self, "custom_variables"): variables += self.custom_variables return variables @@ -151,9 +154,9 @@

    Source code for pina.problem.abstract_problem

    :rtype: list[Location] """ domains = [ - getattr(self, f'{t}_domain') - for t in ['spatial', 'temporal', 'parameter'] - if hasattr(self, f'{t}_domain') + getattr(self, f"{t}_domain") + for t in ["spatial", "temporal", "parameter"] + if hasattr(self, f"{t}_domain") ] if len(domains) == 1: @@ -166,7 +169,7 @@

    Source code for pina.problem.abstract_problem

    [domain.update(d) for d in domains] return domain else: - raise RuntimeError('different domains') + raise RuntimeError("different domains") @input_variables.setter def input_variables(self, variables): @@ -194,24 +197,27 @@

    Source code for pina.problem.abstract_problem

    """ for condition_name in self.conditions: condition = self.conditions[condition_name] - if hasattr(condition, 'input_points'): + if hasattr(condition, "input_points"): samples = condition.input_points self.input_pts[condition_name] = samples self._have_sampled_points[condition_name] = True - if hasattr(self, 'unknown_parameter_domain'): + if hasattr(self, "unknown_parameter_domain"): # initialize the unknown parameters of the inverse problem given # the domain the user gives self.unknown_parameters = {} for i, var in enumerate(self.unknown_variables): range_var = self.unknown_parameter_domain.range_[var] - tensor_var = torch.rand(1, requires_grad=True) * range_var[1] + range_var[0] - self.unknown_parameters[var] = torch.nn.Parameter(tensor_var) - -
    [docs] def discretise_domain(self, - n, - mode='random', - variables='all', - locations='all'): + tensor_var = ( + torch.rand(1, requires_grad=True) * range_var[1] + + range_var[0] + ) + self.unknown_parameters[var] = torch.nn.Parameter( + tensor_var + ) + +
    [docs] def discretise_domain( + self, n, mode="random", variables="all", locations="all" + ): """ Generate a set of points to span the `Location` of all the conditions of the problem. @@ -246,28 +252,32 @@

    Source code for pina.problem.abstract_problem

    # check consistency mode check_consistency(mode, str) - if mode not in ['random', 'grid', 'lh', 'chebyshev', 'latin']: - raise TypeError(f'mode {mode} not valid.') + if mode not in ["random", "grid", "lh", "chebyshev", "latin"]: + raise TypeError(f"mode {mode} not valid.") # check consistency variables - if variables == 'all': + if variables == "all": variables = self.input_variables else: check_consistency(variables, str) if sorted(variables) != sorted(self.input_variables): - TypeError(f'Wrong variables for sampling. Variables ', - f'should be in {self.input_variables}.') + TypeError( + f"Wrong variables for sampling. Variables ", + f"should be in {self.input_variables}.", + ) # check consistency location - if locations == 'all': + if locations == "all": locations = [condition for condition in self.conditions] else: check_consistency(locations, str) if sorted(locations) != sorted(self.conditions): - TypeError(f'Wrong locations for sampling. Location ', - f'should be in {self.conditions}.') + TypeError( + f"Wrong locations for sampling. Location ", + f"should be in {self.conditions}.", + ) # sampling for location in locations: @@ -297,10 +307,10 @@

    Source code for pina.problem.abstract_problem

    # the condition is sampled if input_pts contains all labels if sorted(self.input_pts[location].labels) == sorted( - self.input_variables): + self.input_variables + ): self._have_sampled_points[location] = True
    -
    [docs] def add_points(self, new_points): """ Adding points to the already sampled points. @@ -310,8 +320,10 @@

    Source code for pina.problem.abstract_problem

    """ if sorted(new_points.keys()) != sorted(self.conditions): - TypeError(f'Wrong locations for new points. Location ', - f'should be in {self.conditions}.') + TypeError( + f"Wrong locations for new points. Location ", + f"should be in {self.conditions}.", + ) for location in new_points.keys(): # extract old and new points @@ -320,11 +332,14 @@

    Source code for pina.problem.abstract_problem

    # if they don't have the same variables error if sorted(old_pts.labels) != sorted(new_pts.labels): - TypeError(f'Not matching variables for old and new points ' - f'in condition {location}.') + TypeError( + f"Not matching variables for old and new points " + f"in condition {location}." + ) if old_pts.labels != new_pts.labels: new_pts = torch.hstack( - [new_pts.extract([i]) for i in old_pts.labels]) + [new_pts.extract([i]) for i in old_pts.labels] + ) new_pts.labels = old_pts.labels # merging @@ -355,7 +370,6 @@

    Source code for pina.problem.abstract_problem

    if not is_sample: not_sampled.append(condition_name) return not_sampled
    -
    @@ -365,8 +379,8 @@

    Source code for pina.problem.abstract_problem

    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/problem/parametric_problem.html b/_modules/pina/problem/parametric_problem.html index 4f7ca0a0..706ec56a 100644 --- a/_modules/pina/problem/parametric_problem.html +++ b/_modules/pina/problem/parametric_problem.html @@ -1,11 +1,13 @@ - + - pina.problem.parametric_problem — PINA 0.1 documentation + pina.problem.parametric_problem — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.problem.parametric_problem

     """Module for the ParametricProblem class"""
    +
     from abc import abstractmethod
     
     from .abstract_problem import AbstractProblem
    @@ -150,8 +153,8 @@ 

    Source code for pina.problem.parametric_problem

    <
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/problem/spatial_problem.html b/_modules/pina/problem/spatial_problem.html index 6968c26d..3f9359a2 100644 --- a/_modules/pina/problem/spatial_problem.html +++ b/_modules/pina/problem/spatial_problem.html @@ -1,11 +1,13 @@ - + - pina.problem.spatial_problem — PINA 0.1 documentation + pina.problem.spatial_problem — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.problem.spatial_problem

     """Module for the SpatialProblem class"""
    +
     from abc import abstractmethod
     
     from .abstract_problem import AbstractProblem
    @@ -146,8 +149,8 @@ 

    Source code for pina.problem.spatial_problem

     
       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/problem/timedep_problem.html b/_modules/pina/problem/timedep_problem.html index 8ad153e0..f3d6bddb 100644 --- a/_modules/pina/problem/timedep_problem.html +++ b/_modules/pina/problem/timedep_problem.html @@ -1,11 +1,13 @@ - + - pina.problem.timedep_problem — PINA 0.1 documentation + pina.problem.timedep_problem — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,6 +90,7 @@

    Source code for pina.problem.timedep_problem

     """Module for the TimeDependentProblem class"""
    +
     from abc import abstractmethod
     
     from .abstract_problem import AbstractProblem
    @@ -156,8 +159,8 @@ 

    Source code for pina.problem.timedep_problem

     
       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/solvers/garom.html b/_modules/pina/solvers/garom.html index 347ae3c9..74783244 100644 --- a/_modules/pina/solvers/garom.html +++ b/_modules/pina/solvers/garom.html @@ -1,11 +1,13 @@ - + - pina.solvers.garom — PINA 0.1 documentation + pina.solvers.garom — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -90,10 +92,14 @@

    Source code for pina.solvers.garom

     """ Module for GAROM """
     
     import torch
    +import sys
    +
     try:
         from torch.optim.lr_scheduler import LRScheduler  # torch >= 2.0
     except ImportError:
    -    from torch.optim.lr_scheduler import _LRScheduler as LRScheduler  # torch < 2.0
    +    from torch.optim.lr_scheduler import (
    +        _LRScheduler as LRScheduler,
    +    )  # torch < 2.0
     
     from torch.optim.lr_scheduler import ConstantLR
     from .solver import SolverInterface
    @@ -106,12 +112,12 @@ 

    Source code for pina.solvers.garom

         """
         GAROM solver class. This class implements Generative Adversarial
         Reduced Order Model solver, using user specified ``models`` to solve
    -    a specific order reduction``problem``. 
    +    a specific order reduction``problem``.
     
         .. seealso::
     
             **Original reference**: Coscia, D., Demo, N., & Rozza, G. (2023).
    -        *Generative Adversarial Reduced Order Modelling*. 
    +        *Generative Adversarial Reduced Order Modelling*.
             DOI: `arXiv preprint arXiv:2305.15881.
             <https://doi.org/10.48550/arXiv.2305.15881>`_.
         """
    @@ -123,19 +129,13 @@ 

    Source code for pina.solvers.garom

             discriminator,
             loss=None,
             optimizer_generator=torch.optim.Adam,
    -        optimizer_generator_kwargs={'lr': 0.001},
    +        optimizer_generator_kwargs={"lr": 0.001},
             optimizer_discriminator=torch.optim.Adam,
    -        optimizer_discriminator_kwargs={'lr': 0.001},
    +        optimizer_discriminator_kwargs={"lr": 0.001},
             scheduler_generator=ConstantLR,
    -        scheduler_generator_kwargs={
    -            "factor": 1,
    -            "total_iters": 0
    -        },
    +        scheduler_generator_kwargs={"factor": 1, "total_iters": 0},
             scheduler_discriminator=ConstantLR,
    -        scheduler_discriminator_kwargs={
    -            "factor": 1,
    -            "total_iters": 0
    -        },
    +        scheduler_discriminator_kwargs={"factor": 1, "total_iters": 0},
             gamma=0.3,
             lambda_k=0.001,
             regularizer=False,
    @@ -183,8 +183,10 @@ 

    Source code for pina.solvers.garom

                 problem=problem,
                 optimizers=[optimizer_generator, optimizer_discriminator],
                 optimizers_kwargs=[
    -                optimizer_generator_kwargs, optimizer_discriminator_kwargs
    -            ])
    +                optimizer_generator_kwargs,
    +                optimizer_discriminator_kwargs,
    +            ],
    +        )
     
             # set automatic optimization for GANs
             self.automatic_optimization = False
    @@ -206,13 +208,14 @@ 

    Source code for pina.solvers.garom

             # assign schedulers
             self._schedulers = [
                 scheduler_generator(
    -                self.optimizers[0], **scheduler_generator_kwargs),
    +                self.optimizers[0], **scheduler_generator_kwargs
    +            ),
                 scheduler_discriminator(
    -                self.optimizers[1],
    -                **scheduler_discriminator_kwargs)
    +                self.optimizers[1], **scheduler_discriminator_kwargs
    +            ),
             ]
     
    -        # loss and writer 
    +        # loss and writer
             self._loss = loss
     
             # began hyperparameters
    @@ -229,7 +232,7 @@ 

    Source code for pina.solvers.garom

     
             :param x: The input tensor.
             :type x: torch.Tensor
    -        :param mc_steps: Number of montecarlo samples to approximate the 
    +        :param mc_steps: Number of montecarlo samples to approximate the
                 expected value, defaults to 20.
             :type mc_steps: int
             :param variance: Returining also the sample variance of the solution, defaults to False.
    @@ -277,8 +280,12 @@ 

    Source code for pina.solvers.garom

     
             # generator loss
             r_loss = self._loss(snapshots, generated_snapshots)
    -        d_fake = self.discriminator.forward_map([generated_snapshots, parameters])
    -        g_loss = self._loss(d_fake, generated_snapshots) + self.regularizer * r_loss
    +        d_fake = self.discriminator.forward_map(
    +            [generated_snapshots, parameters]
    +        )
    +        g_loss = (
    +            self._loss(d_fake, generated_snapshots) + self.regularizer * r_loss
    +        )
     
             # backward step
             g_loss.backward()
    @@ -298,7 +305,9 @@ 

    Source code for pina.solvers.garom

     
             # Discriminator pass
             d_real = self.discriminator.forward_map([snapshots, parameters])
    -        d_fake = self.discriminator.forward_map([generated_snapshots, parameters]) 
    +        d_fake = self.discriminator.forward_map(
    +            [generated_snapshots, parameters]
    +        )
     
             # evaluate loss
             d_loss_real = self._loss(d_real, snapshots)
    @@ -323,7 +332,7 @@ 

    Source code for pina.solvers.garom

             self.k += self.lambda_k * diff.item()
             self.k = min(max(self.k, 0), 1)  # Constraint to interval [0, 1]
             return diff
    -    
    +
     
    [docs] def training_step(self, batch, batch_idx): """GAROM solver training step. @@ -336,38 +345,75 @@

    Source code for pina.solvers.garom

             """
     
             dataloader = self.trainer.train_dataloader
    -        condition_idx = batch['condition']
    +        condition_idx = batch["condition"]
     
    -        for condition_id in range(condition_idx.min(), condition_idx.max()+1):
    +        for condition_id in range(condition_idx.min(), condition_idx.max() + 1):
    +
    +            if sys.version_info >= (3, 8):
    +                condition_name = dataloader.condition_names[condition_id]
    +            else:
    +                condition_name = dataloader.loaders.condition_names[
    +                    condition_id
    +                ]
     
    -            condition_name = dataloader.condition_names[condition_id]
                 condition = self.problem.conditions[condition_name]
    -            pts = batch['pts'].detach()
    -            out = batch['output']
    +            pts = batch["pts"].detach()
    +            out = batch["output"]
     
                 if condition_name not in self.problem.conditions:
    -                raise RuntimeError('Something wrong happened.')
    +                raise RuntimeError("Something wrong happened.")
     
                 # for data driven mode
    -            if not hasattr(condition, 'output_points'):
    -                raise NotImplementedError('GAROM works only in data-driven mode.')
    +            if not hasattr(condition, "output_points"):
    +                raise NotImplementedError(
    +                    "GAROM works only in data-driven mode."
    +                )
     
                 # get data
                 snapshots = out[condition_idx == condition_id]
                 parameters = pts[condition_idx == condition_id]
     
                 d_loss_real, d_loss_fake, d_loss = self._train_discriminator(
    -                parameters, snapshots)
    +                parameters, snapshots
    +            )
     
                 r_loss, g_loss = self._train_generator(parameters, snapshots)
    -            
    +
                 diff = self._update_weights(d_loss_real, d_loss_fake)
     
                 # logging
    -            self.log('mean_loss', float(r_loss), prog_bar=True, logger=True, on_epoch=True, on_step=False)
    -            self.log('d_loss', float(d_loss), prog_bar=True, logger=True, on_epoch=True, on_step=False)
    -            self.log('g_loss', float(g_loss), prog_bar=True, logger=True, on_epoch=True, on_step=False)
    -            self.log('stability_metric', float(d_loss_real + torch.abs(diff)), prog_bar=True, logger=True, on_epoch=True, on_step=False)
    +            self.log(
    +                "mean_loss",
    +                float(r_loss),
    +                prog_bar=True,
    +                logger=True,
    +                on_epoch=True,
    +                on_step=False,
    +            )
    +            self.log(
    +                "d_loss",
    +                float(d_loss),
    +                prog_bar=True,
    +                logger=True,
    +                on_epoch=True,
    +                on_step=False,
    +            )
    +            self.log(
    +                "g_loss",
    +                float(g_loss),
    +                prog_bar=True,
    +                logger=True,
    +                on_epoch=True,
    +                on_step=False,
    +            )
    +            self.log(
    +                "stability_metric",
    +                float(d_loss_real + torch.abs(diff)),
    +                prog_bar=True,
    +                logger=True,
    +                on_epoch=True,
    +                on_step=False,
    +            )
     
             return
    @@ -403,8 +449,8 @@

    Source code for pina.solvers.garom

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/solvers/pinn.html b/_modules/pina/solvers/pinn.html index f671bc11..525837c1 100644 --- a/_modules/pina/solvers/pinn.html +++ b/_modules/pina/solvers/pinn.html @@ -1,11 +1,13 @@ - + - pina.solvers.pinn — PINA 0.1 documentation + pina.solvers.pinn — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,12 +90,17 @@

    Source code for pina.solvers.pinn

     """ Module for PINN """
    +
     import torch
    +
     try:
         from torch.optim.lr_scheduler import LRScheduler  # torch >= 2.0
     except ImportError:
    -    from torch.optim.lr_scheduler import _LRScheduler as LRScheduler  # torch < 2.0
    +    from torch.optim.lr_scheduler import (
    +        _LRScheduler as LRScheduler,
    +    )  # torch < 2.0
     
    +import sys
     from torch.optim.lr_scheduler import ConstantLR
     
     from .solver import SolverInterface
    @@ -127,14 +134,11 @@ 

    Source code for pina.solvers.pinn

             extra_features=None,
             loss=torch.nn.MSELoss(),
             optimizer=torch.optim.Adam,
    -        optimizer_kwargs={'lr': 0.001},
    +        optimizer_kwargs={"lr": 0.001},
             scheduler=ConstantLR,
    -        scheduler_kwargs={
    -            "factor": 1,
    -            "total_iters": 0
    -        },
    +        scheduler_kwargs={"factor": 1, "total_iters": 0},
         ):
    -        '''
    +        """
             :param AbstractProblem problem: The formulation of the problem.
             :param torch.nn.Module model: The neural network model to use.
             :param torch.nn.Module loss: The loss function used as minimizer,
    @@ -147,12 +151,14 @@ 

    Source code for pina.solvers.pinn

             :param torch.optim.LRScheduler scheduler: Learning
                 rate scheduler.
             :param dict scheduler_kwargs: LR scheduler constructor keyword args.
    -        '''
    -        super().__init__(models=[model],
    -                         problem=problem,
    -                         optimizers=[optimizer],
    -                         optimizers_kwargs=[optimizer_kwargs],
    -                         extra_features=extra_features)
    +        """
    +        super().__init__(
    +            models=[model],
    +            problem=problem,
    +            optimizers=[optimizer],
    +            optimizers_kwargs=[optimizer_kwargs],
    +            extra_features=extra_features,
    +        )
     
             # check consistency
             check_consistency(scheduler, LRScheduler, subclass=True)
    @@ -193,15 +199,21 @@ 

    Source code for pina.solvers.pinn

             # to the parameters that the optimizer needs to optimize
             if isinstance(self.problem, InverseProblem):
                 self.optimizers[0].add_param_group(
    -                {'params': [self._params[var] for var in self.problem.unknown_variables]}
    -                )
    +                {
    +                    "params": [
    +                        self._params[var]
    +                        for var in self.problem.unknown_variables
    +                    ]
    +                }
    +            )
             return self.optimizers, [self.scheduler]
    def _clamp_inverse_problem_params(self): for v in self._params: self._params[v].data.clamp_( - self.problem.unknown_parameter_domain.range_[v][0], - self.problem.unknown_parameter_domain.range_[v][1]) + self.problem.unknown_parameter_domain.range_[v][0], + self.problem.unknown_parameter_domain.range_[v][1], + ) def _loss_data(self, input, output): return self.loss(self.forward(input), output) @@ -209,9 +221,15 @@

    Source code for pina.solvers.pinn

         def _loss_phys(self, samples, equation):
             try:
                 residual = equation.residual(samples, self.forward(samples))
    -        except TypeError: # this occurs when the function has three inputs, i.e. inverse problem
    -            residual = equation.residual(samples, self.forward(samples), self._params)
    -        return self.loss(torch.zeros_like(residual, requires_grad=True), residual)
    +        except (
    +            TypeError
    +        ):  # this occurs when the function has three inputs, i.e. inverse problem
    +            residual = equation.residual(
    +                samples, self.forward(samples), self._params
    +            )
    +        return self.loss(
    +            torch.zeros_like(residual, requires_grad=True), residual
    +        )
     
     
    [docs] def training_step(self, batch, batch_idx): """ @@ -228,20 +246,25 @@

    Source code for pina.solvers.pinn

             dataloader = self.trainer.train_dataloader
             condition_losses = []
     
    -        condition_idx = batch['condition']
    +        condition_idx = batch["condition"]
     
    -        for condition_id in range(condition_idx.min(), condition_idx.max()+1):
    +        for condition_id in range(condition_idx.min(), condition_idx.max() + 1):
     
    -            condition_name = dataloader.condition_names[condition_id]
    +            if sys.version_info >= (3, 8):
    +                condition_name = dataloader.condition_names[condition_id]
    +            else:
    +                condition_name = dataloader.loaders.condition_names[
    +                    condition_id
    +                ]
                 condition = self.problem.conditions[condition_name]
    -            pts = batch['pts']
    +            pts = batch["pts"]
     
                 if len(batch) == 2:
                     samples = pts[condition_idx == condition_id]
                     loss = self._loss_phys(samples, condition.equation)
                 elif len(batch) == 3:
                     samples = pts[condition_idx == condition_id]
    -                ground_truth = batch['output'][condition_idx == condition_id]
    +                ground_truth = batch["output"][condition_idx == condition_id]
                     loss = self._loss_data(samples, ground_truth)
                 else:
                     raise ValueError("Batch size not supported")
    @@ -249,10 +272,16 @@ 

    Source code for pina.solvers.pinn

                 # TODO for users this us hard to remember when creating a new solver, to fix in a smarter way
                 loss = loss.as_subclass(torch.Tensor)
     
    -#            # add condition losses and accumulate logging for each epoch
    +            #            # add condition losses and accumulate logging for each epoch
                 condition_losses.append(loss * condition.data_weight)
    -            self.log(condition_name + '_loss', float(loss),
    -                     prog_bar=True, logger=True, on_epoch=True, on_step=False)
    +            self.log(
    +                condition_name + "_loss",
    +                float(loss),
    +                prog_bar=True,
    +                logger=True,
    +                on_epoch=True,
    +                on_step=False,
    +            )
     
             # clamp unknown parameters of the InverseProblem to their domain ranges (if needed)
             if isinstance(self.problem, InverseProblem):
    @@ -261,8 +290,14 @@ 

    Source code for pina.solvers.pinn

             # TODO Fix the bug, tot_loss is a label tensor without labels
             # we need to pass it as a torch tensor to make everything work
             total_loss = sum(condition_losses)
    -        self.log('mean_loss', float(total_loss / len(condition_losses)),
    -                 prog_bar=True, logger=True, on_epoch=True, on_step=False)
    +        self.log(
    +            "mean_loss",
    +            float(total_loss / len(condition_losses)),
    +            prog_bar=True,
    +            logger=True,
    +            on_epoch=True,
    +            on_step=False,
    +        )
     
             return total_loss
    @@ -295,8 +330,8 @@

    Source code for pina.solvers.pinn

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/solvers/solver.html b/_modules/pina/solvers/solver.html index 3334419a..1dc7f4f2 100644 --- a/_modules/pina/solvers/solver.html +++ b/_modules/pina/solvers/solver.html @@ -1,11 +1,13 @@ - + - pina.solvers.solver — PINA 0.1 documentation + pina.solvers.solver — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -104,12 +106,14 @@

    Source code for pina.solvers.solver

         LightningModule methods.
         """
     
    -    def __init__(self,
    -                 models,
    -                 problem,
    -                 optimizers,
    -                 optimizers_kwargs,
    -                 extra_features=None):
    +    def __init__(
    +        self,
    +        models,
    +        problem,
    +        optimizers,
    +        optimizers_kwargs,
    +        extra_features=None,
    +    ):
             """
             :param models: A torch neural network model instance.
             :type models: torch.nn.Module
    @@ -119,7 +123,7 @@ 

    Source code for pina.solvers.solver

                 use.
             :param list(dict) optimizer_kwargs: A list of optimizer constructor keyword args.
             :param list(torch.nn.Module) extra_features: The additional input
    -            features to use as augmented input. If ``None`` no extra features 
    +            features to use as augmented input. If ``None`` no extra features
                 are passed. If it is a list of :class:`torch.nn.Module`, the extra feature
                 list is passed to all models. If it is a list of extra features' lists,
                 each single list of extra feature is passed to a model.
    @@ -146,19 +150,23 @@ 

    Source code for pina.solvers.solver

     
             # check length consistency optimizers
             if len_model != len_optimizer:
    -            raise ValueError('You must define one optimizer for each model.'
    -                             f'Got {len_model} models, and {len_optimizer}'
    -                             ' optimizers.')
    +            raise ValueError(
    +                "You must define one optimizer for each model."
    +                f"Got {len_model} models, and {len_optimizer}"
    +                " optimizers."
    +            )
     
             # check length consistency optimizers kwargs
             if len_optimizer_kwargs != len_optimizer:
    -            raise ValueError('You must define one dictionary of keyword'
    -                             ' arguments for each optimizers.'
    -                             f'Got {len_optimizer} optimizers, and'
    -                             f' {len_optimizer_kwargs} dicitionaries')
    +            raise ValueError(
    +                "You must define one dictionary of keyword"
    +                " arguments for each optimizers."
    +                f"Got {len_optimizer} optimizers, and"
    +                f" {len_optimizer_kwargs} dicitionaries"
    +            )
     
             # extra features handling
    -        if (extra_features is None) or (len(extra_features)==0):
    +        if (extra_features is None) or (len(extra_features) == 0):
                 extra_features = [None] * len_model
             else:
                 # if we only have a list of extra features
    @@ -167,24 +175,28 @@ 

    Source code for pina.solvers.solver

                 else:  # if we have a list of list extra features
                     if len(extra_features) != len_model:
                         raise ValueError(
    -                        'You passed a list of extrafeatures list with len'
    -                        f'different of models len. Expected {len_model} '
    -                        f'got {len(extra_features)}. If you want to use '
    -                        'the same list of extra features for all models, '
    -                        'just pass a list of extrafeatures and not a list '
    -                        'of list of extra features.')
    +                        "You passed a list of extrafeatures list with len"
    +                        f"different of models len. Expected {len_model} "
    +                        f"got {len(extra_features)}. If you want to use "
    +                        "the same list of extra features for all models, "
    +                        "just pass a list of extrafeatures and not a list "
    +                        "of list of extra features."
    +                    )
     
             # assigning model and optimizers
             self._pina_models = []
             self._pina_optimizers = []
     
             for idx in range(len_model):
    -            model_ = Network(model=models[idx],
    -                             input_variables=problem.input_variables,
    -                             output_variables=problem.output_variables,
    -                             extra_features=extra_features[idx])
    -            optim_ = optimizers[idx](model_.parameters(),
    -                                     **optimizers_kwargs[idx])
    +            model_ = Network(
    +                model=models[idx],
    +                input_variables=problem.input_variables,
    +                output_variables=problem.output_variables,
    +                extra_features=extra_features[idx],
    +            )
    +            optim_ = optimizers[idx](
    +                model_.parameters(), **optimizers_kwargs[idx]
    +            )
                 self._pina_models.append(model_)
                 self._pina_optimizers.append(optim_)
     
    @@ -243,8 +255,8 @@ 

    Source code for pina.solvers.solver

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/solvers/supervised.html b/_modules/pina/solvers/supervised.html index 5cc7761b..65c69ce1 100644 --- a/_modules/pina/solvers/supervised.html +++ b/_modules/pina/solvers/supervised.html @@ -1,11 +1,13 @@ - + - pina.solvers.supervised — PINA 0.1 documentation + pina.solvers.supervised — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -88,11 +90,16 @@

    Source code for pina.solvers.supervised

     """ Module for SupervisedSolver """
    +
     import torch
    +import sys
    +
     try:
         from torch.optim.lr_scheduler import LRScheduler  # torch >= 2.0
     except ImportError:
    -    from torch.optim.lr_scheduler import _LRScheduler as LRScheduler  # torch < 2.0
    +    from torch.optim.lr_scheduler import (
    +        _LRScheduler as LRScheduler,
    +    )  # torch < 2.0
     
     from torch.optim.lr_scheduler import ConstantLR
     
    @@ -106,7 +113,7 @@ 

    Source code for pina.solvers.supervised

     
    [docs]class SupervisedSolver(SolverInterface): """ SupervisedSolver solver class. This class implements a SupervisedSolver, - using a user specified ``model`` to solve a specific ``problem``. + using a user specified ``model`` to solve a specific ``problem``. """ def __init__( @@ -116,14 +123,11 @@

    Source code for pina.solvers.supervised

             extra_features=None,
             loss=torch.nn.MSELoss(),
             optimizer=torch.optim.Adam,
    -        optimizer_kwargs={'lr': 0.001},
    +        optimizer_kwargs={"lr": 0.001},
             scheduler=ConstantLR,
    -        scheduler_kwargs={
    -            "factor": 1,
    -            "total_iters": 0
    -        },
    +        scheduler_kwargs={"factor": 1, "total_iters": 0},
         ):
    -        '''
    +        """
             :param AbstractProblem problem: The formualation of the problem.
             :param torch.nn.Module model: The neural network model to use.
             :param torch.nn.Module loss: The loss function used as minimizer,
    @@ -137,12 +141,14 @@ 

    Source code for pina.solvers.supervised

             :param torch.optim.LRScheduler scheduler: Learning
                 rate scheduler.
             :param dict scheduler_kwargs: LR scheduler constructor keyword args.
    -        '''
    -        super().__init__(models=[model],
    -                         problem=problem,
    -                         optimizers=[optimizer],
    -                         optimizers_kwargs=[optimizer_kwargs],
    -                         extra_features=extra_features)
    +        """
    +        super().__init__(
    +            models=[model],
    +            problem=problem,
    +            optimizers=[optimizer],
    +            optimizers_kwargs=[optimizer_kwargs],
    +            extra_features=extra_features,
    +        )
     
             # check consistency
             check_consistency(scheduler, LRScheduler, subclass=True)
    @@ -157,7 +163,7 @@ 

    Source code for pina.solvers.supervised

     
    [docs] def forward(self, x): """Forward pass implementation for the solver. - :param torch.Tensor x: Input tensor. + :param torch.Tensor x: Input tensor. :return: Solver solution. :rtype: torch.Tensor """ @@ -183,29 +189,39 @@

    Source code for pina.solvers.supervised

             """
     
             dataloader = self.trainer.train_dataloader
    -        condition_idx = batch['condition']
    +        condition_idx = batch["condition"]
     
    -        for condition_id in range(condition_idx.min(), condition_idx.max()+1):
    +        for condition_id in range(condition_idx.min(), condition_idx.max() + 1):
     
    -            condition_name = dataloader.condition_names[condition_id]
    +            if sys.version_info >= (3, 8):
    +                condition_name = dataloader.condition_names[condition_id]
    +            else:
    +                condition_name = dataloader.loaders.condition_names[
    +                    condition_id
    +                ]
                 condition = self.problem.conditions[condition_name]
    -            pts = batch['pts']
    -            out = batch['output']
    +            pts = batch["pts"]
    +            out = batch["output"]
     
                 if condition_name not in self.problem.conditions:
    -                raise RuntimeError('Something wrong happened.')
    +                raise RuntimeError("Something wrong happened.")
     
                 # for data driven mode
    -            if not hasattr(condition, 'output_points'):
    -                raise NotImplementedError('Supervised solver works only in data-driven mode.')
    -            
    +            if not hasattr(condition, "output_points"):
    +                raise NotImplementedError(
    +                    "Supervised solver works only in data-driven mode."
    +                )
    +
                 output_pts = out[condition_idx == condition_id]
                 input_pts = pts[condition_idx == condition_id]
     
    -            loss = self.loss(self.forward(input_pts), output_pts) * condition.data_weight
    +            loss = (
    +                self.loss(self.forward(input_pts), output_pts)
    +                * condition.data_weight
    +            )
                 loss = loss.as_subclass(torch.Tensor)
     
    -        self.log('mean_loss', float(loss), prog_bar=True, logger=True)
    +        self.log("mean_loss", float(loss), prog_bar=True, logger=True)
             return loss
    @property @@ -237,8 +253,8 @@

    Source code for pina.solvers.supervised

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_modules/pina/trainer.html b/_modules/pina/trainer.html index 0bf3809b..08cda020 100644 --- a/_modules/pina/trainer.html +++ b/_modules/pina/trainer.html @@ -1,11 +1,13 @@ - + - pina.trainer — PINA 0.1 documentation + pina.trainer — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -94,6 +96,7 @@

    Source code for pina.trainer

     from .dataset import SamplePointDataset, SamplePointLoader, DataPointDataset
     from .solvers.solver import SolverInterface
     
    +
     
    [docs]class Trainer(pytorch_lightning.Trainer): def __init__(self, solver, batch_size=None, **kwargs): @@ -118,18 +121,20 @@

    Source code for pina.trainer

             check_consistency(solver, SolverInterface)
             if batch_size is not None:
                 check_consistency(batch_size, int)
    -        
    +
             self._model = solver
             self.batch_size = batch_size
     
             # create dataloader
             if solver.problem.have_sampled_points is False:
    -            raise RuntimeError(f'Input points in {solver.problem.not_sampled_points} '
    -                               'training are None. Please '
    -                               'sample points in your problem by calling '
    -                               'discretise_domain function before train '
    -                               'in the provided locations.')
    -        
    +            raise RuntimeError(
    +                f"Input points in {solver.problem.not_sampled_points} "
    +                "training are None. Please "
    +                "sample points in your problem by calling "
    +                "discretise_domain function before train "
    +                "in the provided locations."
    +            )
    +
             self._create_or_update_loader()
     
         def _create_or_update_loader(self):
    @@ -141,21 +146,23 @@ 

    Source code for pina.trainer

             devices = self._accelerator_connector._parallel_devices
     
             if len(devices) > 1:
    -            raise RuntimeError('Parallel training is not supported yet.')
    +            raise RuntimeError("Parallel training is not supported yet.")
     
             device = devices[0]
             dataset_phys = SamplePointDataset(self._model.problem, device)
             dataset_data = DataPointDataset(self._model.problem, device)
             self._loader = SamplePointLoader(
    -            dataset_phys, dataset_data, batch_size=self.batch_size,
    -            shuffle=True)
    +            dataset_phys, dataset_data, batch_size=self.batch_size, shuffle=True
    +        )
     
     
    [docs] def train(self, **kwargs): """ Train the solver method. """ - return super().fit(self._model, train_dataloaders=self._loader, **kwargs)
    - + return super().fit( + self._model, train_dataloaders=self._loader, **kwargs + )
    + @property def solver(self): """ @@ -171,8 +178,8 @@

    Source code for pina.trainer

       
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/_code.html b/_rst/_code.html index e7228b07..dc1cfd3f 100644 --- a/_rst/_code.html +++ b/_rst/_code.html @@ -1,11 +1,13 @@ - + - Code Documentation — PINA 0.1 documentation + Code Documentation — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -302,8 +308,8 @@

    Metrics and Losses -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/_contributing.html b/_rst/_contributing.html index 09d02162..683f2dbb 100644 --- a/_rst/_contributing.html +++ b/_rst/_contributing.html @@ -1,11 +1,13 @@ - + - How to contribute — PINA 0.1 documentation + How to contribute — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    diff --git a/_rst/_installation.html b/_rst/_installation.html index ef1febc9..95560d8c 100644 --- a/_rst/_installation.html +++ b/_rst/_installation.html @@ -1,11 +1,13 @@ - + - Installation — PINA 0.1 documentation + Installation — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -131,8 +133,8 @@

    Installing from source
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/_tutorial.html b/_rst/_tutorial.html index dcc2b21f..53b57056 100644 --- a/_rst/_tutorial.html +++ b/_rst/_tutorial.html @@ -1,11 +1,13 @@ - + - PINA Tutorials — PINA 0.1 documentation + PINA Tutorials — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403

    @@ -160,8 +164,8 @@

    Supervised Learning -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/callbacks/adaptive_refinment_callbacks.html b/_rst/callbacks/adaptive_refinment_callbacks.html index 35bc1ec6..3f869918 100644 --- a/_rst/callbacks/adaptive_refinment_callbacks.html +++ b/_rst/callbacks/adaptive_refinment_callbacks.html @@ -1,11 +1,13 @@ - + - Adaptive Refinments callbacks — PINA 0.1 documentation + Adaptive Refinments callbacks — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    diff --git a/_rst/callbacks/optimizer_callbacks.html b/_rst/callbacks/optimizer_callbacks.html index 56a63b4c..830cd7a3 100644 --- a/_rst/callbacks/optimizer_callbacks.html +++ b/_rst/callbacks/optimizer_callbacks.html @@ -1,11 +1,13 @@ - + - Optimizer callbacks — PINA 0.1 documentation + Optimizer callbacks — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -119,7 +121,7 @@

    Optimizer callbacksParameters

    diff --git a/_rst/callbacks/processing_callbacks.html b/_rst/callbacks/processing_callbacks.html index 4001b87f..eb63c002 100644 --- a/_rst/callbacks/processing_callbacks.html +++ b/_rst/callbacks/processing_callbacks.html @@ -1,11 +1,13 @@ - + - Processing callbacks — PINA 0.1 documentation + Processing callbacks — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -183,8 +185,8 @@

    Processing callbacks
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/condition.html b/_rst/condition.html index 9a84bbbd..1b581546 100644 --- a/_rst/condition.html +++ b/_rst/condition.html @@ -1,11 +1,13 @@ - + - Condition — PINA 0.1 documentation + Condition — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    diff --git a/_rst/equations.html b/_rst/equations.html index da0dea94..4b3f2b93 100644 --- a/_rst/equations.html +++ b/_rst/equations.html @@ -1,11 +1,13 @@ - + - Equations — PINA 0.1 documentation + Equations — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    diff --git a/_rst/geometry/cartesian.html b/_rst/geometry/cartesian.html index f4464f55..78973e36 100644 --- a/_rst/geometry/cartesian.html +++ b/_rst/geometry/cartesian.html @@ -1,11 +1,13 @@ - + - CartesianDomain — PINA 0.1 documentation + CartesianDomain — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -266,8 +268,8 @@
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/geometry/difference_domain.html b/_rst/geometry/difference_domain.html index 74db85dc..10dca0fa 100644 --- a/_rst/geometry/difference_domain.html +++ b/_rst/geometry/difference_domain.html @@ -1,11 +1,13 @@ - + - Difference — PINA 0.1 documentation + Difference — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -147,7 +149,7 @@
    Parameters
      -
    • point (torch.Tensor) – Point to be checked.

    • +
    • point (torch.Tensor) – Point to be checked.

    • check_border (bool) – If True, the border is considered inside.

    @@ -214,8 +216,8 @@
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/geometry/ellipsoid.html b/_rst/geometry/ellipsoid.html index 932d96a8..a4b3401d 100644 --- a/_rst/geometry/ellipsoid.html +++ b/_rst/geometry/ellipsoid.html @@ -1,11 +1,13 @@ - + - EllipsoidDomain — PINA 0.1 documentation + EllipsoidDomain — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -235,8 +237,8 @@
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/geometry/exclusion_domain.html b/_rst/geometry/exclusion_domain.html index 527bff9b..ca8352f8 100644 --- a/_rst/geometry/exclusion_domain.html +++ b/_rst/geometry/exclusion_domain.html @@ -1,11 +1,13 @@ - + - Exclusion — PINA 0.1 documentation + Exclusion — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -144,7 +146,7 @@
    Parameters
      -
    • point (torch.Tensor) – Point to be checked.

    • +
    • point (torch.Tensor) – Point to be checked.

    • check_border (bool) – If True, the border is considered inside.

    @@ -211,8 +213,8 @@
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/geometry/intersection_domain.html b/_rst/geometry/intersection_domain.html index 1cea05d3..67e30c05 100644 --- a/_rst/geometry/intersection_domain.html +++ b/_rst/geometry/intersection_domain.html @@ -1,11 +1,13 @@ - + - Intersection — PINA 0.1 documentation + Intersection — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -146,7 +148,7 @@
    Parameters
      -
    • point (torch.Tensor) – Point to be checked.

    • +
    • point (torch.Tensor) – Point to be checked.

    • check_border (bool) – If True, the border is considered inside.

    @@ -213,8 +215,8 @@
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/geometry/location.html b/_rst/geometry/location.html index 4aa3edd3..3df5a817 100644 --- a/_rst/geometry/location.html +++ b/_rst/geometry/location.html @@ -1,11 +1,13 @@ - + - Location — PINA 0.1 documentation + Location — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -131,7 +133,7 @@
    Parameters
    @@ -238,7 +240,7 @@

    Continuous convolution\(N\) the number of points in the mesh, D the dimension of the problem.

    -
  • X (torch.Tensor) – Input data. Expect tensor of shape +

  • X (torch.Tensor) – Input data. Expect tensor of shape \([B, N_{in}, M, D]\) where \(B\) is the batch_size, :math`N_{in}`is the number of input fields, \(M\) the number of points in the mesh, \(D\) the dimension of the problem.

  • @@ -251,7 +253,7 @@

    Continuous convolution\(D\) the dimension of the problem.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -273,7 +275,7 @@

    Continuous convolution\(N\) the number of points in the mesh, D the dimension of the problem.

    -
  • X (torch.Tensor) – Input data. Expect tensor of shape +

  • X (torch.Tensor) – Input data. Expect tensor of shape \([B, N_{in}, M, D]\) where \(B\) is the batch_size, :math`N_{in}`is the number of input fields, \(M\) the number of points in the mesh, \(D\) the dimension of the problem.

  • @@ -286,7 +288,7 @@

    Continuous convolution\(D\) the dimension of the problem.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -311,8 +313,8 @@

    Continuous convolution
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/layers/enhanced_linear.html b/_rst/layers/enhanced_linear.html index 9ec4b1b1..1ec3964b 100644 --- a/_rst/layers/enhanced_linear.html +++ b/_rst/layers/enhanced_linear.html @@ -1,11 +1,13 @@ - + - EnhancedLinear — PINA 0.1 documentation + EnhancedLinear — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -113,13 +115,13 @@

    EnhancedLinear
    class EnhancedLinear(layer, activation=None, dropout=None)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    A wrapper class for enhancing a linear layer with activation and/or dropout.

    Parameters
      -
    • layer (torch.nn.Module) – The linear layer to be enhanced.

    • -
    • activation (torch.nn.Module) – The activation function to be applied after the linear layer.

    • +
    • layer (torch.nn.Module) – The linear layer to be enhanced.

    • +
    • activation (torch.nn.Module) – The activation function to be applied after the linear layer.

    • dropout (float) – The dropout probability to be applied after the activation (if provided).

    @@ -136,8 +138,8 @@

    EnhancedLinear
    Parameters
      -
    • layer (torch.nn.Module) – The linear layer to be enhanced.

    • -
    • activation (torch.nn.Module) – The activation function to be applied after the linear layer.

    • +
    • layer (torch.nn.Module) – The linear layer to be enhanced.

    • +
    • activation (torch.nn.Module) – The activation function to be applied after the linear layer.

    • dropout (float) – The dropout probability to be applied after the activation (if provided).

    @@ -148,13 +150,13 @@

    EnhancedLinear
    Parameters
    -

    x (torch.Tensor) – Input tensor.

    +

    x (torch.Tensor) – Input tensor.

    Returns

    Output tensor after passing through the enhanced linear module.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -174,8 +176,8 @@

    EnhancedLinear -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/layers/fourier.html b/_rst/layers/fourier.html index 31b0bb0e..165da7a0 100644 --- a/_rst/layers/fourier.html +++ b/_rst/layers/fourier.html @@ -1,11 +1,13 @@ - + - Fourier Layers — PINA 0.1 documentation + Fourier Layers — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -113,7 +115,7 @@

    Fourier Layers
    class FourierBlock1D(input_numb_fields, output_numb_fields, n_modes, activation=<class 'torch.nn.modules.activation.Tanh'>)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    Fourier block implementation for three dimensional input tensor. The combination of Fourier blocks make up the Fourier Neural Operator

    @@ -124,7 +126,7 @@

    Fourier LayersarXiv preprint arXiv:2010.08895.

    -

    Initializes internal Module state, shared by both nn.Module and ScriptModule.

    +

    Initialize internal Module state, shared by both nn.Module and ScriptModule.

    forward(x)[source]
    @@ -133,7 +135,7 @@

    Fourier Layers
    Parameters
    -

    x (torch.Tensor) – The input tensor for fourier block, expect of size +

    x (torch.Tensor) – The input tensor for fourier block, expect of size [batch, input_numb_fields, x].

    Returns
    @@ -141,7 +143,7 @@

    Fourier Layers[batch, output_numb_fields, x].

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -151,7 +153,7 @@

    Fourier Layers
    class FourierBlock2D(input_numb_fields, output_numb_fields, n_modes, activation=<class 'torch.nn.modules.activation.Tanh'>)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    Fourier block implementation for two dimensional input tensor. The combination of Fourier blocks make up the Fourier Neural Operator

    @@ -178,7 +180,7 @@

    Fourier Layersint) – The number of channels for the output.

  • | tuple n_modes (list) – Number of modes to select for each dimension. It must be at most equal to the floor(Nx/2)+1 and floor(Ny/2)+1.

  • -
  • activation (torch.nn.Module) – The activation function.

  • +
  • activation (torch.nn.Module) – The activation function.

  • @@ -190,7 +192,7 @@

    Fourier Layers
    Parameters
    -

    x (torch.Tensor) – The input tensor for fourier block, expect of size +

    x (torch.Tensor) – The input tensor for fourier block, expect of size [batch, input_numb_fields, x, y].

    Returns
    @@ -198,7 +200,7 @@

    Fourier Layers[batch, output_numb_fields, x, y, z].

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -208,7 +210,7 @@

    Fourier Layers
    class FourierBlock3D(input_numb_fields, output_numb_fields, n_modes, activation=<class 'torch.nn.modules.activation.Tanh'>)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    Fourier block implementation for three dimensional input tensor. The combination of Fourier blocks make up the Fourier Neural Operator

    @@ -236,7 +238,7 @@

    Fourier Layerslist) – Number of modes to select for each dimension. It must be at most equal to the floor(Nx/2)+1, floor(Ny/2)+1 and floor(Nz/2)+1.

    -
  • activation (torch.nn.Module) – The activation function.

  • +
  • activation (torch.nn.Module) – The activation function.

  • @@ -248,7 +250,7 @@

    Fourier Layers
    Parameters
    -

    x (torch.Tensor) – The input tensor for fourier block, expect of size +

    x (torch.Tensor) – The input tensor for fourier block, expect of size [batch, input_numb_fields, x, y, z].

    Returns
    @@ -256,7 +258,7 @@

    Fourier Layers[batch, output_numb_fields, x, y, z].

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -276,8 +278,8 @@

    Fourier Layers -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/layers/residual.html b/_rst/layers/residual.html index a26c0fc1..43e74711 100644 --- a/_rst/layers/residual.html +++ b/_rst/layers/residual.html @@ -1,11 +1,13 @@ - + - Residual layer — PINA 0.1 documentation + Residual layer — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -113,7 +115,7 @@

    Residual layer
    class ResidualBlock(input_dim, output_dim, hidden_dim, spectral_norm=False, activation=ReLU())[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    Residual block base class. Implementation of a residual block.

    See also

    @@ -135,7 +137,7 @@

    Residual layerbool) – Apply spectral normalization to feedforward layers, defaults to False.

    -
  • activation (torch.nn.Module) – Cctivation function after first block.

  • +
  • activation (torch.nn.Module) – Cctivation function after first block.

  • @@ -145,13 +147,13 @@

    Residual layer
    Parameters
    -

    x (torch.Tensor) – Input tensor for the residual layer.

    +

    x (torch.Tensor) – Input tensor for the residual layer.

    Returns

    Output tensor for the residual layer.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -171,8 +173,8 @@

    Residual layer -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/layers/spectral.html b/_rst/layers/spectral.html index 948b4da0..bd5c2310 100644 --- a/_rst/layers/spectral.html +++ b/_rst/layers/spectral.html @@ -1,11 +1,13 @@ - + - Spectral Convolution — PINA 0.1 documentation + Spectral Convolution — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -113,7 +115,7 @@

    Spectral Convolution
    class SpectralConvBlock1D(input_numb_fields, output_numb_fields, n_modes)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    PINA implementation of Spectral Convolution Block for one dimensional tensors.

    The module computes the spectral convolution of the input with a linear kernel in the @@ -137,7 +139,7 @@

    Spectral Convolution

    Forward computation for Spectral Convolution.

    Parameters
    -

    x (torch.Tensor) – The input tensor, expect of size +

    x (torch.Tensor) – The input tensor, expect of size [batch, input_numb_fields, x].

    Returns
    @@ -145,7 +147,7 @@

    Spectral Convolution[batch, output_numb_fields, x].

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -155,7 +157,7 @@

    Spectral Convolution
    class SpectralConvBlock2D(input_numb_fields, output_numb_fields, n_modes)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    PINA implementation of spectral convolution block for two dimensional tensors.

    The module computes the spectral convolution of the input with a linear kernel in the @@ -179,7 +181,7 @@

    Spectral Convolution

    Forward computation for Spectral Convolution.

    Parameters
    -

    x (torch.Tensor) – The input tensor, expect of size +

    x (torch.Tensor) – The input tensor, expect of size [batch, input_numb_fields, x, y].

    Returns
    @@ -187,7 +189,7 @@

    Spectral Convolution[batch, output_numb_fields, x, y].

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -197,7 +199,7 @@

    Spectral Convolution
    class SpectralConvBlock3D(input_numb_fields, output_numb_fields, n_modes)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    PINA implementation of spectral convolution block for three dimensional tensors.

    The module computes the spectral convolution of the input with a linear kernel in the @@ -222,7 +224,7 @@

    Spectral Convolution

    Forward computation for Spectral Convolution.

    Parameters
    -

    x (torch.Tensor) – The input tensor, expect of size +

    x (torch.Tensor) – The input tensor, expect of size [batch, input_numb_fields, x, y, z].

    Returns
    @@ -230,7 +232,7 @@

    Spectral Convolution[batch, output_numb_fields, x, y, z].

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -250,8 +252,8 @@

    Spectral Convolution
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/loss/loss_interface.html b/_rst/loss/loss_interface.html index 737c8dfd..404f387e 100644 --- a/_rst/loss/loss_interface.html +++ b/_rst/loss/loss_interface.html @@ -1,11 +1,13 @@ - + - LpLoss — PINA 0.1 documentation + LpLoss — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -133,15 +135,15 @@
    Parameters
    Returns

    Loss evaluation.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -161,8 +163,8 @@
    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/loss/lploss.html b/_rst/loss/lploss.html index 18018fb2..e9847277 100644 --- a/_rst/loss/lploss.html +++ b/_rst/loss/lploss.html @@ -1,11 +1,13 @@ - + - LpLoss — PINA 0.1 documentation + LpLoss — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -158,15 +160,15 @@

    LpLoss
    Parameters
    Returns

    Loss evaluation.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -186,8 +188,8 @@

    LpLoss -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/loss/powerloss.html b/_rst/loss/powerloss.html index 1e719071..e98c6474 100644 --- a/_rst/loss/powerloss.html +++ b/_rst/loss/powerloss.html @@ -1,11 +1,13 @@ - + - PowerLoss — PINA 0.1 documentation + PowerLoss — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -158,15 +160,15 @@

    PowerLoss
    Parameters
    Returns

    Loss evaluation.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -186,8 +188,8 @@

    PowerLoss -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/models/base_no.html b/_rst/models/base_no.html new file mode 100644 index 00000000..2b97e451 --- /dev/null +++ b/_rst/models/base_no.html @@ -0,0 +1,250 @@ + + + + + + KernelNeuralOperator — PINA 0.1.0.post2403 documentation + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    KernelNeuralOperator

    +
    +
    +class KernelNeuralOperator(lifting_operator, integral_kernels, projection_operator)[source]
    +

    Bases: torch.nn.modules.module.Module

    +

    Base class for composing Neural Operators with integral kernels.

    +

    This is a base class for composing neural operators with multiple +integral kernels. All neural operator models defined in PINA inherit +from this class. The structure is inspired by the work of Kovachki, N. +et al. see Figure 2 of the reference for extra details. The Neural +Operators inheriting from this class can be written as:

    +
    +\[G_\theta := P \circ K_m \circ \cdot \circ K_1 \circ L\]
    +

    where:

    +
      +
    • \(G_\theta: \mathcal{A}\subset \mathbb{R}^{\rm{in}} \rightarrow +\mathcal{D}\subset \mathbb{R}^{\rm{out}}\) is the neural operator +approximation of the unknown real operator \(G\), that is +\(G \approx G_\theta\)

    • +
    • \(L: \mathcal{A}\subset \mathbb{R}^{\rm{in}} \rightarrow +\mathbb{R}^{\rm{emb}}\) is a lifting operator mapping the input +from its domain \(\mathcal{A}\subset \mathbb{R}^{\rm{in}}\) +to its embedding dimension \(\mathbb{R}^{\rm{emb}}\)

    • +
    • \(\{K_i : \mathbb{R}^{\rm{emb}} \rightarrow +\mathbb{R}^{\rm{emb}} \}_{i=1}^m\) are \(m\) integral kernels +mapping each hidden representation to the next one.

    • +
    • \(P : \mathbb{R}^{\rm{emb}} \rightarrow \mathcal{D}\subset +\mathbb{R}^{\rm{out}}\) is a projection operator mapping the hidden +representation to the output function.

    • +
    +
    +

    See also

    +

    Original reference: Kovachki, N., Li, Z., Liu, B., +Azizzadenesheli, K., Bhattacharya, K., Stuart, A., & Anandkumar, A. +(2023). Neural operator: Learning maps between function +spaces with applications to PDEs. Journal of Machine Learning +Research, 24(89), 1-97.

    +
    +
    +
    Parameters
    +
      +
    • lifting_operator (torch.nn.Module) – The lifting operator +mapping the input to its hidden dimension.

    • +
    • integral_kernels (torch.nn.Module) – List of integral kernels +mapping each hidden representation to the next one.

    • +
    • projection_operator (torch.nn.Module) – The projection operator +mapping the hidden representation to the output function.

    • +
    +
    +
    +
    +
    +property lifting_operator
    +

    The lifting operator property.

    +
    + +
    +
    +property projection_operator
    +

    The projection operator property.

    +
    + +
    +
    +property integral_kernels
    +

    The integral kernels operator property.

    +
    + +
    +
    +forward(x)[source]
    +

    Forward computation for Base Neural Operator. It performs a +lifting of the input by the lifting_operator. +Then different layers integral kernels are applied using +integral_kernels. Finally the output is projected +to the final dimensionality by the projection_operator.

    +
    +
    Parameters
    +

    x (torch.Tensor) – The input tensor for performing the +computation. It expects a tensor \(B \times N \times D\), +where \(B\) is the batch_size, \(N\) the number of points +in the mesh, \(D\) the dimension of the problem. In particular +\(D\) is the number of spatial/paramtric/temporal variables +plus the field variables. For example for 2D problems with 2 +outputvariables \(D=4\).

    +
    +
    Returns
    +

    The output tensor obtained from the NO.

    +
    +
    Return type
    +

    torch.Tensor

    +
    +
    +
    + +
    + +
    + + +
    +
    +
    + +
    + +
    +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024. +

    +
    + + Built with Sphinx using a + theme + provided by Read the Docs. + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/_rst/models/deeponet.html b/_rst/models/deeponet.html index 937cebb4..9aa7b220 100644 --- a/_rst/models/deeponet.html +++ b/_rst/models/deeponet.html @@ -1,11 +1,13 @@ - + - DeepONet — PINA 0.1 documentation + DeepONet — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -54,11 +56,13 @@
  • Solvers
  • Models
  • @@ -131,13 +135,13 @@

    DeepONet
    Parameters

    @@ -221,7 +225,7 @@

    DeepONet

    The output computed by the DeepONet model.

    Return type
    -

    LabelTensor or torch.Tensor

    +

    LabelTensor or torch.Tensor

    @@ -253,8 +257,8 @@

    DeepONet -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/models/fnn.html b/_rst/models/fnn.html index b8c6f8cd..f5facb27 100644 --- a/_rst/models/fnn.html +++ b/_rst/models/fnn.html @@ -1,11 +1,13 @@ - + - FeedForward — PINA 0.1 documentation + FeedForward — PINA 0.1.0.post2403 documentation - + + + @@ -21,7 +23,7 @@ - + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -54,11 +56,13 @@
  • Solvers
  • Models
  • @@ -115,7 +119,7 @@

    FeedForward
    class FeedForward(input_dimensions, output_dimensions, inner_size=20, n_layers=2, func=<class 'torch.nn.modules.activation.Tanh'>, layers=None, bias=True)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    The PINA implementation of feedforward network, also refered as multilayer perceptron.

    @@ -131,7 +135,7 @@

    FeedForwardint) – number of hidden layers. Default is 2.

  • func – the activation function to use. If a single -torch.nn.Module is passed, this is used as activation function +torch.nn.Module is passed, this is used as activation function after any layers, except the last one. If a list of Modules is passed, they are used as activation functions at any layers, in order.

  • | tuple(int) layers (list(int)) – a list containing the number of neurons for @@ -147,13 +151,13 @@

    FeedForward
    Parameters
    -

    x (torch.Tensor) – The tensor to apply the forward pass.

    +

    x (torch.Tensor) – The tensor to apply the forward pass.

    Returns

    the output computed by the model.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

  • @@ -166,15 +170,15 @@

    FeedForward - +


    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/models/fnn_residual.html b/_rst/models/fnn_residual.html index 1d01a726..3f7ae464 100644 --- a/_rst/models/fnn_residual.html +++ b/_rst/models/fnn_residual.html @@ -1,11 +1,13 @@ - + - ResidualFeedForward — PINA 0.1 documentation + ResidualFeedForward — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -54,11 +56,13 @@
  • Solvers
  • Models
  • @@ -115,7 +119,7 @@

    ResidualFeedForward
    class ResidualFeedForward(input_dimensions, output_dimensions, inner_size=20, n_layers=2, func=<class 'torch.nn.modules.activation.Tanh'>, bias=True, transformer_nets=None)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    The PINA implementation of feedforward network, also with skipped connection and transformer network, as presented in Understanding and mitigating gradient pathologies in physics-informed neural networks

    @@ -139,7 +143,7 @@

    ResidualFeedForwardint) – number of hidden layers. Default is 2.

  • func – the activation function to use. If a single -torch.nn.Module is passed, this is used as activation function +torch.nn.Module is passed, this is used as activation function after any layers, except the last one. If a list of Modules is passed, they are used as activation functions at any layers, in order.

  • bias (bool) – If True the MLP will consider some bias.

  • @@ -156,13 +160,13 @@

    ResidualFeedForward
    Parameters
    -

    x (torch.Tensor) – The tensor to apply the forward pass.

    +

    x (torch.Tensor) – The tensor to apply the forward pass.

    Returns

    the output computed by the model.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -182,8 +186,8 @@

    ResidualFeedForward -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/models/fno.html b/_rst/models/fno.html index 2ec15fb2..bf01dc94 100644 --- a/_rst/models/fno.html +++ b/_rst/models/fno.html @@ -1,11 +1,13 @@ - + - FNO — PINA 0.1 documentation + FNO — PINA 0.1.0.post2403 documentation - + + + @@ -21,7 +23,7 @@ - + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -54,11 +56,13 @@
  • Solvers
  • Models
  • @@ -115,21 +119,39 @@

    FNO
    class FNO(lifting_net, projecting_net, n_modes, dimensions=3, padding=8, padding_type='constant', inner_size=20, n_layers=2, func=<class 'torch.nn.modules.activation.Tanh'>, layers=None)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: pina.model.base_no.KernelNeuralOperator

    The PINA implementation of Fourier Neural Operator network.

    -

    Fourier Neural Operator (FNO) is a general architecture for learning Operators. -Unlike traditional machine learning methods FNO is designed to map -entire functions to other functions. It can be trained both with -Supervised learning strategies. FNO does global convolution by performing the -operation on the Fourier space.

    +

    Fourier Neural Operator (FNO) is a general architecture for +learning Operators. Unlike traditional machine learning methods +FNO is designed to map entire functions to other functions. It +can be trained with Supervised learning strategies. FNO does global +convolution by performing the operation on the Fourier space.

    See also

    -

    Original reference: Li, Z., Kovachki, N., Azizzadenesheli, K., Liu, B., -Bhattacharya, K., Stuart, A., & Anandkumar, A. (2020). Fourier neural operator for -parametric partial differential equations. +

    Original reference: Li, Z., Kovachki, N., Azizzadenesheli, +K., Liu, B., Bhattacharya, K., Stuart, A., & Anandkumar, A. +(2020). Fourier neural operator for parametric partial +differential equations. DOI: arXiv preprint arXiv:2010.08895.

    -

    Initializes internal Module state, shared by both nn.Module and ScriptModule.

    +
    +
    Parameters
    +
      +
    • lifting_net (torch.nn.Module) – The neural network for lifting +the input.

    • +
    • projecting_net (torch.nn.Module) – The neural network for +projecting the output.

    • +
    • | list[int] n_modes (int) – Number of modes.

    • +
    • dimensions (int) – Number of dimensions (1, 2, or 3).

    • +
    • padding (int) – Padding size, defaults to 8.

    • +
    • padding_type (str) – Type of padding, defaults to constant.

    • +
    • inner_size (int) – Inner size, defaults to 20.

    • +
    • n_layers (int) – Number of layers, defaults to 2.

    • +
    • func (torch.nn.Module) – Activation function, defaults to nn.Tanh.

    • +
    • layers (list[int]) – List of layer sizes, defaults to None.

    • +
    +
    +
    forward(x)[source]
    @@ -139,17 +161,21 @@

    FNOprojecting_net.

    Parameters
    -

    x (torch.Tensor) – The input tensor for fourier block, depending on -dimension in the initialization. In particular it is expected -* 1D tensors: [batch, X, channels] -* 2D tensors: [batch, X, Y, channels] -* 3D tensors: [batch, X, Y, Z, channels]

    +

    x (torch.Tensor) –

    The input tensor for fourier block, +depending on dimension in the initialization. In +particular it is expected:

    +
      +
    • 1D tensors: [batch, X, channels]

    • +
    • 2D tensors: [batch, X, Y, channels]

    • +
    • 3D tensors: [batch, X, Y, Z, channels]

    • +
    +

    Returns
    -

    The output tensor obtained from the FNO.

    +

    The output tensor obtained from FNO.

    Return type
    -

    torch.Tensor

    +

    torch.Tensor

    @@ -162,15 +188,15 @@

    FNO


    -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/models/multifeedforward.html b/_rst/models/multifeedforward.html index 26d5f6db..7cd96d17 100644 --- a/_rst/models/multifeedforward.html +++ b/_rst/models/multifeedforward.html @@ -1,11 +1,13 @@ - + - MultiFeedForward — PINA 0.1 documentation + MultiFeedForward — PINA 0.1.0.post2403 documentation - + + + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -54,11 +56,13 @@
  • Solvers
  • Models
  • @@ -115,7 +119,7 @@

    MultiFeedForward
    class MultiFeedForward(ffn_dict)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    The PINA implementation of MultiFeedForward network.

    This model allows to create a network with multiple FeedForward combined together. The user has to define the forward method choosing how to @@ -125,7 +129,7 @@

    MultiFeedForward

    ffn_dict (dict) – dictionary of FeedForward networks.

    -

    Initializes internal Module state, shared by both nn.Module and ScriptModule.

    +

    Initialize internal Module state, shared by both nn.Module and ScriptModule.

    @@ -141,8 +145,8 @@

    MultiFeedForward -

    © Copyright Copyright 2021-2023, PINA Contributors. - Last updated on Nov 17, 2023. +

    © Copyright Copyright 2021-2024, PINA Contributors. + Last updated on Mar 01, 2024.

    diff --git a/_rst/models/network.html b/_rst/models/network.html index 9cff1bfa..33df3751 100644 --- a/_rst/models/network.html +++ b/_rst/models/network.html @@ -1,11 +1,13 @@ - + - Network — PINA 0.1 documentation + Network — PINA 0.1.0.post2403 documentation - + + + @@ -20,7 +22,7 @@ - + @@ -37,7 +39,7 @@
    - 0.1 + 0.1.0.post2403
    @@ -54,11 +56,13 @@
  • Solvers
  • Models
  • @@ -115,20 +119,20 @@
    class Network(model, input_variables, output_variables, extra_features=None)[source]
    -

    Bases: torch.nn.modules.module.Module

    +

    Bases: torch.nn.modules.module.Module

    Network class with standard forward method and possibility to pass extra features. This class is used internally in PINA to convert -any torch.nn.Module s in a PINA module.

    +any torch.nn.Module s in a PINA module.

    Parameters
      -
    • model (torch.nn.Module) – The torch model to convert in a PINA model.

    • +
    • model (torch.nn.Module) – The torch model to convert in a PINA model.

    • input_variables (list(str)) – The input variables of the AbstractProblem, whose type depends on the type of domain (spatial, temporal, and parameter).

    • output_variables (list(str)) – The output variables of the AbstractProblem, whose type depends on the problem setting.

    • -
    • extra_features (list(torch.nn.Module)) – List of torch models to augment the input, defaults to None.

    • +
    • extra_features (list(torch.nn.Module)) – List of torch models to augment the input, defaults to None.

    @@ -143,7 +147,7 @@ extraction.

    Parameters
    -

    x (torch.Tensor) – Input of the network.

    +

    x (torch.Tensor) – Input of the network.

    Return torch.Tensor

    Output of the network.

    @@ -184,14 +188,14 @@

    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 IFPR7YFKO?%3;+NC literal 0 HcmV?d00001 diff --git a/_modules/index.html b/_modules/index.html index f2480924..b1f10517 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -1,11 +1,13 @@ - + - Overview: module code — PINA 0.1 documentation + Overview: module code — PINA 0.1.0.post2403 documentation - + + + @@ -35,7 +37,7 @@