diff --git a/docs/source/tutorials/tutorial1/tutorial.html b/docs/source/tutorials/tutorial1/tutorial.html new file mode 100644 index 0000000..fef953e --- /dev/null +++ b/docs/source/tutorials/tutorial1/tutorial.html @@ -0,0 +1,8438 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial10/tutorial.html b/docs/source/tutorials/tutorial10/tutorial.html new file mode 100644 index 0000000..a4a5a9b --- /dev/null +++ b/docs/source/tutorials/tutorial10/tutorial.html @@ -0,0 +1,8362 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial11/tutorial.html b/docs/source/tutorials/tutorial11/tutorial.html new file mode 100644 index 0000000..a726f6b --- /dev/null +++ b/docs/source/tutorials/tutorial11/tutorial.html @@ -0,0 +1,8938 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial12/tutorial.html b/docs/source/tutorials/tutorial12/tutorial.html new file mode 100644 index 0000000..8460c25 --- /dev/null +++ b/docs/source/tutorials/tutorial12/tutorial.html @@ -0,0 +1,8088 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + diff --git a/docs/source/tutorials/tutorial13/tutorial.html b/docs/source/tutorials/tutorial13/tutorial.html new file mode 100644 index 0000000..fc26591 --- /dev/null +++ b/docs/source/tutorials/tutorial13/tutorial.html @@ -0,0 +1,8447 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial14/tutorial.html b/docs/source/tutorials/tutorial14/tutorial.html new file mode 100644 index 0000000..2467970 --- /dev/null +++ b/docs/source/tutorials/tutorial14/tutorial.html @@ -0,0 +1,8311 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial15/tutorial.html b/docs/source/tutorials/tutorial15/tutorial.html new file mode 100644 index 0000000..b85ebea --- /dev/null +++ b/docs/source/tutorials/tutorial15/tutorial.html @@ -0,0 +1,8689 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial16/tutorial.html b/docs/source/tutorials/tutorial16/tutorial.html new file mode 100644 index 0000000..021c13a --- /dev/null +++ b/docs/source/tutorials/tutorial16/tutorial.html @@ -0,0 +1,8440 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + diff --git a/docs/source/tutorials/tutorial17/tutorial.html b/docs/source/tutorials/tutorial17/tutorial.html new file mode 100644 index 0000000..0ad7609 --- /dev/null +++ b/docs/source/tutorials/tutorial17/tutorial.html @@ -0,0 +1,8762 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial18/tutorial.html b/docs/source/tutorials/tutorial18/tutorial.html new file mode 100644 index 0000000..8b0ca8d --- /dev/null +++ b/docs/source/tutorials/tutorial18/tutorial.html @@ -0,0 +1,8214 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+ + +
+
+ +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial19/tutorial.html b/docs/source/tutorials/tutorial19/tutorial.html new file mode 100644 index 0000000..743eea6 --- /dev/null +++ b/docs/source/tutorials/tutorial19/tutorial.html @@ -0,0 +1,8532 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + diff --git a/docs/source/tutorials/tutorial2/tutorial.html b/docs/source/tutorials/tutorial2/tutorial.html new file mode 100644 index 0000000..3e4207a --- /dev/null +++ b/docs/source/tutorials/tutorial2/tutorial.html @@ -0,0 +1,8761 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial20/tutorial.html b/docs/source/tutorials/tutorial20/tutorial.html new file mode 100644 index 0000000..a2daafd --- /dev/null +++ b/docs/source/tutorials/tutorial20/tutorial.html @@ -0,0 +1,8329 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial21/tutorial.html b/docs/source/tutorials/tutorial21/tutorial.html new file mode 100644 index 0000000..4a80567 --- /dev/null +++ b/docs/source/tutorials/tutorial21/tutorial.html @@ -0,0 +1,8317 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial22/tutorial.html b/docs/source/tutorials/tutorial22/tutorial.html new file mode 100644 index 0000000..f446481 --- /dev/null +++ b/docs/source/tutorials/tutorial22/tutorial.html @@ -0,0 +1,15401 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial23/tutorial.html b/docs/source/tutorials/tutorial23/tutorial.html new file mode 100644 index 0000000..1b9f497 --- /dev/null +++ b/docs/source/tutorials/tutorial23/tutorial.html @@ -0,0 +1,10078 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial24/tutorial.html b/docs/source/tutorials/tutorial24/tutorial.html new file mode 100644 index 0000000..ba0002a --- /dev/null +++ b/docs/source/tutorials/tutorial24/tutorial.html @@ -0,0 +1,8413 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial3/tutorial.html b/docs/source/tutorials/tutorial3/tutorial.html new file mode 100644 index 0000000..1b46c98 --- /dev/null +++ b/docs/source/tutorials/tutorial3/tutorial.html @@ -0,0 +1,8516 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial4/tutorial.html b/docs/source/tutorials/tutorial4/tutorial.html new file mode 100644 index 0000000..8ff6adb --- /dev/null +++ b/docs/source/tutorials/tutorial4/tutorial.html @@ -0,0 +1,9147 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial5/tutorial.html b/docs/source/tutorials/tutorial5/tutorial.html new file mode 100644 index 0000000..6783b21 --- /dev/null +++ b/docs/source/tutorials/tutorial5/tutorial.html @@ -0,0 +1,8345 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial6/tutorial.html b/docs/source/tutorials/tutorial6/tutorial.html new file mode 100644 index 0000000..315c31c --- /dev/null +++ b/docs/source/tutorials/tutorial6/tutorial.html @@ -0,0 +1,8587 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+ + diff --git a/docs/source/tutorials/tutorial7/tutorial.html b/docs/source/tutorials/tutorial7/tutorial.html new file mode 100644 index 0000000..28704ae --- /dev/null +++ b/docs/source/tutorials/tutorial7/tutorial.html @@ -0,0 +1,8345 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial8/tutorial.html b/docs/source/tutorials/tutorial8/tutorial.html new file mode 100644 index 0000000..587cab1 --- /dev/null +++ b/docs/source/tutorials/tutorial8/tutorial.html @@ -0,0 +1,8455 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/docs/source/tutorials/tutorial9/tutorial.html b/docs/source/tutorials/tutorial9/tutorial.html new file mode 100644 index 0000000..70b657e --- /dev/null +++ b/docs/source/tutorials/tutorial9/tutorial.html @@ -0,0 +1,8271 @@ + + + + + +tutorial + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ + + diff --git a/tutorials/tutorial1/tutorial.py b/tutorials/tutorial1/tutorial.py index d9b5373..1250a20 100644 --- a/tutorials/tutorial1/tutorial.py +++ b/tutorials/tutorial1/tutorial.py @@ -2,13 +2,13 @@ # coding: utf-8 # # Tutorial: Introductory Tutorial: Physics Informed Neural Networks with PINA -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial1/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial1/tutorial.ipynb) # # > ##### ⚠️ ***Before starting:*** -# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://mathlab.github.io/PINA/_tutorial.html#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. +# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://PINA-org.github.io/PINA-Tutorials/#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. # -# In this tutorial, we will demonstrate a typical use case of **PINA** for Physics-Informed training. We will cover the basics of training a `PhysicsInformedSingleModelSolver` with PINA, if you want to go further into Physics-Informed solvers look at our dedicated [tutorials](https://mathlab.github.io/PINA/_tutorial.html#physics-informed-neural-networks) on the topic. +# In this tutorial, we will demonstrate a typical use case of **PINA** for Physics-Informed training. We will cover the basics of training a `PhysicsInformedSingleModelSolver` with PINA, if you want to go further into Physics-Informed solvers look at our dedicated [tutorials](https://PINA-org.github.io/PINA-Tutorials/#physics-informed-neural-networks) on the topic. # # Let's start by importing the useful modules: @@ -150,13 +150,13 @@ def solution(self, pts): # Once the problem is defined and the data is generated, we can move on to modeling. This process consists of three key steps: # # **Choosing a Model** -# - Select a neural network architecture. You can use the model we provide in the `pina.model` module (see [here](https://mathlab.github.io/PINA/_rst/_code.html#models) for a full list), or define a custom PyTorch module (more on this [here](https://pytorch.org/docs/stable/notes/modules.html)). +# - Select a neural network architecture. You can use the model we provide in the `pina.model` module (see [here](https://PINA-org.github.io/PINA/_rst/_code.html#models) for a full list), or define a custom PyTorch module (more on this [here](https://pytorch.org/docs/stable/notes/modules.html)). # # **Choosing a Physics-Informed Solver & Defining the Trainer** -# * Use a Physics-Informed solver from `pina.solver` module to solve the problem using the specified model. We have already implemented most State-Of-The-Arte solvers for you, [have a look](https://mathlab.github.io/PINA/_rst/_code.html#solvers) if interested. Today we will use the standard `PhysicsInformedSingleModelSolver` solver. +# * Use a Physics-Informed solver from `pina.solver` module to solve the problem using the specified model. We have already implemented most State-Of-The-Arte solvers for you, [have a look](https://PINA-org.github.io/PINA/_rst/_code.html#solvers) if interested. Today we will use the standard `PhysicsInformedSingleModelSolver` solver. # # **Training** -# * Train the model with the [`Trainer`](https://mathlab.github.io/PINA/_rst/trainer.html) class. The Trainer class provides powerful features to enhance model accuracy, optimize training time and memory, and simplify logging and visualization, thanks to PyTorch Lightning's excellent work, see [our dedicated tutorial](https://mathlab.github.io/PINA/tutorial11/tutorial.html) for further details. By default, training metrics (e.g., MSE error) are logged using a lightning logger (CSVLogger). If you prefer manual tracking, use `pina.callback.MetricTracker`. +# * Train the model with the [`Trainer`](https://PINA-org.github.io/PINA/_rst/trainer.html) class. The Trainer class provides powerful features to enhance model accuracy, optimize training time and memory, and simplify logging and visualization, thanks to PyTorch Lightning's excellent work, see [our dedicated tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial11/tutorial.html) for further details. By default, training metrics (e.g., MSE error) are logged using a lightning logger (CSVLogger). If you prefer manual tracking, use `pina.callback.MetricTracker`. # # Let's cover all steps one by one! # @@ -177,7 +177,7 @@ def solution(self, pts): # Then we build the solver. The `PhysicsInformedSingleModelSolver` class needs to be initialized with a `model` and a specific `problem` to be solved. They also take extra arguments, as the optimizer, scheduler, loss type and weighting for the different conditions which are all set to their default values. # # >##### 💡***Bonus tip:*** -# > All physics solvers in PINA can handle both forward and inverse problems without requiring any changes to the model or solver structure! See [our tutorial](https://mathlab.github.io/PINA/tutorial7/tutorial.html) of inverse problems for more infos. +# > All physics solvers in PINA can handle both forward and inverse problems without requiring any changes to the model or solver structure! See [our tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial7/tutorial.html) of inverse problems for more infos. # In[ ]: @@ -270,4 +270,4 @@ def solution(self, pts): # # 5. **... and many more!**: The possibilities are vast! Continue experimenting with advanced configurations, solvers, and other features in PINA. # -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial10/tutorial.py b/tutorials/tutorial10/tutorial.py index bc9248f..15bfd38 100644 --- a/tutorials/tutorial10/tutorial.py +++ b/tutorials/tutorial10/tutorial.py @@ -2,15 +2,15 @@ # coding: utf-8 # # Tutorial: Solving the Kuramoto–Sivashinsky Equation with Averaging Neural Operator -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial10/tutorial.ipynb) -# -# -# In this tutorial, we will build a Neural Operator using the **`AveragingNeuralOperator`** model and the **`SupervisedSolver`**. By the end of this tutorial, you will be able to train a Neural Operator to learn the operator for time-dependent PDEs. -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial10/tutorial.ipynb) +# +# +# In this tutorial, we will build a Neural Operator using the **`AveragingNeuralOperator`** model and the **`SupervisedSingleModelSolver`**. By the end of this tutorial, you will be able to train a Neural Operator to learn the operator for time-dependent PDEs. +# # Let's start by importing the necessary modules. -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -24,12 +24,8 @@ get_ipython().system('pip install "pina-mathlab[tutorial]"') # get the data get_ipython().system('mkdir "data"') - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial10/data/Data_KS.mat" -O "data/Data_KS.mat"' - ) - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial10/data/Data_KS2.mat" -O "data/Data_KS2.mat"' - ) + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial10/data/Data_KS.mat" -O "data/Data_KS.mat"') + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial10/data/Data_KS2.mat" -O "data/Data_KS2.mat"') import torch import matplotlib.pyplot as plt @@ -38,47 +34,47 @@ from scipy import io from pina import Trainer, LabelTensor from pina.model import AveragingNeuralOperator -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.problem.zoo import SupervisedProblem warnings.filterwarnings("ignore") # ## Data Generation -# +# # In this tutorial, we will focus on solving the **Kuramoto-Sivashinsky (KS)** equation, a fourth-order nonlinear PDE. The equation is given by: -# +# # $$ # \frac{\partial u}{\partial t}(x,t) = -u(x,t)\frac{\partial u}{\partial x}(x,t) - \frac{\partial^{4}u}{\partial x^{4}}(x,t) - \frac{\partial^{2}u}{\partial x^{2}}(x,t). # $$ -# +# # In this equation, $x \in \Omega = [0, 64]$ represents a spatial location, and $t \in \mathbb{T} = [0, 50]$ represents time. The function $u(x, t)$ is the value of the function at each point in space and time, with $u(x, t) \in \mathbb{R}$. We denote the solution space as $\mathbb{U}$, where $u \in \mathbb{U}$. -# +# # We impose Dirichlet boundary conditions on the derivative of $u$ at the boundary of the domain $\partial \Omega$: -# +# # $$ # \frac{\partial u}{\partial x}(x,t) = 0 \quad \forall (x,t) \in \partial \Omega \times \mathbb{T}. # $$ -# +# # The initial conditions are sampled from a distribution over truncated Fourier series with random coefficients $\{A_k, \ell_k, \phi_k\}_k$, as follows: -# +# # $$ # u(x,0) = \sum_{k=1}^N A_k \sin\left(2 \pi \frac{\ell_k x}{L} + \phi_k\right), # $$ -# +# # where: # - $A_k \in [-0.4, -0.3]$, # - $\ell_k = 2$, # - $\phi_k = 2\pi \quad \forall k=1,\dots,N$. -# -# We have already generated data for different initial conditions. The goal is to build a Neural Operator that, given $u(x,t)$, outputs $u(x,t+\delta)$, where $\delta$ is a fixed time step. -# +# +# We have already generated data for different initial conditions. The goal is to build a Neural Operator that, given $u(x,t)$, outputs $u(x,t+\delta)$, where $\delta$ is a fixed time step. +# # We will cover the Neural Operator architecture later, but for now, let’s start by importing the data. -# +# # **Note:** # The numerical integration is obtained using a pseudospectral method for spatial derivative discretization and implicit Runge-Kutta 5 for temporal dynamics. -# In[2]: +# In[ ]: # load data @@ -106,10 +102,10 @@ # - `B` is the batch size (i.e., how many initial conditions we sample), # - `N` is the number of points in the mesh (which is the product of the discretization in $x$ times the one in $t$), # - `D` is the dimension of the problem (in this case, we have three variables: $[u, t, x]$). -# +# # We are now going to plot some trajectories! -# In[3]: +# In[ ]: # helper function @@ -170,39 +166,39 @@ def plot_trajectory(coords, real, no_sol=None): # As we can see, as time progresses, the solution becomes chaotic, making it very difficult to learn! We will now focus on building a Neural Operator using the `SupervisedSolver` class to tackle this problem. -# +# # ## Averaging Neural Operator -# +# # We will build a neural operator $\texttt{NO}$, which takes the solution at time $t=0$ for any $x\in\Omega$, the time $t$ at which we want to compute the solution, and gives back the solution to the KS equation $u(x, t)$. Mathematically: -# +# # $$ # \texttt{NO}_\theta : \mathbb{U} \rightarrow \mathbb{U}, # $$ -# +# # such that -# +# # $$ # \texttt{NO}_\theta[u(t=0)](x, t) \rightarrow u(x, t). # $$ -# -# There are many ways to approximate the following operator, for example, by using a 2D [FNO](https://mathlab.github.io/PINA/_rst/model/fourier_neural_operator.html) (for regular meshes), a [DeepOnet](https://mathlab.github.io/PINA/_rst/model/deeponet.html), [Continuous Convolutional Neural Operator](https://mathlab.github.io/PINA/_rst/model/block/convolution.html), or [MIONet](https://mathlab.github.io/PINA/_rst/model/mionet.html). In this tutorial, we will use the *Averaging Neural Operator* presented in [*The Nonlocal Neural Operator: Universal Approximation*](https://arxiv.org/abs/2304.13221), which is a [Kernel Neural Operator](https://mathlab.github.io/PINA/_rst/model/kernel_neural_operator.html) with an integral kernel: -# +# +# There are many ways to approximate the following operator, for example, by using a 2D [FNO](https://PINA-org.github.io/PINA/_rst/model/fourier_neural_operator.html) (for regular meshes), a [DeepOnet](https://PINA-org.github.io/PINA/_rst/model/deeponet.html), [Continuous Convolutional Neural Operator](https://PINA-org.github.io/PINA/_rst/model/block/convolution.html), or [MIONet](https://PINA-org.github.io/PINA/_rst/model/mionet.html). In this tutorial, we will use the *Averaging Neural Operator* presented in [*The Nonlocal Neural Operator: Universal Approximation*](https://arxiv.org/abs/2304.13221), which is a [Kernel Neural Operator](https://PINA-org.github.io/PINA/_rst/model/kernel_neural_operator.html) with an integral kernel: +# # $$ # K(v) = \sigma\left(Wv(x) + b + \frac{1}{|\Omega|}\int_\Omega v(y)dy\right) # $$ -# +# # where: -# +# # * $v(x) \in \mathbb{R}^{\rm{emb}}$ is the update for a function $v$, with $\mathbb{R}^{\rm{emb}}$ being the embedding (hidden) size. # * $\sigma$ is a non-linear activation function. # * $W \in \mathbb{R}^{\rm{emb} \times \rm{emb}}$ is a tunable matrix. # * $b \in \mathbb{R}^{\rm{emb}}$ is a tunable bias. -# -# In PINA, many Kernel Neural Operators are already implemented. The modular components of the [Kernel Neural Operator](https://mathlab.github.io/PINA/_rst/model/kernel_neural_operator.html) class allow you to create new ones by composing base kernel layers. -# -# **Note:** We will use the already built class `AveragingNeuralOperator`. As a constructive exercise, try to use the [KernelNeuralOperator](https://mathlab.github.io/PINA/_rst/model/kernel_neural_operator.html) class to build a kernel neural operator from scratch. You might employ the different layers that we have in PINA, such as [FeedForward](https://mathlab.github.io/PINA/_rst/model/feed_forward.html) and [AveragingNeuralOperator](https://mathlab.github.io/PINA/_rst/model/average_neural_operator.html) layers. +# +# In PINA, many Kernel Neural Operators are already implemented. The modular components of the [Kernel Neural Operator](https://PINA-org.github.io/PINA/_rst/model/kernel_neural_operator.html) class allow you to create new ones by composing base kernel layers. +# +# **Note:** We will use the already built class `AveragingNeuralOperator`. As a constructive exercise, try to use the [KernelNeuralOperator](https://PINA-org.github.io/PINA/_rst/model/kernel_neural_operator.html) class to build a kernel neural operator from scratch. You might employ the different layers that we have in PINA, such as [FeedForward](https://PINA-org.github.io/PINA/_rst/model/feed_forward.html) and [AveragingNeuralOperator](https://PINA-org.github.io/PINA/_rst/model/average_neural_operator.html) layers. -# In[4]: +# In[ ]: class SIREN(torch.nn.Module): @@ -226,10 +222,10 @@ def forward(self, x): # Super easy! Notice that we use the `SIREN` activation function, which is discussed in more detail in the paper [Implicit Neural Representations with Periodic Activation Functions](https://arxiv.org/abs/2006.09661). -# +# # ## Solving the KS problem -# -# We will now focus on solving the KS equation using the `SupervisedSolver` class and the `AveragingNeuralOperator` model. As done in the [FNO tutorial](https://github.com/PINA-org/PINA-tutorials/blob/main/tutorial5/tutorial.ipynb), we now create the Neural Operator problem class with `SupervisedProblem`. +# +# We will now focus on solving the KS equation using the `SupervisedSingleModelSolver` class and the `AveragingNeuralOperator` model. As done in the [FNO tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial5/tutorial.html), we now create the Neural Operator problem class with `SupervisedProblem`. # In[ ]: @@ -242,7 +238,7 @@ def forward(self, x): output_variables=sol_train.labels, ) # initialize solver -solver = SupervisedSolver(problem=problem, model=model) +solver = SupervisedSingleModelSolver(problem=problem, model=model) # train, only CPU and avoid model summary at beginning of training (optional) trainer = Trainer( solver=solver, @@ -259,7 +255,7 @@ def forward(self, x): # We can now visualize some plots for the solutions! -# In[6]: +# In[ ]: sample_number = 2 @@ -271,10 +267,10 @@ def forward(self, x): ) -# As we can see, we can obtain nice results considering the small training time and the difficulty of the problem! +# As we can see, we can obtain nice results considering the small training time and the difficulty of the problem! # Let's take a look at the training and testing error: -# In[7]: +# In[ ]: from pina.loss import PowerLoss @@ -297,13 +293,13 @@ def forward(self, x): # As we can see, the error is pretty small, which aligns with the observations from the previous plots. # ## What's Next? -# +# # You have completed the tutorial on solving time-dependent PDEs using Neural Operators in **PINA**. Great job! Here are some potential next steps you can explore: -# +# # 1. **Train the network for longer or with different layer sizes**: Experiment with various configurations, such as adjusting the number of layers or hidden dimensions, to further improve accuracy and observe the impact on performance. -# +# # 2. **Use a more challenging dataset**: Try using the more complex dataset [Data_KS2.mat](dat/Data_KS2.mat) where $A_k \in [-0.5, 0.5]$, $\ell_k \in [1, 2, 3]$, and $\phi_k \in [0, 2\pi]$ for a more difficult task. This dataset may require longer training and testing. -# -# 3. **... and many more...**: Explore other models, such as the [FNO](https://mathlab.github.io/PINA/_rst/models/fno.html), [DeepOnet](https://mathlab.github.io/PINA/_rst/models/deeponet.html), or implement your own operator using the [KernelNeuralOperator](https://mathlab.github.io/PINA/_rst/models/base_no.html) class to compare performance and find the best model for your task. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# 3. **... and many more...**: Explore other models, such as the [FNO](https://PINA-org.github.io/PINA/_rst/models/fno.html), [DeepOnet](https://PINA-org.github.io/PINA/_rst/models/deeponet.html), or implement your own operator using the [KernelNeuralOperator](https://PINA-org.github.io/PINA/_rst/models/base_no.html) class to compare performance and find the best model for your task. +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial11/tutorial.py b/tutorials/tutorial11/tutorial.py index 01067d1..c44ef0c 100644 --- a/tutorials/tutorial11/tutorial.py +++ b/tutorials/tutorial11/tutorial.py @@ -2,9 +2,9 @@ # coding: utf-8 # # Tutorial: Introduction to `Trainer` class -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial11/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial11/tutorial.ipynb) # -# In this tutorial, we will delve deeper into the functionality of the `Trainer` class, which serves as the cornerstone for training **PINA** [Solvers](https://mathlab.github.io/PINA/_rst/_code.html#solvers). +# In this tutorial, we will delve deeper into the functionality of the `Trainer` class, which serves as the cornerstone for training **PINA** [Solvers](https://PINA-org.github.io/PINA/_rst/_code.html#solvers). # # The `Trainer` class offers a plethora of features aimed at improving model accuracy, reducing training time and memory usage, facilitating logging visualization, and more thanks to the amazing job done by the PyTorch Lightning team! # @@ -92,7 +92,7 @@ # ## Trainer Logging # -# In **PINA** you can log metrics in different ways. The simplest approach is to use the `MetricTracker` class from `pina.callback`, as seen in the [*Introduction to Physics Informed Neural Networks training*](https://github.com/PINA-org/PINA-tutorials/blob/main/tutorial1/tutorial.ipynb) tutorial. +# In **PINA** you can log metrics in different ways. The simplest approach is to use the `MetricTracker` class from `pina.callback`, as seen in the [*Introduction to Physics Informed Neural Networks training*](https://PINA-org.github.io/PINA-Tutorials/tutorial1/tutorial.html) tutorial. # # However, especially when we need to train multiple times to get an average of the loss across multiple runs, `lightning.pytorch.loggers` might be useful. Here we will use `TensorBoardLogger` (more on [logging](https://lightning.ai/docs/pytorch/stable/extensions/logging.html) here), but you can choose the one you prefer (or make your own one). # @@ -355,5 +355,5 @@ def on_train_epoch_end(self, trainer, __): # # 4. **...and many more!**: Consider expanding to **multi-GPU** setups or other advanced configurations for large-scale training. # -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). # diff --git a/tutorials/tutorial12/tutorial.py b/tutorials/tutorial12/tutorial.py index 0cb1403..ef00281 100644 --- a/tutorials/tutorial12/tutorial.py +++ b/tutorials/tutorial12/tutorial.py @@ -2,18 +2,18 @@ # coding: utf-8 # # Tutorial: Introduction to PINA `Equation` class -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial12/tutorial.ipynb) -# -# -# In this tutorial, we will explore how to use the `Equation` class in **PINA**. We will focus on how to leverage this class, along with its inherited subclasses, to enforce residual minimization in **Physics-Informed Neural Networks (PINNs)**. -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial12/tutorial.ipynb) +# +# +# In this tutorial, we will explore how to use the `Equation` class in **PINA**. We will focus on how to leverage this class, along with its inherited subclasses, to enforce residual minimization in **Physics-Informed Solvers**. +# # By the end of this guide, you'll understand how to integrate physical laws and constraints directly into your model training, ensuring that the solution adheres to the underlying differential equations. -# -# +# +# # ## Example: The Burgers 1D equation # We will start implementing the viscous Burgers 1D problem Class, described as follows: -# +# # $$ # \begin{equation} # \begin{cases} @@ -23,9 +23,9 @@ # \end{cases} # \end{equation} # $$ -# +# # where we set $ \nu = \frac{0.01}{\pi}$. -# +# # In the class that models this problem we will see in action the `Equation` class and one of its inherited classes, the `FixedValue` class. # In[1]: @@ -46,11 +46,13 @@ # useful imports from pina import Condition from pina.problem import SpatialProblem, TimeDependentProblem -from pina.equation import Equation, FixedValue +from pina.equation import Equation +from pina.equation.zoo import FixedValue from pina.domain import CartesianDomain -from pina.operator import grad, fast_grad, laplacian +from pina.operator import grad, laplacian -# Let's begin by defining the Burgers equation and its initial condition as Python functions. These functions will take the model's `input` (spatial and temporal coordinates) and `output` (predicted solution) as arguments. The goal is to compute the residuals for the Burgers equation, which we will minimize during training. + +# Let's begin by defining the Burgers equation and its initial condition as Python functions. These functions will take the model's `input_` (spatial and temporal coordinates) and `output_` (predicted solution) as arguments. The goal is to compute the residuals for the Burgers equation, which we will minimize during training. # In[2]: @@ -73,12 +75,12 @@ def initial_condition(input_, output_): # - `input_`: A tensor with respect to which the operator is computed. # - `components`: The names of the output variables for which the operator is evaluated. # - `d`: The names of the variables with respect to which the operator is computed. -# +# # Each differential operator has its **fast** version, which performs no internal checks on input and output tensors. For these methods, the user is always required to specify both ``components`` and ``d`` as lists of strings. -# +# # Let's define now the problem! -# -# > **👉 Do you want to learn more on Problems? Check the dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) to learn how to build a Problem from scratch.** +# +# > **👉 Do you want to learn more on Problems? Check the dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) to learn how to build a Problem from scratch.** # In[3]: @@ -107,25 +109,23 @@ class Burgers1D(TimeDependentProblem, SpatialProblem): } -# The `Equation` class takes as input a function (in this case it happens twice, with `initial_condition` and `burgers_equation`) which computes a residual of an equation, such as a PDE. In a problem class such as the one above, the `Equation` class with such a given input is passed as a parameter in the specified `Condition`. -# -# The `FixedValue` class takes as input a value of the same dimensions as the output functions. This class can be used to enforce a fixed value for a specific condition, such as Dirichlet boundary conditions, as demonstrated in our example. -# -# Once the equations are set as above in the problem conditions, the PINN solver will aim to minimize the residuals described in each equation during the training phase. -# -# ### Available classes of equations: -# - `FixedGradient` and `FixedFlux`: These work analogously to the `FixedValue` class, where we can enforce a constant value on the gradient or the divergence of the solution, respectively. -# - `Laplace`: This class can be used to enforce that the Laplacian of the solution is zero. -# - `SystemEquation`: This class allows you to enforce multiple conditions on the same subdomain by passing a list of residual equations defined in the problem. -# +# +# +# +# The `Equation` class takes as input a function that computes the residual of an equation, such as a PDE. In the example above, this is done twice, using `initial_condition` and `burgers_equation`. Once defined, each `Equation` object is passed to a specific `Condition` inside the problem class. When multiple residual equations need to be enforced on the same subdomain, they can be grouped using `SystemEquation`, which takes a list of equations defined in the problem. +# +# For common use cases, PINA also provides several predefined equation classes. The `FixedValue`, `FixedGradient`, `FixedFlux`, and `FixedLaplacian` classes can be used to enforce fixed constraints on the solution or on its derivatives. In particular, `FixedValue` enforces a constant value with the same dimensions as the output function, making it suitable for conditions such as Dirichlet boundary conditions. Similarly, `FixedGradient` and `FixedFlux` enforce fixed values on the gradient or flux of the solution, respectively, while `FixedLaplacian` can be used to impose a fixed value on the Laplacian of the solution. Many ready-to-use equations are also available in the `pina.equation.zoo` module. +# +# Once the equations are assigned to the problem conditions, the physics-informed solver aims to minimize the residuals defined by each equation during the training phase. +# # ## Defining a new Equation class # `Equation` classes can also be inherited to define a new class. For example, we can define a new class `Burgers1D` to represent the Burgers equation. During the class call, we can pass the viscosity parameter $\nu$: -# +# # ```python # class Burgers1D(Equation): # def __init__(self, nu): # self.nu = nu -# +# # def equation(self, input_, output_): # ... # ``` @@ -190,14 +190,14 @@ def initial_condition(input_, output_): # ## What's Next? -# +# # Congratulations on completing the `Equation` class tutorial of **PINA**! As we've seen, you can build new classes that inherit from `Equation` to store more complex equations, such as the 1D Burgers equation, by simply passing the characteristic coefficients of the problem. -# +# # From here, you can: -# +# # - **Define Additional Complex Equation Classes**: Create your own equation classes, such as `SchrodingerEquation`, `NavierStokesEquation`, etc. -# - **Define More `FixedOperator` Classes**: Implement operators like `FixedCurl`, `FixedDivergence`, and others for more advanced simulations. +# - **Define More `FixedOperator` Classes**: Implement operators like `FixedCurl` for more advanced simulations. # - **Integrate Custom Equations and Operators**: Combine your custom equations and operators into larger systems for more complex simulations. # - **and many more!**: Explore for example different residual minimization techniques to improve the performance and accuracy of your models. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial13/tutorial.py b/tutorials/tutorial13/tutorial.py index 86ff592..6d72105 100644 --- a/tutorials/tutorial13/tutorial.py +++ b/tutorials/tutorial13/tutorial.py @@ -3,7 +3,7 @@ # # Tutorial: Learning Multiscale PDEs Using Fourier Feature Networks # -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial13/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial13/tutorial.ipynb) # # This tutorial demonstrates how to solve a PDE with multiscale behavior using Physics-Informed Neural Networks (PINNs), as discussed in [*On the Eigenvector Bias of Fourier Feature Networks: From Regression to Solving Multi-Scale PDEs with Physics-Informed Neural Networks*](https://doi.org/10.1016/j.cma.2021.113938). # @@ -68,7 +68,7 @@ # While this example is simple and pedagogical, it's important to note that the solution exhibits low-frequency behavior in the macro-scale and high-frequency behavior in the micro-scale. This characteristic is common in many practical scenarios. # # Below is the implementation of the `Poisson` problem as described mathematically above. -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** # In[ ]: @@ -113,7 +113,7 @@ def solution(self, x): # # However, solving high-frequency or multi-scale problems presents significant challenges to PINNs, especially when the number of data points is insufficient to capture the different scales effectively. # -# Below, we run a simulation using both the `PhysicsInformedSingleModelSolver` solver and the `SelfAdaptivePhysicsInformedSolver` solver, employing a [`FeedForward`](https://mathlab.github.io/PINA/_modules/pina/model/feed_forward.html#FeedForward) model. +# Below, we run a simulation using both the `PhysicsInformedSingleModelSolver` solver and the `SelfAdaptivePhysicsInformedSolver` solver, employing a [`FeedForward`](https://PINA-org.github.io/PINA/_modules/pina/model/feed_forward.html#FeedForward) model. # # In[ ]: @@ -213,7 +213,7 @@ def plot_solution(pinn_to_use, title): # # where $\mathbf{B}_{ij} \sim \mathcal{N}(0, \sigma^2)$. This simple operation allows the network to learn across multiple scales! # -# In **PINA**, we have already implemented this feature as a `layer` called [`FourierFeatureEmbedding`](https://mathlab.github.io/PINA/_rst/layers/fourier_embedding.html). Below, we will build the *Multi-scale Fourier Feature Architecture*. In this architecture, multiple Fourier feature embeddings (initialized with different $\sigma$ values) are applied to the input coordinates. These embeddings are then passed through the same fully-connected neural network, and the outputs are concatenated with a final linear layer. +# In **PINA**, we have already implemented this feature as a `layer` called [`FourierFeatureEmbedding`](https://PINA-org.github.io/PINA/_rst/layers/fourier_embedding.html). Below, we will build the *Multi-scale Fourier Feature Architecture*. In this architecture, multiple Fourier feature embeddings (initialized with different $\sigma$ values) are applied to the input coordinates. These embeddings are then passed through the same fully-connected neural network, and the outputs are concatenated with a final linear layer. # # In[ ]: @@ -289,4 +289,4 @@ def forward(self, x): # # 4. **...and many more!**: There are countless directions to further explore, from testing on different problems to refining the model architecture. # -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial14/tutorial.py b/tutorials/tutorial14/tutorial.py index 42fb59b..1c684a2 100644 --- a/tutorials/tutorial14/tutorial.py +++ b/tutorials/tutorial14/tutorial.py @@ -3,7 +3,7 @@ # # Tutorial: Learning Bifurcating PDE Solutions with Physics-Informed Deep Ensembles # -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial14/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial14/tutorial.ipynb) # # This tutorial demonstrates how to use the Deep Ensemble Physics Informed Network (DeepEnsemblePINN) to learn PDEs exhibiting bifurcating behavior, as discussed in [*Learning and Discovering Multiple Solutions Using Physics-Informed Neural Networks with Random Initialization and Deep Ensemble*](https://arxiv.org/abs/2503.06320). # @@ -95,9 +95,9 @@ # # We first write the problem class, we do not write the boundary conditions as we will hard impose them. # -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) to teach how to build a Problem — have a look if you're interested!** +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) to teach how to build a Problem — have a look if you're interested!** # -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial3/tutorial.html) to teach how to impose hard constraints — have a look if you're interested!** +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial3/tutorial.html) to teach how to impose hard constraints — have a look if you're interested!** # In[ ]: @@ -288,4 +288,4 @@ def on_train_epoch_start(self, trainer, pl_module): # # 3. **...and many more!**: There are countless directions to further explore, for example, what does it happen when you vary the network initialization hyperparameters? # -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial15/tutorial.py b/tutorials/tutorial15/tutorial.py index 3e2e8fb..112a851 100644 --- a/tutorials/tutorial15/tutorial.py +++ b/tutorials/tutorial15/tutorial.py @@ -2,18 +2,18 @@ # coding: utf-8 # # Tutorial: Chemical Properties Prediction with Graph Neural Networks -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial15/tutorial.ipynb) -# -# In this tutorial we will use **Graph Neural Networks** (GNNs) for chemical properties prediction. Chemical properties prediction involves estimating or determining the physical, chemical, or biological characteristics of molecules based on their structure. -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial15/tutorial.ipynb) +# +# In this tutorial we will use **Graph Neural Networks** (GNNs) for chemical properties prediction. Chemical properties prediction involves estimating or determining the physical, chemical, or biological characteristics of molecules based on their structure. +# # Molecules can naturally be represented as graphs, where atoms serve as the nodes and chemical bonds as the edges connecting them. This graph-based structure makes GNNs a great fit for predicting chemical properties. -# +# # In the tutorial we will use the [QM9 dataset](https://pytorch-geometric.readthedocs.io/en/latest/generated/torch_geometric.datasets.QM9.html#torch_geometric.datasets.QM9) from Pytorch Geometric. The dataset contains small molecules, each consisting of up to 29 atoms, with every atom having a corresponding 3D position. Each atom is also represented by a five-dimensional one-hot encoded vector that indicates the atom type (H, C, N, O, F). -# +# # First of all, let's start by importing useful modules! -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -30,9 +30,10 @@ import warnings from pina import Trainer -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.problem.zoo import SupervisedProblem +from torch_geometric.data import Batch from torch_geometric.datasets import QM9 from torch_geometric.nn import GCNConv, global_mean_pool @@ -42,7 +43,7 @@ # ## Download Data and create the Problem # We download the dataset and save the molecules as a list of `Data` objects (`input_`), where each element contains one molecule encoded in a graph structure. The corresponding target properties (`target_`) are listed below: -# +# # | Target | Property | Description | Unit | # |--------|----------------------------------|-----------------------------------------------------------------------------------|---------------------------------------------| # | 0 | $\mu$ | Dipole moment | $D$ | @@ -64,9 +65,9 @@ # | 16 | $A$ | Rotational constant | $GHz$ | # | 17 | $B$ | Rotational constant | $GHz$ | # | 18 | $C$ | Rotational constant | $GHz$ | -# +# -# In[2]: +# In[ ]: # download the data + shuffling @@ -74,7 +75,7 @@ # save the dataset input_ = [data for data in dataset] -target_ = torch.cat([data.y for data in dataset]) +target_ = torch.stack([data.y for data in dataset], dim=0) # normalize the target mean = target_.mean(dim=0, keepdim=True) @@ -82,9 +83,9 @@ target_ = (target_ - mean) / std -# Great! Once the data are downloaded, building the problem is straightforward by using the [`SupervisedProblem`](https://mathlab.github.io/PINA/_rst/problem/zoo/supervised_problem.html) class. +# Great! Once the data are downloaded, building the problem is straightforward by using the [`SupervisedProblem`](https://PINA-org.github.io/PINA/_rst/problem/zoo/supervised_problem.html) class. -# In[3]: +# In[ ]: # build the problem @@ -92,12 +93,12 @@ # ## Build the Model -# +# # To predict molecular properties, we will construct a simple Convolutional Graph Neural Network using the [`GCNConv`]() module from PyG. While this tutorial focuses on a straightforward model, more advanced architectures—such as Equivariant Networks—could potentially yield better performance. Please note that this tutorial serves only for demonstration purposes. -# +# # **Importantly** notice that in the `forward` pass we pass a data object as input, and unpack inside the graph attributes. This is the only requirement in **PINA** to use graphs and solvers together. -# In[4]: +# In[ ]: class GNN(torch.nn.Module): @@ -118,14 +119,14 @@ def forward(self, data): # ## Train the Model -# -# Now that the problem is created and the model is built, we can train the model using the [`SupervisedSolver`](https://mathlab.github.io/PINA/_rst/solver/supervised.html), which is the solver for standard supervised learning task. We will optimize the Maximum Absolute Error and test on the same metric. In the [`Trainer`](https://mathlab.github.io/PINA/_rst/trainer.html) class we specify the optimization hyperparameters. +# +# Now that the problem is created and the model is built, we can train the model using the [`SupervisedSingleModelSolver`](https://PINA-org.github.io/PINA/_rst/solver/supervised.html), which is the solver for standard supervised learning task. We will optimize the Maximum Absolute Error and test on the same metric. In the [`Trainer`](https://PINA-org.github.io/PINA/_rst/trainer.html) class we specify the optimization hyperparameters. # In[ ]: # define the solver -solver = SupervisedSolver( +solver = SupervisedSingleModelSolver( problem=problem, model=GNN(in_features=11, out_features=19), use_lt=False, @@ -146,46 +147,49 @@ def forward(self, data): # ## Testing Chemical Predictions -# In[6]: +# In[ ]: -_ = trainer.test() +trainer.test() # We observe that the model achieves an average error of approximately 0.4 MAE across all property predictions. This error is an average, but we can also inspect the error for each individual property prediction. -# -# To do this, we need access to the test dataset, which can be retrieved from the trainer's datamodule. Each datamodule contains both the dataloader and dataset objects. For the dataset, we can use the [`get_all_data()`](https://mathlab.github.io/PINA/_rst/data/dataset.html#pina.data.dataset.PinaDataset.get_all_data) method. This function returns the entire dataset as a dictionary, where the keys represent the Condition names, and the values are dictionaries containing input and target tensors. +# +# To do this, we need access to the test dataset, which can be retrieved from the trainer's datamodule. Each datamodule contains both the dataloader and dataset objects. For the dataset, we can use the [`get_all_data()`](https://PINA-org.github.io/PINA/_rst/data/dataset.html#pina.data.dataset.PinaDataset.get_all_data) method. This function returns the entire dataset as a dictionary, where the keys represent the Condition names, and the values are dictionaries containing input and target tensors. -# In[7]: +# In[ ]: # get the test dataset -test_dataset = trainer.datamodule.test_dataset.get_all_data() +test_dataset = trainer.datamodule.test_datasets["data"].get_all_data() print("Here the dataset") -print(f"Dataset keys: {test_dataset.keys()}") -print(f"Dataset keys for data condition: {test_dataset['data'].keys()}") +# print(f"Dataset keys: {test_dataset.keys()}") +print(f"Dataset keys for data condition: {test_dataset.keys}") print( - f"Dataset values type for data condition: {[v.__class__.__name__ for v in test_dataset['data'].values()]}" + f"Dataset values type for data condition: {[v.__class__.__name__ for v in test_dataset]}" ) # extract input and target for test dataset -input_test = test_dataset["data"]["input"] -target_test = test_dataset["data"]["target"] +input_test = test_dataset.input +target_test = test_dataset.target.squeeze() + +# transform the input into a batch +batch = Batch.from_data_list(input_test) -# Now we obtain the prediction my calling the forward pass for the `SupervisedSolver`. +# Now we obtain the prediction my calling the forward pass for the `SupervisedSingleModelSolver`. -# In[8]: +# In[ ]: # get the prediction -prediction_test = solver(input_test) +prediction_test = solver(batch) print(f"Number of prediction properties: {prediction_test.shape[-1]}") # As you can see we obtain a tensor with 19 prediction properties as output, which is what we are looking for. Now let's compute the error for each property: -# In[9]: +# In[ ]: properties = [ @@ -242,7 +246,7 @@ def forward(self, data): # We can see that predicting the some properties are easier and some harder to predict. For example, the rotational constant $A$ is way easier to predict than dipole moment $\mu$. To have a better idea we can also plot a scatter plot between predicted and observed properties: -# In[10]: +# In[ ]: import matplotlib.pyplot as plt @@ -301,15 +305,15 @@ def forward(self, data): # By looking more into details, we can see that $A$ is not predicted that well, but the small values of the quantity lead to a lower MAE than the other properties. From the plot we can see that the atomatization energies, free energy and enthalpy are the predicted properties with higher correlation with the true chemical properties. # ## What's Next? -# +# # Congratulations on completing the tutorial on chemical properties prediction with **PINA**! Now that you've got the basics, there are several exciting directions to explore: -# +# # 1. **Train the network for longer or with different layer sizes**: Experiment with various configurations to see how the network's accuracy improves. -# +# # 2. **Use a different network**: For example, Equivariant Graph Neural Networks (EGNNs) have shown great results on molecular tasks by leveraging group symmetries. If you're interested, check out [*E(n) Equivariant Graph Neural Networks*](https://arxiv.org/abs/2102.09844) for more details. -# +# # 3. **What if the input is time-dependent?**: For example, predicting force fields in Molecular Dynamics simulations. In PINA, you can predict force fields with ease, as it's still a supervised learning task. If this interests you, have a look at [*Machine Learning Force Fields*](https://pubs.acs.org/doi/10.1021/acs.chemrev.0c01111). -# +# # 4. **...and many more!**: The possibilities are vast, including exploring new architectures, working with larger datasets, and applying this framework to more complex systems. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial16/tutorial.py b/tutorials/tutorial16/tutorial.py index e0d39ca..89f59cf 100644 --- a/tutorials/tutorial16/tutorial.py +++ b/tutorials/tutorial16/tutorial.py @@ -3,7 +3,7 @@ # # Tutorial: How to build a Problem in PINA # -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial16/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial16/tutorial.ipynb) # # In this tutorial, we will demonstrate how to build a **Problem** in **PINA** using a toy example. The tutorial will cover the following topics: @@ -60,9 +60,9 @@ # # # other stuff ... # ``` -# Observe that we define `input_variables` and `output_variables` as lists of symbols. This is because, in PINA, `torch.Tensors` can be labeled (see [`LabelTensor`](https://mathlab.github.io/PINA/_rst/label_tensor.html)), providing maximum flexibility for tensor manipulation. If you prefer to use regular tensors, you can simply set these to ``None``. +# Observe that we define `input_variables` and `output_variables` as lists of symbols. This is because, in PINA, `torch.Tensors` can be labeled (see [`LabelTensor`](https://PINA-org.github.io/PINA/_rst/label_tensor.html)), providing maximum flexibility for tensor manipulation. If you prefer to use regular tensors, you can simply set these to ``None``. # -# To specify the input and target data, you need to use the [`Condition`](https://mathlab.github.io/PINA/_rst/condition/condition.html) interface. A condition defines the constraints (such as physical equations, boundary conditions, etc.) that must be satisfied within the problem. Once the condition is applied, the full problem is outlined below: +# To specify the input and target data, you need to use the [`Condition`](https://PINA-org.github.io/PINA/_rst/condition/condition.html) interface. A condition defines the constraints (such as physical equations, boundary conditions, etc.) that must be satisfied within the problem. Once the condition is applied, the full problem is outlined below: # In[ ]: @@ -102,7 +102,7 @@ class SupervisedProblem(BaseProblem): problem.are_all_domains_discretised -# >👉 **You can use multiple data structures in PINA conditions, including `Graph` or `Data` from `PyG`. To explore the different data structures available in PINA, check out [this tutorial](), and for more information on input-target conditions, visit the conditions factory classes [here](https://mathlab.github.io/PINA/_rst/condition/input_target_condition.html)**. +# >👉 **You can use multiple data structures in PINA conditions, including `Graph` or `Data` from `PyG`. To explore the different data structures available in PINA, check out [this tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial19/tutorial.html), and for more information on input-target conditions, visit the conditions factory classes [here](https://PINA-org.github.io/PINA/_rst/condition/input_target_condition.html)**. # ### Simple Ordinary Differential Equation # What if we don't have data but we know the physical laws that define the data? Then physics-informed training is the solution! As an example, consider the following Ordinary Differential Equation (ODE): @@ -120,7 +120,7 @@ class SupervisedProblem(BaseProblem): # * `SpatialProblem` $\rightarrow$ a differential equation with spatial variable(s) # * `TimeDependentProblem` $\rightarrow$ a time-dependent differential equation with temporal variable(s) # * `ParametricProblem` $\rightarrow$ a parametrized differential equation with parametric variable(s) -# * `InverseProblem` $\rightarrow$ this is a more advanced topic, see [this tutorial](https://mathlab.github.io/PINA/tutorial7/tutorial.html) for more details. +# * `InverseProblem` $\rightarrow$ this is a more advanced topic, see [this tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial7/tutorial.html) for more details. # # In our case, the physical ODE inherits from the `SpatialProblem` class, since only spatial variables define the ODE. # @@ -146,7 +146,7 @@ class SupervisedProblem(BaseProblem): # # other stuff ... # ``` # -# Differently from data-driven problems, differential-problems need to specify the domain type. If you look at our ODE definition, the spatial varibale $x$ is defined in the interval $(0,1)$, and accordingly the `spatial_domain` is a `CartesianDomain` with the input variable `x` in `[0,1]`. To know more about the Domain class see the [related tutorial](https://mathlab.github.io/PINA/tutorial6/tutorial.html). Different problems require different domain, here below we summarize the relevant ones: +# Differently from data-driven problems, differential-problems need to specify the domain type. If you look at our ODE definition, the spatial varibale $x$ is defined in the interval $(0,1)$, and accordingly the `spatial_domain` is a `CartesianDomain` with the input variable `x` in `[0,1]`. To know more about the Domain class see the [related tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial6/tutorial.html). Different problems require different domain, here below we summarize the relevant ones: # # | Problem Type | Required Domain | # |-------------------------|--------------------------------| @@ -201,17 +201,17 @@ def solution(self, pts): return torch.exp(pts.extract(["x"])) -# As you can see, we implemented the `ode_equation` function which given the model ouput and input returns the equation residual. These residuals are the ones minimized during PINN optimization (for more on PINN see [the related tutorials](https://mathlab.github.io/PINA/_tutorial.html#physics-informed-neural-networks)). +# As you can see, we implemented the `ode_equation` function which given the model ouput and input returns the equation residual. These residuals are the ones minimized during PINN optimization (for more on PINN see [the related tutorials](https://PINA-org.github.io/PINA-Tutorials/#physics-informed-neural-networks)). # # How are the residuals computed? -# Given the output we perform differential operation using the [operator modulus](https://mathlab.github.io/PINA/_rst/operator.html). It is pretty intuitive, each differential operator takes the following inputs: +# Given the output we perform differential operation using the [operator modulus](https://PINA-org.github.io/PINA/_rst/operator.html). It is pretty intuitive, each differential operator takes the following inputs: # - A tensor on which the operator is applied. # - A tensor with respect to which the operator is computed. # - The names of the output variables for which the operator is evaluated. # - The names of the variables with respect to which the operator is computed. # We also have a `fast` version of differential operators, where no checks are performed. This can be used to boost performances, once you know the standard ones are doing their job. # -# Notice that we do not pass directly a `python` function, but an `Equation` object, which is initialized with the `python` function. This is done so that all the computations and internal checks are done inside **PINA**, see [the related tutorials](https://mathlab.github.io/PINA/tutorial12/tutorial.html) for more. +# Notice that we do not pass directly a `python` function, but an `Equation` object, which is initialized with the `python` function. This is done so that all the computations and internal checks are done inside **PINA**, see [the related tutorials](https://PINA-org.github.io/PINA-Tutorials/tutorial12/tutorial.html) for more. # # Once we have defined the function, we need to tell the neural network where these methods are to be applied. To do so, we use again the `Condition` class. In the `Condition` class, we pass the location points and the equation we want minimized on those points. # @@ -289,7 +289,7 @@ def solution(self, pts): # ## The Problem Zoo module # -# In PINA many problems are already implemented for you in the [Problem Zoo module](https://mathlab.github.io/PINA/_rst/_code.html#problems-zoo). For example, the supervised problem at the beginning of the tutorial is implemented in [`SupervisedProblem`](https://mathlab.github.io/PINA/_rst/problem/zoo/supervised_problem.html)! +# In PINA many problems are already implemented for you in the [Problem Zoo module](https://PINA-org.github.io/PINA/_rst/_code.html#problems-zoo). For example, the supervised problem at the beginning of the tutorial is implemented in [`SupervisedProblem`](https://PINA-org.github.io/PINA/_rst/problem/zoo/supervised_problem.html)! # # Let's see now a physics based example, the advection equation @@ -317,8 +317,8 @@ def solution(self, pts): # # 1. **Create Custom Problems**: Try building your own problems using the PINA framework, experiment with different PDEs, initial/boundary conditions, and data structures. # -# 2. **Explore the Problem Zoo**: Dive into the [`problem.zoo` module](https://mathlab.github.io/PINA/_rst/_code.html#problems-zoo) to find a variety of predefined problem setups and use them as a starting point or inspiration for your own. +# 2. **Explore the Problem Zoo**: Dive into the [`problem.zoo` module](https://PINA-org.github.io/PINA/_rst/_code.html#problems-zoo) to find a variety of predefined problem setups and use them as a starting point or inspiration for your own. # # 3. **...and many more!**: The possibilities are vast! Consider experimenting with different solver strategies, model architectures, or even implementing your own physical constraints. # -# For more examples and in-depth guides, be sure to check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more examples and in-depth guides, be sure to check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial17/tutorial.py b/tutorials/tutorial17/tutorial.py index 8e81647..c540402 100644 --- a/tutorials/tutorial17/tutorial.py +++ b/tutorials/tutorial17/tutorial.py @@ -2,83 +2,86 @@ # coding: utf-8 # # Tutorial: Introductory Tutorial: A Beginner’s Guide to PINA -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial17/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial17/tutorial.ipynb) +# #

-# PINA logo +# PINA logo #

-# -# +# +# # Welcome to **PINA**! -# +# # PINA [1] is an open-source Python library designed for **Scientific Machine Learning (SciML)** tasks, particularly involving: -# +# # - **Physics-Informed Neural Networks (PINNs)** # - **Neural Operators (NOs)** -# - **Reduced Order Models (ROMs)** # - **Graph Neural Networks (GNNs)** # - ... -# +# # Built on **PyTorch**, **PyTorch Lightning**, and **PyTorch Geometric**, it provides a **user-friendly, intuitive interface** for formulating and solving differential problems using neural networks. -# +# # This tutorial offers a **step-by-step guide** to using PINA—starting from basic to advanced techniques—enabling users to tackle a broad spectrum of differential problems with minimal code. -# -# -# +# +# +# -# ## The PINA Workflow -# +# ## The PINA Workflow +# #

# PINA Workflow #

-# +# # Solving a differential problem in **PINA** involves four main steps: -# +# # 1. ***Problem & Data*** -# Define the mathematical problem and its physical constraints using PINA’s base classes: -# - `AbstractProblem` +# Define the mathematical problem and its physical constraints using PINA’s base classes: +# - `BaseProblem` # - `SpatialProblem` -# - `InverseProblem` +# - `InverseProblem` # - ... -# -# Then prepare inputs by discretizing the domain or importing numerical data. PINA provides essential tools like the `Conditions` class and the `pina.domain` module to facilitate domain sampling and ensure that the input data aligns with the problem's requirements. -# -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** -# -# 2. ***Model Design*** +# +# Then prepare inputs by discretizing the domain or importing numerical data. PINA provides essential tools like the `Condition` class and the `pina.domain` module to facilitate domain sampling and ensure that the input data aligns with the problem's requirements. +# +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** +# +# 2. ***Model Design*** # Build neural network models as **PyTorch modules**. For graph-structured data, use **PyTorch Geometric** to build Graph Neural Networks. You can also import models from `pina.model` module! -# -# 3. ***Solver Selection*** +# +# 3. ***Solver Selection*** # Choose and configure a solver to optimize your model. Options include: -# - **Supervised solvers**: `SupervisedSolver`, `ReducedOrderModelSolver` -# - **Physics-informed solvers**: `PINN` and (many) variants -# - **Generative solvers**: `GAROM` +# - **Supervised solvers**: `SupervisedSingleModelSolver`, `SupervisedEnsembleSolver` +# - **Physics-informed solvers**: `PhysicsInformedSingleModelSolver` and many variants +# # Solvers can be used out-of-the-box, extended, or fully customized. -# -# 4. ***Training*** +# +# 4. ***Training*** # Train your model using the `Trainer` class (built on **PyTorch Lightning**), which enables scalable and efficient training with advanced features. -# -# +# +# # By following these steps, PINA simplifies applying deep learning to scientific computing and differential problems. -# -# +# +# # ## A Simple Regression Problem in PINA # We'll start with a simple regression problem [2] of approximating the following function with a Neural Net model $\mathcal{M}_{\theta}$: -# $$y = x^3 + \epsilon, \quad \epsilon \sim \mathcal{N}(0, 9)$$ -# using only 20 samples: -# +# +# $$ +# y = x^3 + \epsilon, \quad \epsilon \sim \mathcal{N}(0, 9) +# $$ +# +# using only 20 samples: +# # $$x_i \sim \mathcal{U}[-3, 3], \; \forall i \in \{1, \dots, 20\}$$ -# +# # Using PINA, we will: -# +# # - Generate a synthetic dataset. # - Implement a **Bayesian regressor**. # - Use **Monte Carlo (MC) Dropout** for **Bayesian inference** and **uncertainty estimation**. -# +# # This example highlights how PINA can be used for classic regression tasks with probabilistic modeling capabilities. Let's first import useful modules! -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -98,21 +101,22 @@ warnings.filterwarnings("ignore") from pina import Condition, LabelTensor -from pina.problem import AbstractProblem +from pina.problem import BaseProblem from pina.domain import EllipsoidDomain, Difference, CartesianDomain, Union + # #### ***Problem & Data*** -# -# We'll start by defining a `BayesianProblem` inheriting from `AbstractProblem` to handle input/output data. This is suitable when data is available. For other cases like PDEs without data, use: -# +# +# We'll start by defining a `BayesianProblem` inheriting from `BaseProblem` to handle input/output data. This is suitable when data is available. For other cases like PDEs without data, use: +# # - `SpatialProblem` – for spatial variables # - `TimeDependentProblem` – for temporal variables # - `ParametricProblem` – for parametric inputs # - `InverseProblem` – for parameter estimation from observations -# +# # but we will see this more in depth in a while! -# In[2]: +# In[ ]: # (a) Data generation and plot @@ -122,7 +126,7 @@ # (b) PINA Problem formulation -class BayesianProblem(AbstractProblem): +class BayesianProblem(BaseProblem): output_variables = ["y"] input_variables = ["x"] @@ -139,17 +143,17 @@ class BayesianProblem(AbstractProblem): # We highlight two very important features of PINA -# -# 1. **`LabelTensor` Structure** -# - Alongside the standard `torch.Tensor`, PINA introduces the `LabelTensor` structure, which allows **string-based indexing**. -# - Ideal for managing and stacking tensors with different labels (e.g., `"x"`, `"t"`, `"u"`) for improved clarity and organization. +# +# 1. **`LabelTensor` Structure** +# - Alongside the standard `torch.Tensor`, PINA introduces the `LabelTensor` structure, which allows **string-based indexing**. +# - Ideal for managing and stacking tensors with different labels (e.g., `"x"`, `"t"`, `"u"`) for improved clarity and organization. # - You can still use standard PyTorch tensors if needed. -# -# 2. **`Condition` Object** -# - The `Condition` object enforces the **constraints** that the model $\mathcal{M}_{\theta}$ must satisfy, such as boundary or initial conditions. +# +# 2. **`Condition` Object** +# - The `Condition` object enforces the **constraints** that the model $\mathcal{M}_{\theta}$ must satisfy, such as boundary or initial conditions. # - It ensures that the model adheres to the specific requirements of the problem, making constraint handling more intuitive and streamlined. -# In[3]: +# In[ ]: # EXTRA - on the use of LabelTensor @@ -167,15 +171,15 @@ class BayesianProblem(AbstractProblem): # #### ***Model Design*** -# -# We will now solve the problem using a **simple PyTorch Neural Network** with **Dropout**, which we will implement from scratch following [2]. -# It's important to note that PINA provides a wide range of **state-of-the-art (SOTA)** architectures in the `pina.model` module, which you can explore further [here](https://mathlab.github.io/PINA/_rst/_code.html#models). -# +# +# We will now solve the problem using a **simple PyTorch Neural Network** with **Dropout**, which we will implement from scratch following [2]. +# It's important to note that PINA provides a wide range of **state-of-the-art (SOTA)** architectures in the `pina.model` module, which you can explore further [here](https://PINA-org.github.io/PINA/_rst/_code.html#models). +# # #### ***Solver Selection*** -# -# For this task, we will use a straightforward **supervised learning** approach by importing the `SupervisedSolver` from `pina.solvers`. The solver is responsible for defining the training strategy. -# -# The `SupervisedSolver` is designed to handle typical regression tasks effectively by minimizing the following loss function: +# +# For this task, we will use a straightforward **supervised learning** approach by importing the `SupervisedSingleModelSolver` from `pina.solver`. The solver is responsible for defining the training strategy. +# +# The `SupervisedSingleModelSolver` is designed to handle typical regression tasks effectively by minimizing the following loss function: # $$ # \mathcal{L}_{\rm{problem}} = \frac{1}{N}\sum_{i=1}^N # \mathcal{L}(y_i - \mathcal{M}_{\theta}(x_i)) @@ -184,21 +188,21 @@ class BayesianProblem(AbstractProblem): # $$ # \mathcal{L}(v) = \| v \|^2_2. # $$ -# +# # #### **Training** -# +# # Next, we will use the `Trainer` class to train the model. The `Trainer` class, based on **PyTorch Lightning**, offers many features that help: # - **Improve model accuracy** # - **Reduce training time and memory usage** -# - **Facilitate logging and visualization** -# +# - **Facilitate logging and visualization** +# # The great work done by the PyTorch Lightning team ensures a streamlined training process. # In[ ]: -from pina.solver import SupervisedSolver -from pina.trainer import Trainer +from pina.solver import SupervisedSingleModelSolver +from pina import Trainer # define problem & data (step 1) @@ -222,7 +226,7 @@ def forward(self, x): model = BayesianModel() # solver selection (step 3) -solver = SupervisedSolver(problem, model) +solver = SupervisedSingleModelSolver(problem, model) # training (step 4) trainer = Trainer(solver=solver, max_epochs=2000, accelerator="cpu") @@ -230,18 +234,18 @@ def forward(self, x): # #### ***Model Training Complete! Now Visualize the Solutions*** -# +# # The model has been trained! Since we used **Dropout** during training, the model is probabilistic (Bayesian) [3]. This means that each time we evaluate the forward pass on the input points $x_i$, the results will differ due to the stochastic nature of Dropout. -# +# # To visualize the model's predictions and uncertainty, we will: -# +# # 1. **Evaluate the Forward Pass**: Perform multiple forward passes to get different predictions for each input $x_i$. # 2. **Compute the Mean**: Calculate the average prediction $\mu_\theta$ across all forward passes. # 3. **Compute the Standard Deviation**: Calculate the variability of the predictions $\sigma_\theta$, which indicates the model's uncertainty. -# +# # This allows us to understand not only the predicted values but also the confidence in those predictions. -# In[5]: +# In[ ]: x_test = LabelTensor(torch.linspace(-4, 4, 100).reshape(-1, 1), "x") @@ -266,34 +270,34 @@ def forward(self, x): # ## PINA for Physics-Informed Machine Learning -# +# # In the previous section, we used PINA for **supervised learning**. However, one of its main strengths lies in **Physics-Informed Machine Learning (PIML)**, specifically through **Physics-Informed Neural Networks (PINNs)**. -# +# # ### What Are PINNs? -# +# # PINNs are deep learning models that integrate the laws of physics directly into the training process. By incorporating **differential equations** and **boundary conditions** into the loss function, PINNs allow the modeling of complex physical systems while ensuring the predictions remain consistent with scientific laws. -# +# # ### Solving a 2D Poisson Problem -# -# In this section, we will solve a **2D Poisson problem** with **Dirichlet boundary conditions** on an **hourglass-shaped domain** using a simple PINN [4]. You can explore other PINN variants, e.g. [5] or [6] in PINA by visiting the [PINA solvers documentation](https://mathlab.github.io/PINA/_rst/_code.html#solvers). We aim to solve the following 2D Poisson problem: -# +# +# In this section, we will solve a **2D Poisson problem** with **Dirichlet boundary conditions** on an **hourglass-shaped domain** using a simple `PhysicsInformedSingleModelSolver` [4]. You can explore other PINN variants, e.g. [5] or [6] in PINA by visiting the [PINA solvers documentation](https://PINA-org.github.io/PINA/_rst/_code.html#solvers). We aim to solve the following 2D Poisson problem: +# # $$ # \begin{cases} # \Delta u(x, y) = \sin{(\pi x)} \sin{(\pi y)} & \text{in } D, \\ -# u(x, y) = 0 & \text{on } \partial D +# u(x, y) = 0 & \text{on } \partial D # \end{cases} # $$ -# +# # where $D$ is an **hourglass-shaped domain** defined as the difference between a **Cartesian domain** and two intersecting **ellipsoids**, and $\partial D$ is the boundary of the domain. -# +# # ### Building Complex Domains -# +# # PINA allows you to build complex geometries easily. It provides many built-in domain shapes and Boolean operators for combining them. For this problem, we will define the hourglass-shaped domain using the existing `CartesianDomain` and `EllipsoidDomain` classes, with Boolean operators like `Difference` and `Union`. -# -# > **👉 If you are interested in exploring the `domain` module in more detail, check out [this tutorial](https://mathlab.github.io/PINA/tutorial6/tutorial.html).** -# +# +# > **👉 If you are interested in exploring the `domain` module in more detail, check out [this tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial6/tutorial.html).** +# -# In[6]: +# In[ ]: # (a) Building the interior of the hourglass-shaped domain @@ -326,10 +330,10 @@ def forward(self, x): # #### Plotting the domain -# +# # Nice! Now that we have built the domain, let's try to plot it -# In[7]: +# In[ ]: plt.figure(figsize=(8, 4)) @@ -353,19 +357,20 @@ def forward(self, x): # #### Writing the Poisson Problem Class -# -# Very good! Now we will implement the problem class for the 2D Poisson problem. Unlike the previous examples, where we inherited from `AbstractProblem`, for this problem, we will inherit from the `SpatialProblem` class. -# +# +# Very good! Now we will implement the problem class for the 2D Poisson problem. Unlike the previous examples, where we inherited from `BaseProblem`, for this problem, we will inherit from the `SpatialProblem` class. +# # The reason for this is that the Poisson problem involves **spatial variables** as input, so we use `SpatialProblem` to handle such cases. -# +# # This will allow us to define the problem with spatial dependencies and set up the neural network model accordingly. -# In[8]: +# In[ ]: from pina.problem import SpatialProblem from pina.operator import laplacian -from pina.equation import FixedValue, Equation +from pina.equation import Equation +from pina.equation.zoo import FixedValue def poisson_equation(input_, output_): @@ -395,15 +400,15 @@ class Poisson(SpatialProblem): # As you can see, writing the problem class for a differential equation in PINA is straightforward! The main differences are: -# -# - We inherit from **`SpatialProblem`** instead of `AbstractProblem` to account for spatial variables. +# +# - We inherit from **`SpatialProblem`** instead of `BaseProblem` to account for spatial variables. # - We use **`domain`** and **`equation`** inside the `Condition` to define the problem. -# -# The `Equation` class can be very useful for creating modular problem classes. If you're interested, check out [this tutorial](https://mathlab.github.io/PINA/tutorial12/tutorial.html) for more details. There's also a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) for building custom problems! -# +# +# The `Equation` class can be very useful for creating modular problem classes. If you're interested, check out [this tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial12/tutorial.html) for more details. There's also a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) for building custom problems! +# # Once the problem class is set, we need to **sample the domain** to obtain the data. PINA will automatically handle this, and if you forget to sample, an error will be raised before training begins 😉. -# In[9]: +# In[ ]: print("Points are not automatically sampled, you can see this by:") @@ -415,16 +420,16 @@ class Poisson(SpatialProblem): # ### Building the Model -# +# # After setting the problem and sampling the domain, the next step is to **build the model** $\mathcal{M}_{\theta}$. -# -# For this, we will use the custom PINA models available [here](https://mathlab.github.io/PINA/_rst/_code.html#models). Specifically, we will use a **feed-forward neural network** by importing the `FeedForward` class. -# -# This neural network takes the **coordinates** (in this case `['x', 'y']`) as input and outputs the unknown field of the Poisson problem. -# +# +# For this, we will use the custom PINA models available [here](https://PINA-org.github.io/PINA/_rst/_code.html#models). Specifically, we will use a **feed-forward neural network** by importing the `FeedForward` class. +# +# This neural network takes the **coordinates** (in this case `['x', 'y']`) as input and outputs the unknown field of the Poisson problem. +# # In this tutorial, the neural network is composed of 2 hidden layers, each with 120 neurons and tanh activation. -# In[10]: +# In[ ]: from pina.model import FeedForward @@ -438,40 +443,40 @@ class Poisson(SpatialProblem): # ### Solver Selection -# +# # The thir part of the PINA pipeline involves using a **Solver**. -# -# In this tutorial, we will use the **classical PINN** solver. However, many other variants are also available and we invite to try them! -# +# +# In this tutorial, we will use the classical `PhysicsInformedSingleModelSolver` solver. However, many other variants are also available and we invite to try them! +# # #### Loss Function in PINA -# -# The loss function in the **classical PINN** is defined as follows: -# +# +# The loss function in the **classical physics-informed solver** is defined as follows: +# # $$\theta_{\rm{best}}=\min_{\theta}\mathcal{L}_{\rm{problem}}(\theta), \quad \mathcal{L}_{\rm{problem}}(\theta)= \frac{1}{N_{D}}\sum_{i=1}^N # \mathcal{L}(\Delta\mathcal{M}_{\theta}(\mathbf{x}_i, \mathbf{y}_i) - \sin(\pi x_i)\sin(\pi y_i)) + # \frac{1}{N}\sum_{i=1}^N # \mathcal{L}(\mathcal{M}_{\theta}(\mathbf{x}_i, \mathbf{y}_i))$$ -# +# # This loss consists of: # 1. The **differential equation residual**: Ensures the model satisfies the Poisson equation. # 2. The **boundary condition**: Ensures the model satisfies the Dirichlet boundary condition. -# +# # ### Training -# -# For the last part of the pipeline we need a `Trainer`. We will train the model for **1000 epochs** using the default optimizer parameters. These parameters can be adjusted as needed. For more details, check the solvers documentation [here](https://mathlab.github.io/PINA/_rst/_code.html#solvers). -# +# +# For the last part of the pipeline we need a `Trainer`. We will train the model for **1500 epochs** using the default optimizer parameters. These parameters can be adjusted as needed. For more details, check the solvers documentation [here](https://PINA-org.github.io/PINA/_rst/_code.html#solvers). +# # To track metrics during training, we use the **`MetricTracker`** class. -# -# > **👉 Want to know more about `Trainer` and how to boost PINA performance, check out [this tutorial](https://mathlab.github.io/PINA/tutorial11/tutorial.html).** +# +# > **👉 Want to know more about `Trainer` and how to boost PINA performance? Check out [this tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial11/tutorial.html).** # In[ ]: -from pina.solver import PINN +from pina.solver import PhysicsInformedSingleModelSolver from pina.callback import MetricTracker # define the solver -solver = PINN(poisson_problem, model) +solver = PhysicsInformedSingleModelSolver(poisson_problem, model) # define trainer trainer = Trainer( @@ -488,7 +493,7 @@ class Poisson(SpatialProblem): # Done! We can plot the solution and its residual -# In[12]: +# In[ ]: # sample points in the domain. remember to set requires_grad! @@ -497,7 +502,8 @@ class Poisson(SpatialProblem): solution = solver(pts) # compute the residual in the interior equation = poisson_problem.conditions["interior"].equation -residual = solver.compute_residual(pts, equation) +residual = equation.residual(pts, solution) + # simple plot with torch.no_grad(): plt.subplot(1, 2, 1) @@ -520,28 +526,28 @@ class Poisson(SpatialProblem): # ## What's Next? -# +# # Congratulations on completing the introductory tutorial of **PINA**! Now that you have a solid foundation, here are a few directions you can explore: -# -# 1. **Explore Advanced Solvers**: Dive into more advanced solvers like **SAPINN** or **RBAPINN** and experiment with different variations of Physics-Informed Neural Networks. +# +# 1. **Explore Advanced Solvers**: Dive into more advanced solvers like **RBAPhysicsInformedSingleModelSolver** and experiment with different variants of Physics-Informed solvers. # 2. **Apply PINA to New Problems**: Try solving other types of differential equations or explore inverse problems and parametric problems using the PINA framework. # 3. **Optimize Model Performance**: Use the `Trainer` class to enhance model performance by exploring features like dynamic learning rates, early stopping, and model checkpoints. -# +# # 4. **...and many more!** — There are countless directions to further explore, from testing on different problems to refining the model architecture! -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). -# -# +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). +# +# # ### References -# +# # [1] *Coscia, Dario, et al. "Physics-informed neural networks for advanced modeling." Journal of Open Source Software, 2023.* -# +# # [2] *Hernández-Lobato, José Miguel, and Ryan Adams. "Probabilistic backpropagation for scalable learning of bayesian neural networks." International conference on machine learning, 2015.* -# +# # [3] *Gal, Yarin, and Zoubin Ghahramani. "Dropout as a bayesian approximation: Representing model uncertainty in deep learning." International conference on machine learning, 2016.* -# +# # [4] *Raissi, Maziar, Paris Perdikaris, and George E. Karniadakis. "Physics-informed neural networks: A deep learning framework for solving forward and inverse problems involving nonlinear partial differential equations." Journal of Computational Physics, 2019.* -# +# # [5] *McClenny, Levi D., and Ulisses M. Braga-Neto. "Self-adaptive physics-informed neural networks." Journal of Computational Physics, 2023.* -# +# # [6] *Anagnostopoulos, Sokratis J., et al. "Residual-based attention in physics-informed neural networks." Computer Methods in Applied Mechanics and Engineering, 2024.* diff --git a/tutorials/tutorial18/tutorial.py b/tutorials/tutorial18/tutorial.py index d4dd7cd..1009ec7 100644 --- a/tutorials/tutorial18/tutorial.py +++ b/tutorials/tutorial18/tutorial.py @@ -2,138 +2,95 @@ # coding: utf-8 # # Tutorial: Introduction to Solver classes -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial18/tutorial.ipynb) -# -# In this tutorial, we will explore the Solver classes in PINA, that are the core components for optimizing models. Solvers are designed to manage and execute the optimization process, providing the flexibility to work with various types of neural networks and loss functions. We will show how to use this class to select and implement different solvers, such as Supervised Learning, Physics-Informed Neural Networks (PINNs), and Generative Learning solvers. By the end of this tutorial, you'll be equipped to easily choose and customize solvers for your own tasks, streamlining the model training process. -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial18/tutorial.ipynb) +# +# In this tutorial, we will explore the Solver classes in PINA, that are the core components for optimizing models. Solvers are designed to manage and execute the optimization process, providing the flexibility to work with various types of neural networks and loss functions. We will show how to use this class to select and implement different solvers, such as Supervised or Physics-Informed solvers. By the end of this tutorial, you'll be equipped to easily choose and customize solvers for your own tasks, streamlining the model training process. +# # ## Introduction to Solvers -# -# [`Solvers`](https://mathlab.github.io/PINA/_rst/_code.html#solvers) are versatile objects in PINA designed to manage the training and optimization of machine learning models. They handle key components of the learning process, including: -# -# - Loss function minimization +# +# [`Solvers`](https://PINA-org.github.io/PINA/_rst/_code.html#solvers) are versatile objects in PINA designed to manage the training and optimization of machine learning models. They handle key components of the learning process, including: +# +# - Loss function minimization # - Model optimization (optimizer, schedulers) # - Validation and testing workflows -# +# # PINA solvers are built on top of the [PyTorch Lightning `LightningModule`](https://lightning.ai/docs/pytorch/stable/common/lightning_module.html), which provides a structured and scalable training framework. This allows solvers to leverage advanced features such as distributed training, early stopping, and logging — all with minimal setup. -# -# ## Solvers Hierarchy: Single and MultiSolver -# -# PINA provides two main abstract interfaces for solvers, depending on whether the training involves a single model or multiple models. These interfaces define the base functionality that all specific solver implementations inherit from. -# -# ### 1. [`SingleSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/solver_interface.html) -# -# This is the abstract base class for solvers that train **a single model**, such as in standard supervised learning or physics-informed training. All specific solvers (e.g., `SupervisedSolver`, `PINN`) inherit from this interface. -# +# +# ## Solvers Hierarchy: Single-Model, Multi-Model, and Ensemble +# +# PINA provides three main base solver classes, designed respectively for training a single model, multiple distinct models, or an ensemble of models. These interfaces define the core functionality inherited by all specific solver implementations. +# +# ### 1. [`SingleModelSolver`](https://PINA-org.github.io/PINA/_rst/solver/single_model_solver.html) +# +# This is the base class for solvers that train **a single model**, such as in standard supervised learning or physics-informed training. +# # **Arguments:** # - `problem` – The problem to be solved. # - `model` – The neural network model. # - `optimizer` – Defaults to `torch.optim.Adam` if not provided. # - `scheduler` – Defaults to `torch.optim.lr_scheduler.ConstantLR`. -# - `weighting` – Optional loss weighting schema., see [here](https://mathlab.github.io/PINA/_rst/_code.html#losses-and-weightings). We weight already for you! +# - `weighting` – Optional loss weighting schema., see [here](https://PINA-org.github.io/PINA/_rst/_code.html#losses-and-weightings). +# - `loss` - Defaults to `torch.nn.MSELoss`. # - `use_lt` – Whether to use LabelTensors as input. -# +# # --- -# -# ### 2. [`MultiSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/multi_solver_interface.html) -# -# This is the abstract base class for solvers involving **multiple models**, such as in GAN architectures or ensemble training strategies. All multi-model solvers (e.g., `DeepEnsemblePINN`, `GAROM`) inherit from this interface. -# +# +# ### 2. [`MultiModelSolver`](https://PINA-org.github.io/PINA/_rst/solver/multi_model_solver.html) +# +# This is the base class for solvers involving **multiple models** with distinct behaviours, such as in `SelfAdaptivePhysicsInformedSolver`. +# # **Arguments:** # - `problem` – The problem to be solved. -# - `models` – The model or models used for training. +# - `models` – The models used for training. # - `optimizers` – Defaults to `torch.optim.Adam`. # - `schedulers` – Defaults to `torch.optim.lr_scheduler.ConstantLR`. -# - `weightings` – Optional loss weighting schema, see [here](https://mathlab.github.io/PINA/_rst/_code.html#losses-and-weightings). We weight already for you! +# - `weighting` – Optional loss weighting schema, see [here](https://PINA-org.github.io/PINA/_rst/_code.html#losses-and-weightings). +# - `loss` - Defaults to `torch.nn.MSELoss`. # - `use_lt` – Whether to use LabelTensors as input. -# +# # --- -# -# These base classes define the structure and behavior of solvers in PINA, allowing you to create customized training strategies while leveraging PyTorch Lightning's features under the hood. -# -# These classes are used to define the backbone, i.e. setting the problem, the model(s), the optimizer(s) and scheduler(s), but miss a key component the `optimization_cycle` method. -# -# -# ## Optimization Cycle -# The `optimization_cycle` method is the core function responsible for computing losses for **all conditions** in a given training batch. Each condition (e.g. initial condition, boundary condition, PDE residual) contributes its own loss, which is tracked and returned in a dictionary. This method should return a dictionary mapping **condition names** to their respective **scalar loss values**. -# -# For supervised learning tasks, where each condition consists of an input-target pair, for example, the `optimization_cycle` may look like this: -# -# ```python -# def optimization_cycle(self, batch): -# """ -# The optimization cycle for Supervised solvers. -# Computes loss for each condition in the batch. -# """ -# condition_loss = {} -# for condition_name, data in batch: -# condition_loss[condition_name] = self.loss_data( -# input=data["input"], target=data["target"] -# ) -# return condition_loss -# ``` -# In PINA, a **batch** is structured as a list of tuples, where each tuple corresponds to a specific training condition. Each tuple contains: -# -# - The **name of the condition** -# - A **dictionary of data** associated with that condition -# -# for example: -# -# ```python -# batch = [ -# ("condition1", {"input": ..., "target": ...}), -# ("condition2", {"input": ..., "equation": ...}), -# ("condition3", {"input": ..., "target": ...}), -# ] -# ``` -# -# Fortunately, you don't need to implement the `optimization_cycle` yourself in most cases — PINA already provides default implementations tailored to common solver types. These implementations are available through the solver interfaces and cover various training strategies. -# -# 1. [`PINNInterface`](https://mathlab.github.io/PINA/_rst/solver/physics_informed_solver/pinn_interface.html) -# Implements the optimization cycle for **physics-based solvers** (e.g., PDE residual minimization) as well as other useful methods to compute PDE residuals. -# ➤ [View method](https://mathlab.github.io/PINA/_rst/solver/physics_informed_solver/pinn_interface.html#pina.solver.physics_informed_solver.pinn_interface.PINNInterface.optimization_cycle) -# -# 2. [`SupervisedSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/supervised_solver/supervised_solver_interface.html) -# Defines the optimization cycle for **supervised learning tasks**, including traditional regression and classification. -# ➤ [View method](https://mathlab.github.io/PINA/_rst/solver/supervised_solver/supervised_solver_interface.html#pina.solver.supervised_solver.supervised_solver_interface.SupervisedSolverInterface.optimization_cycle) -# -# 3. [`DeepEnsembleSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/ensemble_solver/ensemble_solver_interface.html) -# Provides the optimization logic for **deep ensemble methods**, commonly used for uncertainty quantification or robustness. -# ➤ [View method](https://mathlab.github.io/PINA/_rst/solver/ensemble_solver/ensemble_solver_interface.html#pina.solver.ensemble_solver.ensemble_solver_interface.DeepEnsembleSolverInterface.optimization_cycle) -# -# These ready-to-use implementations ensure that your solvers are properly structured and compatible with PINA’s training workflow. You can also inherit and override them to fit more specialized needs. They only require, the following arguments: +# +# ### 3. [`EnsembleSolver`](https://PINA-org.github.io/PINA/_rst/solver/ensemble_solver.html) +# +# This is the base class for solvers that train **an ensemble of models** with the same behaviour, such as in `SupervisedEnsembleSolver`. +# # **Arguments:** # - `problem` – The problem to be solved. -# - `loss` - The loss to be minimized -# - `weightings` – Optional loss weighting schema. +# - `models` – The models used for training. +# - `optimizers` – Defaults to `torch.optim.Adam`. +# - `schedulers` – Defaults to `torch.optim.lr_scheduler.ConstantLR`. +# - `weighting` – Optional loss weighting schema, see [here](https://PINA-org.github.io/PINA/_rst/_code.html#losses-and-weightings). +# - `loss` - Defaults to `torch.nn.MSELoss`. # - `use_lt` – Whether to use LabelTensors as input. -# +# +# --- +# +# These base classes define the structure and behavior of solvers in PINA, enabling customized training strategies while leveraging PyTorch Lightning under the hood. +# # ## Structure a Solver with Multiple Inheritance: -# -# Thanks to PINA’s modular design, creating a custom solver is straightforward using **multiple inheritance**. You can combine different interfaces to define both the **optimization logic** and the **model structure**. -# -# - **`PINN` Solver** -# - Inherits from: -# - [`PINNInterface`](https://mathlab.github.io/PINA/_rst/solver/physics_informed_solver/pinn_interface.html) → physics-based optimization loop -# - [`SingleSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/solver_interface.html) → training a single model -# -# - **`SupervisedSolver`** +# +# Thanks to PINA’s modular design, custom solvers can be easily built through **multiple inheritance**. By combining a chosen base solver class with one or more [`Mixins`](https://PINA-org.github.io/PINA/_rst/_code.html#mixins), users can define flexible and specialized optimization logic. Each mixin encapsulates an isolated component of the solver behavior, making it possible to assemble reusable building blocks into a fully functional custom solver. Examples include: +# +# - **`PhysicsInformedSingleModelSolver`** # - Inherits from: -# - [`SupervisedSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/supervised_solver/supervised_solver_interface.html) → data-driven optimization loop -# - [`SingleSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/solver_interface.html) → training a single model -# -# - **`GAROM`** (a variant of GAN) -# - Inherits from: -# - [`SupervisedSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/supervised_solver/supervised_solver_interface.html) → data-driven optimization loop -# - [`MultiSolverInterface`](https://mathlab.github.io/PINA/_rst/solver/multi_solver_interface.html) → training multiple models (e.g., generator and discriminator) -# +# +# - [`PhysicsInformedMixin`](https://PINA-org.github.io/PINA/_rst/solver/mixin/physics_informed_mixin.html) → enables gradient computation for validation and test phases +# - [`SingleModelSolver`](https://PINA-org.github.io/PINA/_rst/solver/single_model_solver.html) → manages training of a single model +# +# - **`AutoregressiveEnsembleSolver`** +# - Inherits from: +# +# - [`AutoregressiveMixin`](https://PINA-org.github.io/PINA/_rst/solver/mixin/autoregressive_mixin.html) → implements autoregressive logic +# - [`EnsembleSolver`](https://PINA-org.github.io/PINA/_rst/solver/ensemble_solver.html) → manages training of an ensemble model +# # This structure promotes **code reuse** and **extensibility**, allowing you to quickly prototype new solver strategies by reusing core training and optimization logic. -# +# # ## Let's try to build some solvers! -# -# We will now start building a simple supervised solver in PINA. Let's first import useful modules! +# +# We will now start building a supervised single-model solver in PINA. Let's first import useful modules! -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -148,52 +105,60 @@ import warnings import torch -import matplotlib.pyplot as plt warnings.filterwarnings("ignore") from pina import Trainer -from pina.solver import SingleSolverInterface, SupervisedSolverInterface +from pina.solver import BaseSolver +from pina.solver.mixin import SingleModelMixin, ConditionAggregatorMixin +from pina.condition import InputTargetCondition from pina.model import FeedForward from pina.problem.zoo import SupervisedProblem -# Since we are using only one model for this task, we will inherit from two base classes: -# -# - `SingleSolverInterface`: This ensures we are working with a single model. -# - `SupervisedSolverInterface`: This allows us to use supervised learning strategies for training the model. -# In[2]: +# We inherit from three classes: +# +# - `SingleModelMixin`: manages the forward pass logic for a single model +# - `ConditionAggregationMixin`: combines the losses from one or more conditions into a single quantity +# - `BaseSolver`: initializes the solver components + +# In[ ]: -class MyFirstSolver(SupervisedSolverInterface, SingleSolverInterface): +class MyFirstSolver(SingleModelMixin, ConditionAggregatorMixin, BaseSolver): + + # Accepted condition types for this solver + accepted_conditions_types = (InputTargetCondition,) + def __init__( self, problem, model, - loss=None, optimizer=None, scheduler=None, weighting=None, + loss=None, use_lt=True, ): - super().__init__( - model=model, - problem=problem, - loss=loss, - optimizer=optimizer, - scheduler=scheduler, - weighting=weighting, - use_lt=use_lt, + # Initialize the base solver + BaseSolver.__init__(self, problem=problem, use_lt=use_lt) + + # Initialize the components of the solver + self._init_solver_components( + models=model, + optimizers=optimizer, + schedulers=scheduler, ) + # Initialize the weighting scheme for the conditions and the loss + self._init_weighting_and_loss(weighting=weighting, loss=loss) -# By default, Python follows a specific method resolution order (MRO) when a class inherits from multiple parent classes. This means that the initialization (`__init__`) method is called based on the order of inheritance. -# -# Since we inherit from `SupervisedSolverInterface` first, Python will call the `__init__` method from `SupervisedSolverInterface` (initialize `problem`, `loss`, `weighting` and `use_lt`) before calling the `__init__` method from `SingleSolverInterface` (initialize `model`, `optimizer`, `scheduler`). This allows us to customize the initialization process for our custom solver. -# -# We will learn a very simple problem, try to learn $y=\sin(x)$. -# In[3]: +# When a class inherits from multiple parent classes, Python determines the order in which methods are resolved using the method resolution order (MRO). In this case, because `SingleModelMixin` and `ConditionAggregationMixin` appear before `BaseSolver` in the inheritance list, their initialization logic is handled first, before delegating to the `__init__` method of `BaseSolver`. This allows the custom solver to extend or modify the initialization process while still relying on the base functionality provided by `BaseSolver`. + +# Let's use `MyFirstSolver` to solve a very simple problem: $y=\sin(x)$. + +# In[ ]: # get the data @@ -205,95 +170,42 @@ def __init__( model = FeedForward(1, 1) -# If we now try to initialize the solver `MyFirstSolver` we will get the following error: -# -# ```python -# --------------------------------------------------------------------------- -# TypeError Traceback (most recent call last) -# Cell In[41], line 1 -# ----> 1 MyFirstSolver(problem, model) -# -# TypeError: Can't instantiate abstract class MyFirstSolver with abstract method loss_data -# ``` -# -# ### Data and Physics Loss -# The error above is because in PINA, all solvers must specify how to compute the loss during training. There are two main types of losses that can be computed, depending on the nature of the problem: -# -# 1. **`loss_data`**: Computes the **data loss** between the model's output and the true solution. This is typically used in **supervised learning** setups, where we have ground truth data to compare the model's predictions. It expects some `input` (tensor, graph, ...) and a `target` (tensor, graph, ...) -# -# 2. **`loss_phys`**: Computes the **physics loss** for **physics-informed solvers** (PINNs). This loss is based on the residuals of the governing equations that model physical systems, enforcing the equations during training. It expects some `samples` (`LabelTensor`) and an `equation` (`Equation`) -# -# Therefore our implementation becomes: - # In[ ]: -class MyFirstSolver(SupervisedSolverInterface, SingleSolverInterface): - def __init__( - self, - problem, - model, - loss=None, - optimizer=None, - scheduler=None, - weighting=None, - use_lt=True, - ): - super().__init__( - model=model, - problem=problem, - loss=loss, - optimizer=optimizer, - scheduler=scheduler, - weighting=weighting, - use_lt=use_lt, - ) - - def loss_data(self, input, target): - # self.loss stores the loss passed in the init - network_output = self.forward(input) - return self.loss(network_output, target) - - # initialize (we use plain tensors!) -solver = MyFirstSolver(problem, model, use_lt=False) +solver = MyFirstSolver(problem=problem, model=model, use_lt=False) # simple training trainer = Trainer( solver, max_epochs=500, train_size=0.8, test_size=0.2, accelerator="cpu" ) trainer.train() -_ = trainer.test() +trainer.test() # ## A Summary on Solvers -# +# # Solvers in PINA play a critical role in training and optimizing machine learning models, especially when working with complex problems like physics-informed neural networks (PINNs) or standard supervised learning. Here’s a quick recap of the key concepts we've covered: -# -# 1. **Solver Interfaces**: -# - **`SingleSolverInterface`**: For solvers using one model (e.g., a standard supervised solver or a single physics-informed model). -# - **`MultiSolverInterface`**: For solvers using multiple models (e.g., Generative Adversarial Networks (GANs)). -# -# 2. **Loss Functions**: -# - **`loss_data`**: Computes the loss for supervised solvers, typically comparing the model's predictions to the true targets. -# - **`loss_phys`**: Computes the physics loss for PINNs, typically using the residuals of a physical equation to enforce consistency with the physics of the system. -# +# +# 1. **Solver base classes**: +# - **`SingleModelSolver`**: For solvers using a single model. +# - **`MultiModelSolver`**: For solvers using multiple models. +# - **`EnsembleSolver`**: For solvers using an ensemble of models. +# # 3. **Custom Solver Implementation**: -# - You can create custom solvers by inheriting from base classes such as `SingleSolverInterface`. The **`optimization_cycle`** method must be implemented to define how to compute the loss for each batch. -# - `SupervisedSolverInterface`, `PINNInterface` already implement the `optimization_cycle` for you! -# -# -# By understanding and implementing solvers in PINA, you can build flexible, scalable models that can be optimized both with traditional supervised learning techniques and more specialized, physics-based methods. +# - Custom solvers can be created by inheriting from one of PINA’s base solver classes and combining it with one or more mixins. +# - Several solver implementations are already readily available in PINA and can be used directly or extended to define more specialized training strategies. # ## What's Next? -# +# # Congratulations on completing the tutorial on solver classes! Now that you have a solid foundation, here are a few directions you can explore: -# -# -# 1. **Physics Solvers**: Try to implement your own physics-based solver. Can you do it? This will involve creating a custom loss function that enforces the physics of a given problem insied `loss_phys`. -# -# 2. **Multi-Model Solvers**: Take it to the next level by exploring multi-model solvers, such as GANs or ensemble-based solvers. You could implement and train models that combine the strengths of multiple neural networks. -# +# +# +# 1. **Physics-Informed Solvers**: Try to implement your own physics-based solver. Can you do it? +# +# 2. **Multi-Model Solvers**: Take it to the next level by exploring multi-model solvers. You could implement and train models that combine the strengths of multiple neural networks. +# # 3. **...and many more!**: There are countless directions to further explore, try to look at our `solver` for example! -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial19/tutorial.py b/tutorials/tutorial19/tutorial.py index 7bb2996..62fcc0a 100644 --- a/tutorials/tutorial19/tutorial.py +++ b/tutorials/tutorial19/tutorial.py @@ -2,13 +2,13 @@ # coding: utf-8 # # Tutorial: Data structure for SciML: `Tensor`, `LabelTensor`, `Data` and `Graph` -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial19/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial19/tutorial.ipynb) +# # In this tutorial, we’ll quickly go through the basics of Data Structures for Scientific Machine Learning, convering: # 1. **PyTorch Tensors** / **PINA LabelTensors** # 2. **PyTorch Geometric Data** / **PINA Graph** -# +# # first let's import the data structures we will use! # In[ ]: @@ -32,13 +32,14 @@ from pina import LabelTensor, Graph + # ## PyTorch Tensors -# +# # A **tensor** is a multi-dimensional matrix used for storing and manipulating data in PyTorch. It's the basic building block for all computations in PyTorch, including deep learning models. -# +# # You can create a tensor in several ways: -# In[2]: +# In[ ]: # Creating a tensor from a list @@ -61,7 +62,7 @@ # ### Basic Tensor Operations # Tensors support a variety of operations, such as element-wise arithmetic, matrix operations, and more: -# In[4]: +# In[ ]: # Addition @@ -77,7 +78,7 @@ # ### Device Management # PyTorch allows you to move tensors to different devices (CPU or GPU). For instance: -# In[6]: +# In[ ]: # Move tensor to GPU @@ -88,16 +89,16 @@ # To know more about PyTorch Tensors, see the dedicated tutorial done by the PyTorch team [here](https://pytorch.org/tutorials/beginner/introyt/tensors_deeper_tutorial.html). # ## Label Tensors -# -# In scientific machine learning, especially when working with **Physics-Informed Neural Networks (PINNs)**, handling tensors effectively is crucial. Often, we deal with many indices that represent physical quantities such as spatial and temporal coordinates, making it vital to ensure we use the correct indexing. -# -# For instance, in PINNs, if the wrong index is used to represent the coordinates of a physical domain, it could lead to incorrect calculations of derivatives, integrals, or residuals. This can significantly affect the accuracy and correctness of the model. -# +# +# In scientific machine learning, especially when working with **Physics-Informed Solvers**, handling tensors effectively is crucial. Often, we deal with many indices that represent physical quantities such as spatial and temporal coordinates, making it vital to ensure we use the correct indexing. +# +# For instance, if the wrong index is used to represent the coordinates of a physical domain, it could lead to incorrect calculations of derivatives, integrals, or residuals. This can significantly affect the accuracy and correctness of the model. +# # ### What are Label Tensors? -# +# # **Label Tensors** are a specialized type of tensor used to keep track of indices that represent specific labels. Similar to torch tensor we can perform operation, but the slicing is simplified by using indeces: -# In[7]: +# In[ ]: # standard torch tensor @@ -108,14 +109,14 @@ # The label tensor is initialized by passing the tensor, and a set of labels. Specifically, the labels must match the following conditions: -# +# # - At each dimension, the number of labels must match the size of the dimension. # - At each dimension, the labels must be unique. -# +# # For example: -# +# -# In[9]: +# In[ ]: # full labels @@ -128,7 +129,7 @@ # You can access last column labels by `.labels` attribute, or using `.full_labels` to access all labels -# In[10]: +# In[ ]: print(f"{tensor.labels=}") @@ -136,12 +137,12 @@ # ### Label Tensors slicing -# -# One of the powerful features of label tensors is the ability to easily slice and extract specific parts of the tensor based on labels, just like regular PyTorch tensors but with the ease of labels. -# +# +# One of the powerful features of label tensors is the ability to easily slice and extract specific parts of the tensor based on labels, just like regular PyTorch tensors but with the ease of labels. +# # Here’s how slicing works with label tensors. Suppose we have a label tensor that contains both spatial and temporal data, and we want to slice specific parts of this data to focus on certain time intervals or spatial regions. -# In[26]: +# In[ ]: # Create a label tensor containing spatial and temporal coordinates @@ -163,7 +164,7 @@ # You can do more complex slicing by using the extract method. For example: -# In[30]: +# In[ ]: label_tensor = LabelTensor( @@ -180,19 +181,19 @@ # ## PyTorch Geometric Data # PyTorch Geometric (PyG) extends PyTorch to handle graph-structured data. It provides utilities to represent graphs and perform graph-based learning tasks such as node classification, graph classification, and more. -# +# # ### Graph Data Structure # PyTorch Geometric uses a custom `Data` object to store graph data. The `Data` object contains the following attributes: -# +# # - **x**: Node features (tensor of shape `[num_nodes, num_features]`) -# +# # - **edge_index**: Edge indices (tensor of shape `[2, num_edges]`), representing the graph's connectivity -# +# # - **edge_attr**: Edge features (optional, tensor of shape `[num_edges, num_edge_features]`) -# +# # - **y**: Target labels for nodes/graphs (optional) -# In[32]: +# In[ ]: # Node features: [2 nodes, 3 features] @@ -209,7 +210,7 @@ # Once you have your graph in a Data object, you can easily perform graph-based operations using PyTorch Geometric’s built-in functions: -# In[33]: +# In[ ]: # Accessing node features @@ -228,10 +229,10 @@ # ## PINA Graph -# +# # If you've understood Label Tensors and Data in PINA, then you're well on your way to grasping how **PINA Graph** works. Simply put, a **Graph** in PINA is a `Data` object with extra methods for handling label tensors. We highly suggest to use `Graph` instead of `Data` in PINA, expecially when using label tensors. -# In[36]: +# In[ ]: # Node features: [2 nodes, 3 features] @@ -262,7 +263,7 @@ # But we can also use labeltensors.... -# In[40]: +# In[ ]: # Node features: [2 nodes, 3 features] @@ -295,13 +296,13 @@ # ## What's Next? -# +# # Congratulations on completing the tutorials on the **PINA Data Structures**! You now have a solid foundation in using the different data structures within PINA, such as **Tensors**, **Label Tensors**, and **Graphs**. Here are some exciting next steps you can take to continue your learning journey: -# -# 1. **Deep Dive into Label Tensors**: Check the documentation of [`LabelTensor`](https://mathlab.github.io/PINA/_rst/label_tensor.html) to learn more about the available methods. -# -# 2. **Working with Graphs in PINA**: In PINA we implement many graph structures, e.g. `KNNGraph`, `RadiusGraph`, .... see [here](https://mathlab.github.io/PINA/_rst/_code.html#graphs-structures) for further details. -# -# 3. **...and many more!**: Consider exploring `LabelTensor` for PINNs! -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# 1. **Deep Dive into Label Tensors**: Check the documentation of [`LabelTensor`](https://PINA-org.github.io/PINA/_rst/label_tensor.html) to learn more about the available methods. +# +# 2. **Working with Graphs in PINA**: In PINA we implement many graph structures, e.g. `KNNGraph`, `RadiusGraph`, .... see [here](https://PINA-org.github.io/PINA/_rst/_code.html#graphs-structures) for further details. +# +# 3. **...and many more!**: Consider exploring `LabelTensor` for Physics-Informed Solvers! +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial2/tutorial.py b/tutorials/tutorial2/tutorial.py index ade54d3..5920c58 100644 --- a/tutorials/tutorial2/tutorial.py +++ b/tutorials/tutorial2/tutorial.py @@ -2,14 +2,14 @@ # coding: utf-8 # # Tutorial: Enhancing PINNs with Extra Features to solve the Poisson Problem -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial2/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial2/tutorial.ipynb) +# # This tutorial presents how to solve with Physics-Informed Neural Networks (PINNs) a 2D Poisson problem with Dirichlet boundary conditions. We will train with standard PINN's training, and with extrafeatures. For more insights on extrafeature learning please read [*An extended physics informed neural network for preliminary analysis of parametric optimal control problems*](https://www.sciencedirect.com/science/article/abs/pii/S0898122123002018). -# +# # First of all, some useful imports. -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -28,7 +28,7 @@ from pina import LabelTensor, Trainer from pina.model import FeedForward -from pina.solver import PINN +from pina.solver import PhysicsInformedSingleModelSolver from torch.nn import Softplus warnings.filterwarnings("ignore") @@ -44,13 +44,13 @@ # \end{cases} # \end{equation} # where $D$ is a square domain $[0,1]^2$, and $\Gamma_i$, with $i=1,...,4$, are the boundaries of the square. -# +# # The Poisson problem is written in **PINA** code as a class. The equations are written as *conditions* that should be satisfied in the corresponding domains. The *solution* -# is the exact solution which will be compared with the predicted one. If interested in how to write problems see [this tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html). -# +# is the exact solution which will be compared with the predicted one. If interested in how to write problems see [this tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html). +# # We will directly import the problem from `pina.problem.zoo`, which contains a vast list of PINN problems and more. -# In[2]: +# In[ ]: from pina.problem.zoo import Poisson2DSquareProblem as Poisson @@ -72,7 +72,7 @@ # ## Solving the problem with standard PINNs # After the problem, the feed-forward neural network is defined, through the class `FeedForward`. This neural network takes as input the coordinates (in this case $x$ and $y$) and provides the unkwown field of the Poisson problem. The residual of the equations are evaluated at several sampling points and the loss minimized by the neural network is the sum of the residuals. -# +# # In this tutorial, the neural network is composed by two hidden layers of 10 neurons each, and it is trained for 1000 epochs with a learning rate of 0.006 and $l_2$ weight regularization set to $10^{-8}$. These parameters can be modified as desired. We set the `train_size` to 0.8 and `test_size` to 0.2, this mean that the discretised points will be divided in a 80%-20% fashion, where 80% will be used for training and the remaining 20% for testing. # In[ ]: @@ -87,7 +87,7 @@ output_dimensions=len(problem.output_variables), input_dimensions=len(problem.input_variables), ) -pinn = PINN( +pinn = PhysicsInformedSingleModelSolver( problem, model, optimizer=TorchOptimizer(torch.optim.Adam, lr=0.006, weight_decay=1e-8), @@ -108,9 +108,9 @@ # Now we plot the results using `matplotlib`. -# The solution predicted by the neural network is plotted on the left, the exact one is in the center and on the right the error between the exact and the predicted solutions is showed. +# The solution predicted by the neural network is plotted on the left, the exact one is in the center and on the right the error between the exact and the predicted solutions is showed. -# In[4]: +# In[ ]: @torch.no_grad() @@ -137,11 +137,12 @@ def plot_solution(solver): field.tensor.flatten(), ) plt.colorbar(), plt.tight_layout() + plt.show() # Here the solution: -# In[5]: +# In[ ]: plt.figure(figsize=(12, 6)) @@ -153,17 +154,17 @@ def plot_solution(solver): # ## Solving the problem with extra-features PINNs # Now, the same problem is solved in a different way. -# A new neural network is now defined, with an additional input variable, named extra-feature, which coincides with the forcing term in the Laplace equation. +# A new neural network is now defined, with an additional input variable, named extra-feature, which coincides with the forcing term in the Laplace equation. # The set of input variables to the neural network is: -# +# # \begin{equation} # [x, y, k(x, y)], \text{ with } k(x, y)= 2\pi^2\sin{(\pi x)}\sin{(\pi y)}, # \end{equation} -# +# # where $x$ and $y$ are the spatial coordinates and $k(x, y)$ is the added feature which is equal to the forcing term. -# +# # This feature is initialized in the class `SinSin`, which is a simple `torch.nn.Module`. After declaring such feature, we can just adjust the `FeedForward` class by creating a subclass `FeedForwardWithExtraFeatures` with an adjusted forward method and the additional attribute `extra_features`. -# +# # Finally, we perform the same training as before: the problem is `Poisson`, the network is composed by the same number of neurons and optimizer parameters are equal to previous test, the only change is the new extra feature. # In[ ]: @@ -200,7 +201,7 @@ def forward(self, x): extra_features=SinSin(), ) -pinn_feat = PINN( +pinn_feat = PhysicsInformedSingleModelSolver( problem, model_feat, optimizer=TorchOptimizer(torch.optim.Adam, lr=0.006, weight_decay=1e-8), @@ -222,7 +223,7 @@ def forward(self, x): # The predicted and exact solutions and the error between them are represented below. # We can easily note that now our network, having almost the same condition as before, is able to reach additional order of magnitudes in accuracy. -# In[7]: +# In[ ]: plt.figure(figsize=(12, 6)) @@ -232,14 +233,14 @@ def forward(self, x): # ## Solving the problem with learnable extra-features PINNs # We can still do better! -# +# # Another way to exploit the extra features is the addition of learnable parameter inside them. # In this way, the added parameters are learned during the training phase of the neural network. In this case, we use: -# +# # \begin{equation} # k(x, \mathbf{y}) = \beta \sin{(\alpha x)} \sin{(\alpha y)}, # \end{equation} -# +# # where $\alpha$ and $\beta$ are the abovementioned parameters. # Their implementation is quite trivial: by using the class `torch.nn.Parameter` we cam define all the learnable parameters we need, and they are managed by `autograd` module! @@ -273,7 +274,7 @@ def forward(self, x): extra_features=SinSinAB(), ) -pinn_learn = PINN( +pinn_learn = PhysicsInformedSingleModelSolver( problem, model_learn, optimizer=TorchOptimizer(torch.optim.Adam, lr=0.006, weight_decay=1e-8), @@ -305,7 +306,7 @@ def forward(self, x): input_dimensions=len(problem.input_variables) + 1, extra_features=SinSinAB(), ) -pinn_learn = PINN( +pinn_learn = PhysicsInformedSingleModelSolver( problem, model_learn, optimizer=TorchOptimizer(torch.optim.Adam, lr=0.006, weight_decay=1e-8), @@ -329,7 +330,7 @@ def forward(self, x): # We conclude here by showing the test error for the analysed methodologies: the standard PINN, PINN with extra features, and PINN with learnable extra features. -# In[10]: +# In[ ]: # test error base pinn @@ -340,19 +341,19 @@ def forward(self, x): trainer_feat.test() # test error learnable extra features pinn print("PINN with learnable extra features") -_ = trainer_learn.test() +trainer_learn.test() # ## What's Next? -# +# # Congratulations on completing the two-dimensional Poisson tutorial of **PINA**! Now that you've learned the basics, there are multiple directions you can explore: -# +# # 1. **Train the Network for Longer**: Continue training the network for a longer duration or experiment with different layer sizes to assess the final accuracy. -# +# # 2. **Propose New Types of Extrafeatures**: Experiment with new extrafeatures and investigate how they affect the learning process. -# +# # 3. **Leverage Extrafeature Training for Complex Problems**: Apply extrafeature training techniques to more complex problems to improve model performance. -# +# # 4. **... and many more!.**: There are endless possibilities! Continue exploring and experimenting with new ideas. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial20/tutorial.py b/tutorials/tutorial20/tutorial.py index 317e61e..4a4ee1b 100644 --- a/tutorials/tutorial20/tutorial.py +++ b/tutorials/tutorial20/tutorial.py @@ -2,18 +2,18 @@ # coding: utf-8 # # Tutorial: Introductory Tutorial: Supervised Learning with PINA -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial20/tutorial.ipynb) -# -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial20/tutorial.ipynb) +# +# # > ##### ⚠️ ***Before starting:*** -# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://mathlab.github.io/PINA/_tutorial.html#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. -# -# In this tutorial, we will demonstrate a typical use case of **PINA** for Supervised Learning training. We will cover the basics of training a Supervised Solver with PINA, if you want to go further into PINNs look at our dedicated [tutorials](https://mathlab.github.io/PINA/_tutorial.html#supervised-learning) on the topic. -# +# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://PINA-org.github.io/PINA-Tutorials/#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. +# +# In this tutorial, we will demonstrate a typical use case of **PINA** for Supervised Learning training. We will cover the basics of training a Supervised Solver with PINA, if you want to go further into PINNs look at our dedicated [tutorials](https://PINA-org.github.io/PINA-Tutorials/#supervised-learning) on the topic. +# # Let's start by importing the useful modules: -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -36,33 +36,34 @@ from pina import Trainer from pina.model import FeedForward from pina.domain import CartesianDomain -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.adaptive_function import AdaptiveSIREN from pina.problem.zoo import SupervisedProblem + # ## Building a Neural Implicit Field for a Sphere -# +# # In this tutorial, we will construct a **Neural Implicit Field** to learn the **Signed Distance Function (SDF)** of a sphere. The problem is relatively simple: we aim to learn a function $d_\theta$, parameterized by a neural network, that captures the signed distance to the surface of a sphere. -# +# # The function $d_\theta(\mathbf{x})$$ should satisfy the following properties: -# -# - $d_\theta(\mathbf{x}) = 0$ on the surface of the sphere -# - $d_\theta(\mathbf{x}) > 0$ outside the sphere -# - $d_\theta(\mathbf{x}) < 0$ inside the sphere -# +# +# - $d_\theta(\mathbf{x}) = 0$ on the surface of the sphere +# - $d_\theta(\mathbf{x}) > 0$ outside the sphere +# - $d_\theta(\mathbf{x}) < 0$ inside the sphere +# # This setup allows us to implicitly represent the geometry of the sphere through the learned function. -# +# # ### Mathematical Description -# +# # We define the signed distance function (SDF) for a sphere centered at the origin with radius $r$ as: # $d(\mathbf{x}) = \|\mathbf{x}\| - r$, where $\mathbf{x} \in \mathbb{R}^3$ is a point in 3D space. -# +# # Our goal is to approximate this function using a neural network: $d_\theta(\mathbf{x}) \approx d(\mathbf{x})$ with a Neural Network. Let's start by generating the data for the problem by: # 1. Sample random 3D points within a bounding cube (e.g., $[-1.5, 1.5]^3$). # 2. Compute their ground truth signed distances from a sphere of radius $r$ centered at the origin. # 3. Package this into tensors for training. -# In[2]: +# In[ ]: def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): @@ -83,10 +84,10 @@ def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): # ### Visualizing the Data -# +# # To better understand the problem and the nature of the solutions, we can visualize the generated data: -# In[3]: +# In[ ]: # --- Generate Data --- @@ -112,30 +113,27 @@ def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): # ## Creating the Problem -# +# # The problem we will define is a basic `SupervisedProblem`, where the inputs are the coordinates and the outputs are the corresponding Signed Distance Function (SDF) values. -# -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** +# +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** -# In[4]: +# In[ ]: problem = SupervisedProblem(coords, sdf) -# ## Solving the Problem with Supervised Solver -# -# We will use the `SupervisedSolver` to solve the task. A Supervised Solver in PINA aims to find a mapping between an input \( x \) and an output \( y \). +# ## Solving the Problem with Supervised Single-Model Solver +# +# We will use the `SupervisedSingleModelSolver` to solve the task. A supervised solver in PINA aims to find a mapping between an input \( x \) and an output \( y \). # Given a PINA `model` $\mathcal{M}$, the following loss function is minimized during training: -# +# # $$ # \mathcal{L}_{\rm{supervised}} = \frac{1}{N}\sum_{i=1}^N \mathcal{l}(y_i, \mathcal{M}(x_i)), # $$ -# +# # where $l$ is a specific loss function, typically the MSE (Mean Squared Error). -# -# ### Specify the Loss Function -# By default, the loss function applies a forward pass of the `model` on the input and compares it to the target using the `loss` attribute of `SupervisedSolver`. The [`loss_data`](https://mathlab.github.io/PINA/_rst/solver/supervised.html#pina.solver.supervised.SupervisedSolver.loss_data) function computes the loss for supervised solvers, and it can be overridden by the user to match specific needs (e.g., performing pre-process operations on the input, post-process operations on the output, etc.). # In[ ]: @@ -144,7 +142,7 @@ def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): model = FeedForward(input_dimensions=3, output_dimensions=1, func=AdaptiveSIREN) # Define the solver -solver = SupervisedSolver(problem, model, use_lt=False) +solver = SupervisedSingleModelSolver(problem, model, use_lt=False) # Simple training trainer = Trainer( @@ -157,16 +155,16 @@ def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): enable_model_summary=False, ) trainer.train() -_ = trainer.test() +trainer.test() # ## Visualizing the Predictions -# +# # As we can see, we have achieved a very low MSE, even after training for only one epoch. Now, we will visualize the results in the same way as we did previously: -# +# # We will plot the predicted Signed Distance Function (SDF) values alongside the true SDF values to evaluate the model's performance. -# In[6]: +# In[ ]: import torch @@ -221,12 +219,12 @@ def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): # Nice! We can see that the network is correctly learning the signed distance function! Let's now visualize the rendering of the sphere surface learned by the network. -# +# # ### Visualizing the Sphere Surface -# +# # To visualize the surface, we will extract the level set where the SDF equals zero and plot the resulting sphere. This will show how well the network has learned the geometry of the object. -# In[7]: +# In[ ]: # --- Generate new Data --- @@ -261,14 +259,14 @@ def generate_sdf_data(num_points=1000000, radius=1.0, cube_bound=1.5): # ## What's Next? -# -# Congratulations on completing the introductiory tutorial on supervised solver! Now that you have a solid foundation, here are a few directions you can explore: -# -# +# +# Congratulations on completing the introductiory tutorial on supervised solvers! Now that you have a solid foundation, here are a few directions you can explore: +# +# # 1. **Experiment with Training Duration & Network Architecture**: Try different training durations and tweak the network architecture to optimize performance. -# +# # 2. **Explore Other Models in `pina.model`**: Check out other models available in `pina.model` or design your own custom PyTorch module to suit your needs. -# +# # 3. **... and many more!**: The possibilities are vast! Continue experimenting with advanced configurations, solvers, and other features in PINA. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial21/tutorial.py b/tutorials/tutorial21/tutorial.py index 8c419f7..da8a0ce 100644 --- a/tutorials/tutorial21/tutorial.py +++ b/tutorials/tutorial21/tutorial.py @@ -2,15 +2,15 @@ # coding: utf-8 # # Tutorial: Introductory Tutorial: Neural Operator Learning with PINA -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial21/tutorial.ipynb) -# -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial21/tutorial.ipynb) +# +# # > ##### ⚠️ ***Before starting:*** -# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://mathlab.github.io/PINA/_tutorial.html#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. -# -# In this tutorial, we will demonstrate a typical use case of **PINA** for Neural Operator learning. We will cover the basics of training a Neural Operator with PINA, if you want to go further into the topic look at our dedicated [tutorials](https://mathlab.github.io/PINA/_tutorial.html#neural-operator-learning) on the topic. -# +# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://PINA-org.github.io/PINA-Tutorials/#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. +# +# In this tutorial, we will demonstrate a typical use case of **PINA** for Neural Operator learning. We will cover the basics of training a Neural Operator with PINA, if you want to go further into the topic look at our dedicated [tutorials](https://PINA-org.github.io/PINA-Tutorials/#neural-operator-learning) on the topic. +# # Let's start by importing the useful modules: # In[ ]: @@ -33,64 +33,65 @@ warnings.filterwarnings("ignore") from pina import Trainer -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.model import KernelNeuralOperator from pina.model.block import FourierBlock1D from pina.problem.zoo import SupervisedProblem + # ## Learning Differential Operators via Neural Operator -# +# # In this tutorial, we explore how **Neural Operators** can be used to learn and approximate **differential operators**, which are fundamental in modeling physical and engineering systems governed by differential equations. -# +# # ### What Are Neural Operators? -# +# # **Neural Operators (NOs)** are a class of machine learning models designed to learn mappings *between function spaces*, unlike traditional neural networks which learn mappings between finite-dimensional vectors. In the context of differential equations, this means a Neural Operator can learn the **solution operator**: # $$ # \mathcal{G}(a) = u, # $$ # where $a$ is an input function (e.g., a PDE coefficient) and $u$ is the solution function. -# +# # ### Why Are Neural Operators Useful? -# +# # - **Mesh-free learning**: Neural Operators work directly with functions, allowing them to generalize across different spatial resolutions or grids. # - **Fast inference**: Once trained, they can predict the solution of a PDE for new input data almost instantaneously. # - **Physics-aware extensions**: Some variants can incorporate physical laws and constraints into the training process, improving accuracy and generalization. -# +# # ## Learning the 1D Advection Equation with a Neural Operator -# +# # To make things concrete, we'll a Neural Operator to learn the 1D advection equation. We generate synthetic data based on the analytical solution: -# +# # $$ # \frac{\partial u}{\partial t} + c \frac{\partial u}{\partial x} = 0 # $$ -# +# # For a given initial condition $u(x, 0)$, the exact solution at time $t$ is: -# +# # $$ # u(x, t) = u(x - ct) # $$ -# +# # We use this property to generate training data without solving the PDE numerically. -# +# # ### Problem Setup -# +# # 1. **Define the spatial domain**: We work on a 1D grid $x \in [0, 1]$ with periodic boundary conditions. -# +# # 2. **Generate initial conditions**: Each initial condition $u(x, 0)$ is created as a sum of sine waves with random amplitudes and phases: # $$ # u(x, 0) = \sum_{k=1}^K A_k \sin(2\pi k x + \phi_k) # $$ # where $A_k \in [0, 0.5]$ and $\phi_k \in [0, 2\pi]$ are sampled randomly for each sample. -# -# 3. **Compute the solution at time $t$**: +# +# 3. **Compute the solution at time $t$**: # Using the analytical solution, we shift each initial condition by $t=0.5$ ($c=1$), applying periodic wrap-around: # $$ # u(x, t=0.5) = u(x - 0.5) # $$ -# +# # 4. **Create input-output pairs**: The input to the model is the function $u(x, 0)$, and the target output is $u(x, 0.5)$. These pairs can be used to train a Neural Operator to learn the underlying differential operator. -# In[18]: +# In[ ]: def generate_data(n_samples, x, c=1, t=0.5): @@ -119,64 +120,65 @@ def generate_data(n_samples, x, c=1, t=0.5): plt.xlabel("x") plt.legend() plt.grid(True) +plt.show() # ## Solving the Neural Operator Problem -# +# # At their core, **Neural Operators** transform an input function $a$ into an output function $u$. The general structure of a Neural Operator consists of three key components: -# +# #

# Neural Operators #

-# +# # 1. **Encoder**: The encoder maps the input into a specific embedding space. -# -# 2. **Processor**: The processor consists of multiple layers performing **function convolutions**, which is the core computational unit in a Neural Operator. +# +# 2. **Processor**: The processor consists of multiple layers performing **function convolutions**, which is the core computational unit in a Neural Operator. # 3. **Decoder**: The decoder maps the processor's output back into the desired output space. -# +# # By varying the design and implementation of these three components — encoder, processor, and decoder — different Neural Operators are created, each tailored for specific applications or types of data. -# +# # ### Types of Neural Operators -# +# # Different variants of Neural Operators are designed to solve specific tasks. Some prominent examples include: -# -# - **Fourier Neural Operator (FNO)**: -# The **Fourier Neural Operator** utilizes the **Fourier transform** in the processor to perform global convolutions. This enables the operator to capture long-range dependencies efficiently. FNOs are particularly useful for problems with periodic data or problems where global patterns and interactions are important. -# ➤ [Learn more about FNO](https://mathlab.github.io/PINA/_rst/model/fourier_neural_operator.html). -# -# - **Graph Neural Operator (GNO)**: -# The **Graph Neural Operator** leverages **Graph Neural Networks (GNNs)** to exchange information between nodes, enabling the operator to perform convolutions on unstructured domains, such as graphs or meshes. GNOs are especially useful for problems that naturally involve irregular data, such as graph-based datasets or data on non-Euclidean spaces. -# ➤ [Learn more about GNO](https://mathlab.github.io/PINA/_rst/model/graph_neural_operator.html). -# -# - **Deep Operator Network (DeepONet)**: +# +# - **Fourier Neural Operator (FNO)**: +# The **Fourier Neural Operator** utilizes the **Fourier transform** in the processor to perform global convolutions. This enables the operator to capture long-range dependencies efficiently. FNOs are particularly useful for problems with periodic data or problems where global patterns and interactions are important. +# ➤ [Learn more about FNO](https://PINA-org.github.io/PINA/_rst/model/fourier_neural_operator.html). +# +# - **Graph Neural Operator (GNO)**: +# The **Graph Neural Operator** leverages **Graph Neural Networks (GNNs)** to exchange information between nodes, enabling the operator to perform convolutions on unstructured domains, such as graphs or meshes. GNOs are especially useful for problems that naturally involve irregular data, such as graph-based datasets or data on non-Euclidean spaces. +# ➤ [Learn more about GNO](https://PINA-org.github.io/PINA/_rst/model/graph_neural_operator.html). +# +# - **Deep Operator Network (DeepONet)**: # **DeepONet** is a variant of Neural Operators designed to solve operator equations by learning mappings between input and output functions. Unlike other Neural Operators, **DeepONet** does not use the typical encoder-processor-decoder structure. Instead, it uses two distinct neural networks: -# +# # 1. **Branch Network**: Takes the **function inputs** (e.g., $u(x)$) and learns a feature map of the input function. # 2. **Trunk Network**: Takes the **spatial locations** (e.g., $x$) and maps them to the output space. -# -# The output of **DeepONet** is the combination of these two networks' outputs, which together provide the mapping from the input function to the output function. -# ➤ [Learn more about DeepONet](https://mathlab.github.io/PINA/_rst/model/deeponet.html). -# +# +# The output of **DeepONet** is the combination of these two networks' outputs, which together provide the mapping from the input function to the output function. +# ➤ [Learn more about DeepONet](https://PINA-org.github.io/PINA/_rst/model/deeponet.html). +# # In this tutorial we will focus on Neural Operator which follow the Encoder - Processor - Decoder structure, which we call *Kernel* Neural Operator. Implementing kernel neural Operators in PINA is very simple, you just need to use the `KernelNeuralOperator` API. -# +# # ### KernelNeuralOperator API -# The `KernelNeuralOperator` API requires three parameters: -# +# The `KernelNeuralOperator` API requires three parameters: +# # 1. `lifting_operator`: a `torch.nn.Module` apping the input to its hidden dimension (Encoder). -# +# # 2. `integral_kernels`: a `torch.nn.Module` representing the integral kernels mapping each hidden representation to the next one. -# +# # 3. `projection_operator`: a `torch.nn.Module` representing the hidden representation to the output function. -# -# To construct the kernel, you can use the Neural Operator Blocks available in PINA (see [here](https://mathlab.github.io/PINA/_rst/_code.html#blocks)) or implement you own one! Let's build a simple FNO using the `FourierBlock1D`. In particular we will: -# +# +# To construct the kernel, you can use the Neural Operator Blocks available in PINA (see [here](https://PINA-org.github.io/PINA/_rst/_code.html#blocks)) or implement you own one! Let's build a simple FNO using the `FourierBlock1D`. In particular we will: +# # 1. Define the encoder, a simple linear layer mapping the input dimension to the hidden dimension # 2. Define the decoder, two linear layers mapping the hidden dimension to 128 and back to the input dimension # 3. Define the processor, a two layer Fourier block with a specific hidden dimension. # 4. Combine the encoder-processor-decoder using the `KernelNeuralOperator` API to create the `model`. -# +# -# In[23]: +# In[ ]: # 1. Define the encoder (simple linear layer 1->64) @@ -234,11 +236,11 @@ def forward(self, x): ) -# Done! Let's now solve the Neural Operator problem. The problem we will define is a basic `SupervisedProblem`, and we will use the `SupervisedSolver` to train the Neural Operator. -# -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** -# -# > **👉 We have a dedicated [tutorial](https://mathlab.github.io/PINA/tutorial18/tutorial.html) for an overview of Solvers in PINA — have a look if you're interested!** +# Done! Let's now solve the Neural Operator problem. The problem we will define is a basic `SupervisedProblem`, and we will use the `SupervisedSingleModelSolver` to train the Neural Operator. +# +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial16/tutorial.html) to teach how to build a Problem from scratch — have a look if you're interested!** +# +# > **👉 We have a dedicated [tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial18/tutorial.html) for an overview of Solvers in PINA — have a look if you're interested!** # In[ ]: @@ -247,7 +249,7 @@ def forward(self, x): problem = SupervisedProblem(input, target) # making the solver -solver = SupervisedSolver(problem, model, use_lt=False) +solver = SupervisedSingleModelSolver(problem, model, use_lt=False) # simple training trainer = Trainer( @@ -260,14 +262,14 @@ def forward(self, x): enable_model_summary=False, ) trainer.train() -_ = trainer.test() +trainer.test() # ## Visualizing the Predictions -# +# # As we can see, we have achieved a very low MSE, even after training for only one epoch. Now, we will visualize the results in the same way as we did previously: -# In[30]: +# In[ ]: # generate new data @@ -284,18 +286,19 @@ def forward(self, x): plt.xlabel("x") plt.legend() plt.grid(True) +plt.show() # Nice! We can see that the network is correctly learning the solution operator and it was very simple! -# +# # ## What's Next? -# +# # Congratulations on completing the introductory tutorial on Neural Operators! Now that you have a solid foundation, here are a few directions you can explore: -# +# # 1. **Experiment with Training Duration & Network Architecture** — Try different training durations and tweak the network architecture to optimize performance. Choose different integral kernels and see how the results vary. -# +# # 2. **Explore Other Models in `pina.model`** — Check out other models available in `pina.model` or design your own custom PyTorch module to suit your needs. What about trying a `DeepONet`? -# +# # 3. **...and many more!** — The possibilities are vast! Continue experimenting with advanced configurations, solvers, and features in PINA. For example, consider incorporating physics-informed terms during training to enhance model generalization. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial22/tutorial.py b/tutorials/tutorial22/tutorial.py index 4e795ce..6d27095 100644 --- a/tutorials/tutorial22/tutorial.py +++ b/tutorials/tutorial22/tutorial.py @@ -3,11 +3,11 @@ # # Tutorial: Reduced Order Model with Graph Neural Networks # -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial22/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial22/tutorial.ipynb) # # # > ##### ⚠️ ***Before starting:*** -# > We assume you are already familiar with the concepts covered in the [Data Structure for SciML](https://mathlab.github.io/PINA/tutorial19/tutorial.html) tutorial. If not, we strongly recommend reviewing them before exploring this advanced topic. +# > We assume you are already familiar with the concepts covered in the [Data Structure for SciML](https://PINA-org.github.io/PINA-Tutorials/tutorial19/tutorial.html) tutorial. If not, we strongly recommend reviewing them before exploring this advanced topic. # # In this tutorial, we use **PINA** to construct a graph-based reduced-order model for a parametrized partial differential equation. The workflow is largely inspired by [*A graph convolutional autoencoder approach to model order reduction for parametrized PDEs*](https://www.sciencedirect.com/science/article/pii/S0021999124000111). # @@ -32,7 +32,7 @@ IN_COLAB = False if IN_COLAB: get_ipython().system('pip install "pina-mathlab[tutorial]"') - get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial22/holed_poisson.pt" -O "holed_poisson.pt"') + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial22/holed_poisson.pt" -O "holed_poisson.pt"') import torch from torch_geometric.nn import GMMConv @@ -273,7 +273,7 @@ def forward(self, data): # # We now train the autoencoder with PINA's supervised-learning workflow. # -# First, we define a [`SupervisedProblem`](https://mathlab.github.io/PINA/_rst/problem/zoo/supervised_problem.html#module-pina.problem.zoo.supervised_problem): +# First, we define a [`SupervisedProblem`](https://PINA-org.github.io/PINA/_rst/problem/zoo/supervised_problem.html#module-pina.problem.zoo.supervised_problem): # # - **input:** graph objects containing the solution fields and mesh information; # - **target:** the original node-wise solution values that the autoencoder must reconstruct. @@ -516,4 +516,4 @@ def forward(self, output, target): # # 3. **...and many more!** — The possibilities are vast! Continue experimenting with advanced configurations, solvers, and features in PINA. # -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial23/tutorial.py b/tutorials/tutorial23/tutorial.py index 44ba105..4159d55 100644 --- a/tutorials/tutorial23/tutorial.py +++ b/tutorials/tutorial23/tutorial.py @@ -2,18 +2,18 @@ # coding: utf-8 # # Tutorial: Data-driven System Identification with SINDy -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial23/tutorial.ipynb) -# -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial23/tutorial.ipynb) +# +# # > ##### ⚠️ ***Before starting:*** -# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://mathlab.github.io/PINA/_tutorial.html#getting-started-with-pina) tutorial. If not, we strongly recommend reviewing them before exploring this advanced topic. -# +# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://PINA-org.github.io/PINA-Tutorials/#getting-started-with-pina) tutorial. If not, we strongly recommend reviewing them before exploring this advanced topic. +# # In this tutorial, we will demonstrate a typical use case of **PINA** for Data-driven system identification using SINDy. The tutorial is largely inspired by the paper [Discovering governing equations from data by sparse identification of nonlinear dynamical systems](dx.doi.org/10.1073/pnas.1517384113). -# +# # Let's start by importing the useful modules: -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -37,26 +37,27 @@ from scipy.integrate import odeint from pina import Trainer, LabelTensor from pina.problem.zoo import SupervisedProblem -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.optim import TorchOptimizer from pina.model import SINDy + # ## Data generation # In this tutorial, we'll focus on the **identification** of a dynamical system starting only from a finite set of **snapshots**. # More precisely, we'll assume that the dynamics is governed by dynamical system written as follows: # $$\dot{\boldsymbol{x}}(t)=\boldsymbol{f}(\boldsymbol{x}(t)),$$ # along with suitable initial conditions. # For simplicity, we'll omit the argument of $\boldsymbol{x}$ from this point onward. -# +# # Since $\boldsymbol{f}$ is unknown, we want to model it. # While neural networks could be used to find an expression for $\boldsymbol{f}$, in certain contexts - for instance, to perform long-horizon forecasting - it might be useful to have an **explicit** set of equations describing it, which would also allow for a better degree of **interpretability** of our model. -# +# # As a result, we use SINDy (introduced in [this paper](https://www.pnas.org/doi/full/10.1073/pnas.1517384113)), which we'll describe later on. # Now, instead, we describe the system that is going to be considered in this tutorial: the **Lorenz** system. -# +# # The Lorenz system is a set of three ordinary differential equations and is a simplified model of atmospheric convection. # It is well-known because it can exhibit chaotic behavior, _i.e._, for given values of the parameters solutions are highly sensitive to small perturbations in the initial conditions, making forecasting extremely challenging. -# +# # Mathematically speaking, we can write the Lorenz equations as # $$ # \begin{cases} @@ -66,13 +67,13 @@ # \end{cases} # $$ # With $\sigma = 10,\, \rho = 28$, and $\beta=8/3$, the solutions trace out the famous butterfly-shaped Lorenz attractor. -# +# # With the following lines of code, we just generate the dataset for SINDy and plot some trajectories. -# +# # **Disclaimer**: of course, here we use the equations defining the Lorenz system just to generate the data. # If we had access to the dynamical term $\boldsymbol{f}$, there would be no need to use SINDy. -# In[2]: +# In[ ]: sigma, rho, beta = 10.0, 28.0, 8 / 3 @@ -125,29 +126,29 @@ def plot_n_conditions(X, n_to_plot): # \dot{x}_i = f_i(\boldsymbol{x}) = \sum_{k}\Theta(\boldsymbol{x})_{k}\xi_{k,i}, # $$ # with $\boldsymbol{\xi}_i\in\mathbb{R}^r$ a vector of **coefficients** telling us which terms are active in the expression of $f_i$. -# +# # Since we are in a supervised setting, we assume that we have at our disposal the snapshot matrix $\boldsymbol{X}$ and a matrix $\dot{\boldsymbol{X}}$ containing time **derivatives** at the corresponding time instances. # Then, we can just impose that the previous relation holds on the data at our disposal. # That is, our optimization problem will read as follows: # $$ # \min_{\boldsymbol{\Xi}}\|\dot{\boldsymbol{X}}-\Theta(\boldsymbol{X})\boldsymbol{\Xi}\|_2^2. # $$ -# +# # Notice, however, that the solution to the previous equation might not be **sparse**, as there might be many non-zero terms in it. # In practice, many physical systems are described by a parsimonious and **interpretable** set of equations. # Thus, we also impose a $L^1$ **penalization** on the model weights, encouraging them to be small in magnitude and trying to enforce sparsity. # The final loss is then expressed as -# +# # $$ # \min_{\boldsymbol{\Xi}}\bigl(\|\dot{\boldsymbol{X}}-\Theta(\boldsymbol{X})\boldsymbol{\Xi}\|_2^2 + \lambda\|\boldsymbol{\Xi}\|_1\bigr), # $$ # with $\lambda\in\mathbb{R}^+$ a hyperparameter. -# +# # Let us begin by computing the time derivatives of the data. # Of course, usually we do not have access to the exact time derivatives of the system, meaning that $\dot{\boldsymbol{X}}$ needs to be **approximated**. # Here we do it using a simple Finite Difference (FD) scheme, but [more sophisticated ideas](https://arxiv.org/abs/2505.16058) could be considered. -# In[3]: +# In[ ]: dXdt = np.gradient(X, t, axis=1, edge_order=2) @@ -159,7 +160,7 @@ def plot_n_conditions(X, n_to_plot): # We create two `LabelTensor` objects to keep everything as readable as possible. -# In[4]: +# In[ ]: X_torch = LabelTensor(X_torch, ["x", "y", "z"]) @@ -171,7 +172,7 @@ def plot_n_conditions(X, n_to_plot): # While the `SINDy` class in **PINA** expects a **list** of callables, here we define also dictionary, as its keys will be used to print the retrieved equations, enhancing the model interpretability and allowing it to be compared to the original Lorenz system. # Notice how readable the code is as a result of the use of the `LabelTensor` class! -# In[5]: +# In[ ]: function_dict = { @@ -194,12 +195,12 @@ def plot_n_conditions(X, n_to_plot): # ## Training with PINA # We are now ready to train our model! We can use **PINA** to train the model, following the workflow from previous tutorials. -# First, we need to define the problem. In this case, we will use the [`SupervisedProblem`](https://mathlab.github.io/PINA/_rst/problem/zoo/supervised_problem.html#module-pina.problem.zoo.supervised_problem), which expects: -# -# - **Input**: the state variables tensor $\boldsymbol{X}$ containing all the collected snapshots. +# First, we need to define the problem. In this case, we will use the [`SupervisedProblem`](https://PINA-org.github.io/PINA/_rst/problem/zoo/supervised_problem.html#module-pina.problem.zoo.supervised_problem), which expects: +# +# - **Input**: the state variables tensor $\boldsymbol{X}$ containing all the collected snapshots. # - **Output**: the corresponding time derivatives $\dot{\boldsymbol{X}}$. -# In[6]: +# In[ ]: _lambda = 1e-3 @@ -208,15 +209,15 @@ def plot_n_conditions(X, n_to_plot): problem = SupervisedProblem(X_torch, dXdt_torch) -# Finally, we will use the `SupervisedSolver` to perform the training as we're dealing with a supervised problem. -# +# Finally, we will use the `SupervisedSingleModelSolver` to perform the training as we're dealing with a supervised problem. +# # Recall that we should use $L^1$-regularization on the model's weights to ensure sparsity. For the ease of implementation, we adopt $L^2$ regularization, which is less common in SINDy literature but will suffice in our case. # Additionally, more refined strategies could be used, for instance pruning coefficients below a certain **threshold** at every fixed number of epochs, but here we avoid further complications. -# In[7]: +# In[ ]: -solver = SupervisedSolver( +solver = SupervisedSingleModelSolver( problem, model=model, optimizer=TorchOptimizer(torch.optim.Adam, lr=1e-3, weight_decay=_lambda), @@ -245,14 +246,14 @@ def plot_n_conditions(X, n_to_plot): # Now we'll print the identified equations and compare them with the original ones. -# +# # Before going on, we underline that after training there might be many coefficients that are small, yet still non-zero. # It is common for SINDy practitioners to interpret these coefficients as noise in the model and prune them. # This is typically done by fixing a threshold $\tau\in\mathbb{R}^+$ and setting to $0$ all those $\xi_{i,j}$ such that $|\xi_{i,j}|<\tau$. -# +# # In the following cell, we also define a function to print the identified model. -# In[9]: +# In[ ]: def print_coefficients(model, function_names, tau, vars=None): @@ -298,12 +299,12 @@ def print_coefficients(model, function_names, tau, vars=None): # \dot{z}=-\frac{8}{3} z+xy. # \end{cases} # $$ -# +# # That's a good result, especially considering that we did not perform tuning on the weight decay hyperparameter $\lambda$ and did not really care much about other optimization parameters. -# +# # Let's plot a few trajectories! -# In[10]: +# In[ ]: def SINDy_equations(x, t): # we need a numpy array for odeint @@ -327,14 +328,14 @@ def SINDy_equations(x, t): # we need a numpy array for odeint # Great! We can see that the qualitative behavior of the system is really close to the real one. -# +# # ## What's next? # Congratulations on completing the introductory tutorial on **Data-driven System Identification with SINDy**! Now that you have a solid foundation, here are a few directions to explore: -# -# 1. **Experiment with Dimensionality Reduction techniques** — Try to combine SINDy with different reductions techniques such as POD or autoencoders - or both of them, as done [here](https://www.sciencedirect.com/science/article/abs/pii/S0045793025003019). -# +# +# 1. **Experiment with Dimensionality Reduction techniques** — Try to combine SINDy with different reductions techniques such as POD or autoencoders - or both of them, as done [here](https://www.sciencedirect.com/science/article/abs/pii/S0045793025003019). +# # 2. **Study Parameterized Systems** — Write your own SINDy model for parameterized problems. -# +# # 3. **...and many more!** — The possibilities are vast! Continue experimenting with advanced configurations, solvers, and features in PINA. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial24/tutorial.py b/tutorials/tutorial24/tutorial.py index 66b245b..dc365d3 100644 --- a/tutorials/tutorial24/tutorial.py +++ b/tutorials/tutorial24/tutorial.py @@ -2,18 +2,18 @@ # coding: utf-8 # # Tutorial: Advection Equation with data driven DeepONet -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial24/tutorial.ipynb) -# -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial24/tutorial.ipynb) +# +# # > ##### ⚠️ ***Before starting:*** -# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://mathlab.github.io/PINA/_tutorial.html#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. -# +# > We assume you are already familiar with the concepts covered in the [Getting started with PINA](https://PINA-org.github.io/PINA-Tutorials/#getting-started-with-pina) tutorials. If not, we strongly recommend reviewing them before exploring this advanced topic. +# # In this tutorial, we demonstrate how to solve the advection operator learning problem using `DeepONet`. We follow the original formulation of Lu *et al.* in [*DeepONet: Learning nonlinear operators for identifying differential equations based on the universal approximation theorem of operator*](https://arxiv.org/abs/1910.03193). -# +# # We begin by importing the necessary modules. -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -27,18 +27,10 @@ get_ipython().system('pip install "pina-mathlab[tutorial]"') # get the data get_ipython().system('mkdir "data"') - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial24/data/advection_input_testing.pt" -O "data/advection_input_testing.pt"' - ) - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial24/data/advection_input_training.pt" -O "data/advection_input_training.pt"' - ) - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial24/data/advection_output_testing.pt" -O "data/advection_output_testing.pt"' - ) - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial24/data/advection_output_training.pt" -O "data/advection_output_training.pt"' - ) + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial24/data/advection_input_testing.pt" -O "data/advection_input_testing.pt"') + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial24/data/advection_input_training.pt" -O "data/advection_input_training.pt"') + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial24/data/advection_output_testing.pt" -O "data/advection_output_testing.pt"') + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial24/data/advection_output_training.pt" -O "data/advection_output_training.pt"') import matplotlib.pyplot as plt import torch @@ -47,7 +39,7 @@ from pina import Trainer, LabelTensor from pina.model import FeedForward, DeepONet -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.problem.zoo import SupervisedProblem from pina.loss import LpLoss @@ -55,34 +47,34 @@ # ## Advection problem and data preparation -# +# # We consider the 1D advection equation # $$ -# \frac{\partial u}{\partial t} + \frac{\partial u}{\partial x} = 0, +# \frac{\partial u}{\partial t} + \frac{\partial u}{\partial x} = 0, # \quad x \in [0,2], \; t \in [0,1], # $$ # with periodic boundary conditions. The initial condition is chosen as a Gaussian pulse centered at a random location # $\mu \sim U(0.05, 1)$ and with variance $\sigma^2 = 0.02$: # $$ -# u_0(x) = \frac{1}{\sqrt{\pi\sigma^2}} e^{-\frac{(x - \mu)^2}{2\sigma^2}}, +# u_0(x) = \frac{1}{\sqrt{\pi\sigma^2}} e^{-\frac{(x - \mu)^2}{2\sigma^2}}, # \quad x \in [0,2]. # $$ -# +# # Our goal is to learn the operator # $$ # \mathcal{G}: u_0(x) \mapsto u(x, t = \delta) = u_0(x - \delta), # $$ -# with $\delta = 0.5$ for this tutorial. In practice, this means learning a mapping from the initial condition to the solution at a fixed later time. +# with $\delta = 0.5$ for this tutorial. In practice, this means learning a mapping from the initial condition to the solution at a fixed later time. # The dataset therefore consists of trajectories where inputs are initial profiles and outputs are the same profiles shifted by $\delta$. -# +# # The data has shape `[T, Nx, D]`, where: # - `T` — number of trajectories (100 for training, 1000 for testing), # - `Nx` — number of spatial grid points (fixed at 100), # - `D = 1` — single scalar field value `u`. -# +# # We now load the dataset and visualize sample trajectories. -# In[2]: +# In[ ]: # loading training data @@ -108,7 +100,7 @@ # The data are loaded, let's visualize a few of the initial conditions! -# In[3]: +# In[ ]: # storing the discretization in space: @@ -126,30 +118,31 @@ plt.xlabel(rf"$x$") plt.tight_layout() plt.legend() +plt.show() # Great — we have generated a traveling wave and visualized a few samples. Next, we will use this data to train a `DeepONet`. -# +# # ## DeepONet -# +# # The standard `DeepONet` architecture consists of two subnetworks: a **branch** network and a **trunk** network (see figure below). -# +# #
# image from: Moya, C.; Lin, G. Fed-DeepONet: Stochastic Gradient-Based Federated Training of Deep Operator Networks. Algorithms 2022, 15, 325. https://doi.org/10.3390/a15090325 #
#
# Image source: Moya & Lin (2022) #
-# +# # In our setting: # - The **branch network** receives the initial condition of each trajectory, with input shape `[B, Nx]` — where `B` is the batch size and `Nx` the spatial discretization points of the field at \( t = 0 \). # - The **trunk network** takes input of shape `[B, 1]`, corresponding to the location at which we evaluate the solution (in this 1D case, the spatial coordinate). -# +# # Together, these networks learn the mapping from the initial field to the solution at a later time. -# +# # We now define and train the model for the advection problem. -# In[4]: +# In[ ]: problem = SupervisedProblem( @@ -162,7 +155,7 @@ # We now proceede to create the trunk and branch networks. -# In[5]: +# In[ ]: # create Trunk model @@ -189,12 +182,12 @@ def forward(self, x): # The `TrunkNet` is implemented as a standard `FeedForward` network with a slightly modified `forward` method. In this case, the trunk network simply outputs a tensor filled with the value \(0.5\), repeated for each trajectory — corresponding to evaluating the solution at time \(t = 0.5\). -# +# # The `BranchNet` is also a `FeedForward` network, but its `forward` pass first flattens the input along the last dimension. This produces a vector of length `Nx`, representing the sampled initial condition at the sensor locations. -# +# # With both subnetworks defined, we can now instantiate the DeepONet model using the `DeepONet` class from `pina.model`. -# In[6]: +# In[ ]: # initialize truck and branch net @@ -223,14 +216,14 @@ def forward(self, x): # The aggregation and reduction functions combine the outputs of the branch and trunk networks. In this example, their outputs are multiplied element-wise, and no reduction is applied — meaning the final output has the same dimensionality as each network’s output. -# -# We train the model using a `SupervisedSolver` with an `MSE` loss. Below, we first define the solver and then the trainer used to run the optimization. +# +# We train the model using a `SupervisedSingleModelSolver` with an `MSE` loss. Below, we first define the solver and then the trainer used to run the optimization. # In[ ]: # define solver -solver = SupervisedSolver(problem=problem, model=model) +solver = SupervisedSingleModelSolver(problem=problem, model=model) # define the trainer and train trainer = Trainer( @@ -241,7 +234,7 @@ def forward(self, x): # Let's see the final train and test errors: -# In[8]: +# In[ ]: # the l2 error @@ -257,7 +250,7 @@ def forward(self, x): # We can see that the testing error is slightly higher than the training one, maybe due to overfitting. We now plot some results trajectories. -# In[9]: +# In[ ]: for i in [1, 2, 3]: @@ -274,12 +267,12 @@ def forward(self, x): ) plt.xlabel(r"$x$") plt.legend(loc="upper right") - plt.show() +plt.show() -# As we can see, they are barely indistinguishable. To better understand the difference, we now plot the residuals, i.e. the difference of the exact solution and the predicted one. +# As we can see, they are barely indistinguishable. To better understand the difference, we now plot the residuals, i.e. the difference of the exact solution and the predicted one. -# In[10]: +# In[ ]: for i in [1, 2, 3]: @@ -293,20 +286,21 @@ def forward(self, x): plt.xlabel(r"$x$") plt.tight_layout() plt.legend(loc="upper right") +plt.show() # ## What's Next? -# +# # We have seen a simple example of using `DeepONet` to learn the advection operator. This only scratches the surface of what neural operators can do. Here are some suggested directions to continue your exploration: -# +# # 1. **Train on more complex PDEs**: Extend beyond the advection equation to more challenging operators, such as diffusion or nonlinear conservation laws. -# +# # 2. **Increase training scope**: Experiment with larger datasets, deeper networks, and longer training schedules to unlock the full potential of neural operator learning. -# +# # 3. **Generalize to the full advection operator**: Train the model to learn the general operator $\mathcal{G}_t: u_0(x) \mapsto u(x,t) = u_0(x - t)$ so the network predicts solutions for arbitrary times, not just a single fixed horizon. -# +# # 4. **Investigate architectural variations**: Compare different operator learning architectures (e.g., Fourier Neural Operators, Physics-Informed DeepONets) to see how they perform on similar problems. -# +# # 5. **...and much more!**: From adding noise robustness to testing on real scientific datasets, the space of possibilities is wide open. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial3/tutorial.py b/tutorials/tutorial3/tutorial.py index bf6585b..deea560 100644 --- a/tutorials/tutorial3/tutorial.py +++ b/tutorials/tutorial3/tutorial.py @@ -2,11 +2,11 @@ # coding: utf-8 # # Tutorial: Applying Hard Constraints in PINNs to solve the Wave Problem -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial3/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial3/tutorial.ipynb) +# # In this tutorial, we will present how to solve the wave equation using **hard constraint Physics-Informed Neural Networks (PINNs)**. To achieve this, we will build a custom `torch` model and pass it to the **PINN solver**. -# +# # First of all, some useful imports. # In[ ]: @@ -29,18 +29,18 @@ from pina import Condition, LabelTensor, Trainer from pina.problem import SpatialProblem, TimeDependentProblem from pina.domain import CartesianDomain -from pina.solver import PINN -from pina.equation import Equation, FixedValue +from pina.solver import PhysicsInformedSingleModelSolver +from pina.equation import Equation +from pina.equation.zoo import FixedValue, AcousticWaveEquation from pina.callback import MetricTracker -from pina.equation import AcousticWave warnings.filterwarnings("ignore") -# ## The problem definition -# +# ## The problem definition +# # The problem is described by the following system of partial differential equations (PDEs): -# +# # \begin{equation} # \begin{cases} # \Delta u(x,y,t) = \frac{\partial^2}{\partial t^2} u(x,y,t) \quad \text{in } D, \\\\ @@ -48,17 +48,17 @@ # u(x, y, t) = 0 \quad \text{on } \Gamma_1 \cup \Gamma_2 \cup \Gamma_3 \cup \Gamma_4, # \end{cases} # \end{equation} -# +# # Where: -# +# # - $D$ is a square domain $[0, 1]^2$. # - $\Gamma_i$, where $i = 1, \dots, 4$, are the boundaries of the square where Dirichlet conditions are applied. # - The velocity in the standard wave equation is fixed to $1$. -# In[2]: +# In[ ]: -wave_equation = AcousticWave(c=1.0) +wave_equation = AcousticWaveEquation(c=1.0) def initial_condition(input_, output_): @@ -101,16 +101,16 @@ def solution(self, pts): # ## Hard Constraint Model -# +# # Once the problem is defined, a **torch** model is needed to solve the PINN. While **PINA** provides several pre-implemented models, users have the option to build their own custom model using **torch**. The hard constraint we impose is on the boundary of the spatial domain. Specifically, the solution is written as: -# +# # $$ u_{\rm{pinn}} = xy(1-x)(1-y)\cdot NN(x, y, t), $$ -# +# # where $NN$ represents the neural network output. This neural network takes the spatial coordinates $x$, $y$, and time $t$ as input and provides the unknown field $u$. By construction, the solution is zero at the boundaries. -# +# # The residuals of the equations are evaluated at several sampling points (which the user can manipulate using the `discretise_domain` method). The loss function minimized by the neural network is the sum of the residuals. -# In[3]: +# In[ ]: class HardMLP(torch.nn.Module): @@ -144,13 +144,13 @@ def forward(self, x): # generate the data -problem.discretise_domain(1000, "random", domains="all") +problem.discretise_domain(1000, "random") # define model model = HardMLP(len(problem.input_variables), len(problem.output_variables)) # crete the solver -pinn = PINN(problem=problem, model=model) +pinn = PhysicsInformedSingleModelSolver(problem=problem, model=model) # create trainer and train trainer = Trainer( @@ -168,7 +168,7 @@ def forward(self, x): # Let's now plot the losses inside `MetricTracker` to see how they vary during training. -# In[5]: +# In[ ]: trainer_metrics = trainer.callbacks[0].metrics @@ -179,11 +179,12 @@ def forward(self, x): plt.ylabel("loss") plt.yscale("log") plt.legend() +plt.show() # Notice that the loss on the boundaries of the spatial domain is exactly zero, as expected! Once the training is completed, we can plot the results using `matplotlib`. We will display the predicted output on the left side, the true solution in the center, and the difference between them on the right side using the `plot_solution` function. -# In[6]: +# In[ ]: @torch.no_grad() @@ -215,11 +216,12 @@ def plot_solution(solver, time): field.tensor.flatten(), ) plt.colorbar(), plt.tight_layout() + plt.show() # Let's take a look at the results at different times, for example `0.0`, `0.5` and `1.0`: -# In[7]: +# In[ ]: plt.figure(figsize=(12, 6)) @@ -233,16 +235,16 @@ def plot_solution(solver, time): # The results are not ideal, and we can clearly see that as time progresses, the solution deteriorates. Can we do better? -# +# # One valid approach is to impose the initial condition as a hard constraint as well. Specifically, we modify the solution to: -# +# # $$ # u_{\rm{pinn}} = xy(1-x)(1-y) \cdot NN(x, y, t) \cdot t + \cos(\sqrt{2}\pi t)\sin(\pi x)\sin(\pi y), # $$ -# +# # Now, let us start by building the neural network. -# In[8]: +# In[ ]: class HardMLPtime(torch.nn.Module): @@ -285,7 +287,7 @@ def forward(self, x): model = HardMLPtime(len(problem.input_variables), len(problem.output_variables)) # crete the solver -pinn = PINN(problem=problem, model=model) +pinn = PhysicsInformedSingleModelSolver(problem=problem, model=model) # create trainer and train trainer = Trainer( @@ -303,7 +305,7 @@ def forward(self, x): # We can clearly see that the loss is way lower now. Let's plot the results -# In[10]: +# In[ ]: plt.figure(figsize=(12, 6)) @@ -319,19 +321,19 @@ def forward(self, x): # We can now see that the results are much better! This improvement is due to the fact that, previously, the network was not correctly learning the initial condition, which led to a poor solution as time evolved. By imposing the initial condition as a hard constraint, the network is now able to correctly solve the problem. # ## What's Next? -# +# # Congratulations on completing the two-dimensional Wave tutorial of **PINA**! Now that you’ve got the basics down, there are several directions you can explore: -# +# # 1. **Train the Network for Longer**: Train the network for a longer duration or experiment with different layer sizes to assess the final accuracy. -# +# # 2. **Propose New Types of Hard Constraints in Time**: Experiment with new time-dependent hard constraints, for example: -# +# # $$ # u_{\rm{pinn}} = xy(1-x)(1-y)\cdot NN(x, y, t)(1-\exp(-t)) + \cos(\sqrt{2}\pi t)\sin(\pi x)\sin(\pi y) # $$ -# +# # 3. **Exploit Extrafeature Training**: Apply extrafeature training techniques to improve models from 1 and 2. -# +# # 4. **...and many more!**: The possibilities are endless! Keep experimenting and pushing the boundaries. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial4/tutorial.py b/tutorials/tutorial4/tutorial.py index 1db6340..bef47cf 100644 --- a/tutorials/tutorial4/tutorial.py +++ b/tutorials/tutorial4/tutorial.py @@ -2,10 +2,10 @@ # coding: utf-8 # # Tutorial: Unstructured Convolutional Autoencoders with Continuous Convolution -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial4/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial4/tutorial.ipynb) # In this tutorial, we will show how to use the Continuous Convolutional Filter, and how to build common Deep Learning architectures with it. The implementation of the filter follows the original work [*A Continuous Convolutional Trainable Filter for Modelling Unstructured Data*](https://arxiv.org/abs/2210.13416). -# +# # First of all we import the modules needed for the tutorial: # In[ ]: @@ -28,7 +28,7 @@ from pina import Trainer from pina.problem.zoo import SupervisedProblem -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.trainer import Trainer from pina.model.block import ContinuousConvBlock from pina.model import FeedForward # for building AE and MNIST classification @@ -37,76 +37,76 @@ # ## Tutorial Structure -# +# # The tutorial is structured as follows: -# -# - [🔹 Continuous Filter Background](#continuous-filter-background): +# +# - [🔹 Continuous Filter Background](#continuous-filter-background): # Understand how the convolutional filter works and how to use it. -# -# - [🔹 Building a MNIST Classifier](#building-a-mnist-classifier): +# +# - [🔹 Building a MNIST Classifier](#building-a-mnist-classifier): # Learn how to build a simple classifier using the MNIST dataset, and how to combine a continuous convolutional layer with a feedforward neural network. -# -# - [🔹 Building a Continuous Convolutional Autoencoder](#building-a-continuous-convolutional-autoencoder): +# +# - [🔹 Building a Continuous Convolutional Autoencoder](#building-a-continuous-convolutional-autoencoder): # Explore how to use the continuous filter to work with unstructured data for autoencoding and up-sampling. -# +# # ## Continuous Filter Background -# +# # As reported by the authors in the original paper, in contrast to discrete convolution, **continuous convolution** is mathematically defined as: -# +# # $$ # \mathcal{I}_{\rm{out}}(\mathbf{x}) = \int_{\mathcal{X}} \mathcal{I}(\mathbf{x} + \mathbf{\tau}) \cdot \mathcal{K}(\mathbf{\tau}) d\mathbf{\tau}, # $$ -# +# # where: # - $\mathcal{K} : \mathcal{X} \rightarrow \mathbb{R}$ is the **continuous filter** function, # - $\mathcal{I} : \Omega \subset \mathbb{R}^N \rightarrow \mathbb{R}$ is the input function. -# +# # The **continuous filter function** is approximated using a **FeedForward Neural Network**, which is **trainable** during the training phase. The way in which the integral is approximated can vary. In the **PINA** framework, we approximate it using a simple sum, as suggested by the authors. Thus, given the points $\{\mathbf{x}_i\}_{i=1}^{n}$ in $\mathbb{R}^N$ mapped onto the filter domain $\mathcal{X}$, we approximate the equation as: -# +# # $$ # \mathcal{I}_{\rm{out}}(\mathbf{\tilde{x}}_i) = \sum_{{\mathbf{x}_i}\in\mathcal{X}} \mathcal{I}(\mathbf{x}_i + \mathbf{\tau}) \cdot \mathcal{K}(\mathbf{x}_i), # $$ -# +# # where $\mathbf{\tau} \in \mathcal{S}$, with $\mathcal{S}$ being the set of available strides, represents the current stride position of the filter. The $\mathbf{\tilde{x}}_i$ points are obtained by taking the **centroid** of the filter position mapped onto the domain $\Omega$. -# +# # ### Working with the Continuous Filter -# +# # From the above definition, what is needed is: # 1. A **domain** and a **function** defined on that domain (the input), # 2. A **stride**, corresponding to the positions where the filter needs to be applied (this is the `stride` variable in `ContinuousConv`), # 3. The **filter's rectangular domain**, which corresponds to the `filter_dim` variable in `ContinuousConv`. -# +# # ### Input Function -# +# # The input function for the continuous filter is defined as a tensor of shape: -# +# # $$[B \times N_{\text{in}} \times N \times D]$$ -# +# # where: # - $B$ is the **batch size**, # - $N_{\text{in}}$ is the number of input fields, # - $N$ is the number of points in the mesh, -# - $D$ is the dimension of the problem. -# +# - $D$ is the dimension of the problem. +# # In particular: # - $D$ represents the **number of spatial variables** + 1. The last column must contain the field value. For example, for 2D problems, $D=3$ and the tensor will look like `[first coordinate, second coordinate, field value]`. # - $N_{\text{in}}$ represents the number of vectorial functions presented. For example, a vectorial function $f = [f_1, f_2]$ will have $N_{\text{in}}=2$. -# +# # #### Example: Input Function for a Vectorial Field -# +# # Let’s see an example to clarify the idea. Suppose we wish to create the function: -# +# # $$ # f(x, y) = [\sin(\pi x) \sin(\pi y), -\sin(\pi x) \sin(\pi y)] \quad (x,y)\in[0,1]\times[0,1] # $$ -# +# # We can do this with a **batch size** equal to 1. This function consists of two components (vectorial field), so $N_{\text{in}}=2$. For each $(x,y)$ pair in the domain $[0,1] \times [0,1]$, we will compute the corresponding field values: -# +# # 1. $\sin(\pi x) \sin(\pi y)$ # 2. $-\sin(\pi x) \sin(\pi y)$ -# In[2]: +# In[ ]: # batch size fixed to 1 @@ -139,9 +139,9 @@ # ### Stride -# +# # The **stride** is passed as a dictionary `stride` that dictates where the filter should move. Here's an example for the domain $[0,1] \times [0,5]$: -# +# # ```python # # stride definition # stride = {"domain": [1, 5], @@ -155,13 +155,13 @@ # 2. `start`: The starting position of the filter's centroid. In this example, the filter starts at the position $(0, 0)$. # 3. `jump`: The steps or jumps of the filter’s centroid to the next position. In this example, the filter moves by $(0.1, 0.3)$ along the x and y axes respectively. # 4. `direction`: The directions of the jumps for each coordinate. A value of 1 indicates the filter moves right, 0 means no movement, and -1 indicates the filter moves left with respect to its current position. -# +# # ### Filter definition -# +# # Now that we have defined the stride, we can move on to construct the continuous filter. # Let’s assume we want the output to contain only one field, and we will set the filter dimension to be $[0.1, 0.1]$. -# In[3]: +# In[ ]: # filter dim @@ -185,10 +185,10 @@ # That's it! In just one line of code, we have successfully created the continuous convolutional filter. By default, the `pina.model.FeedForward` neural network is initialized, which can be further customized according to your needs. -# +# # Additionally, if the mesh does not change during training, we can set the `optimize` flag to `True` to leverage optimizations for efficiently finding the points to convolve. This feature helps in improving the performance by reducing redundant calculations when the mesh remains constant. -# In[4]: +# In[ ]: # creating the filter + optimization @@ -203,7 +203,7 @@ # Let's try to do a forward pass: -# In[5]: +# In[ ]: print(f"Filter input data has shape: {data.shape}") @@ -216,7 +216,7 @@ # If you don't want to use the default `FeedForward` neural network, you can pass a custom PyTorch model by specifying it in the `model` keyword. Here's an example of how to do it: -# In[6]: +# In[ ]: class SimpleKernel(torch.nn.Module): @@ -244,13 +244,13 @@ def forward(self, x): ) -# Notice that we pass the **class** of the model and not an already built object! This is important because the `ContinuousConv` filter will automatically instantiate the model class when needed during training. -# +# Notice that we pass the **class** of the model and not an already built object! This is important because the `ContinuousConv` filter will automatically instantiate the model class when needed during training. +# # ## Building a MNIST Classifier -# +# # Let's see how we can build a MNIST classifier using a continuous convolutional filter. We will use the MNIST dataset from PyTorch. In order to keep small training times we use only 6000 samples for training and 1000 samples for testing. -# In[7]: +# In[ ]: numb_training = 6000 # get just 6000 images for training @@ -276,10 +276,10 @@ def forward(self, x): # Now, let's proceed to build a simple classifier for the MNIST dataset. The MNIST dataset consists of vectors with the shape `[batch, 1, 28, 28]`, but we can treat them as field functions where each pixel at coordinates $i,j$ corresponds to a point in a $[0, 27] \times [0, 27]$ domain. The pixel values represent the field values. -# +# # To use the continuous convolutional filter, we need to transform the regular tensor into a format compatible with the filter. Here's a function that will help with this transformation: -# In[8]: +# In[ ]: def transform_input(x): @@ -302,7 +302,7 @@ def transform_input(x): # We can now build a simple classifier! We will use just one convolutional filter followed by a feedforward neural network -# In[9]: +# In[ ]: # setting the seed @@ -345,22 +345,25 @@ def forward(self, x): return self.nn(x[..., -1].flatten(1)) -# We now aim to solve a classification problem. For this we will use the `SupervisedSolver` and the `SupervisedProblem`. The input of the supervised problems are the images, while the output the corresponding class. We will train with `CrossEntropyLoss`. +# We now aim to solve a classification problem. For this we will use the `SupervisedSingleModelSolver` and the `SupervisedProblem`. Because the supervised single-model solver works on prediction-target residuals, we encode the MNIST labels as one-hot vectors so their shape matches the 10-class output of the classifier. We then train with `MSELoss`. # In[ ]: # setting the problem +train_targets = torch.nn.functional.one_hot( + train_data.train_labels, num_classes=10 +).float() problem = SupervisedProblem( input_=train_data.train_data.unsqueeze(1), # adding channel dimension - output_=train_data.train_labels, + output_=train_targets, ) # setting the solver -solver = SupervisedSolver( +solver = SupervisedSingleModelSolver( problem=problem, model=ContinuousClassifier(), - loss=torch.nn.CrossEntropyLoss(), + loss=torch.nn.MSELoss(), use_lt=False, ) @@ -380,7 +383,7 @@ def forward(self, x): # Let's see the performance on the test set! -# In[11]: +# In[ ]: correct = 0 @@ -390,6 +393,9 @@ def forward(self, x): for data in trainer.data_module.test_dataloader(): test_data = data["data"] images, labels = test_data["input"], test_data["target"] + # if targets are one-hot vectors, convert back to class indices + if labels.ndim > 1 and labels.shape[-1] > 1: + labels = labels.argmax(dim=1) # calculate outputs by running images through the network outputs = solver(images) # the class with the highest energy is what we choose as prediction @@ -397,16 +403,16 @@ def forward(self, x): total += labels.size(0) correct += (predicted == labels).sum().item() -print(f"Accuracy of the network on the test images: {(correct / total):.3%}") +print(f"Accuracy of the network on the test images: {(correct / total):.2%}") # As we can see we have very good performance for having trained only for 1 epoch! Nevertheless, we are still using structured data... Let's see how we can build an autoencoder for unstructured data now. -# +# # ## Building a Continuous Convolutional Autoencoder -# +# # As a toy problem, we will now build an autoencoder for the function \( f(x, y) = \sin(\pi x) \sin(\pi y) \) on the unit circle domain centered at \( (0.5, 0.5) \). We will also explore the ability to up-sample the results (once trained) without needing to retrain the model. To begin, we'll generate the input data for the function. First, we will use a mesh of 100 points and visualize the input function. Here’s how to proceed: -# In[12]: +# In[ ]: # create inputs @@ -451,9 +457,9 @@ def circle_grid(N=100): # Now, let's create a simple autoencoder using the continuous convolutional filter. Since the data is inherently unstructured, a standard convolutional filter may not be effective without some form of projection or interpolation. We'll begin by building an `Encoder` and `Decoder` class, and then combine them into a unified `Autoencoder` class. -# +# -# In[13]: +# In[ ]: class Encoder(torch.nn.Module): @@ -520,7 +526,7 @@ def forward(self, weights, grid): # Great! In the `Decoder` class, during the `forward` pass, we used the `.transpose()` method of the `ContinuousConvolution` class. This method takes the `weights` for upsampling and the `grid` on which to perform the upsampling. Now, let's go ahead and build the autoencoder! We'll define the hidden dimension with the `hidden_dimension` variable, and apply the sigmoid function on the output since the field values are constrained within the range $[0, 1]$. -# In[14]: +# In[ ]: class Autoencoder(torch.nn.Module): @@ -540,7 +546,7 @@ def forward(self, x): return out -# Now, let's proceed with training the autoencoder by minimizing the mean squared error (MSE) loss and optimizing using the Adam optimizer. We'll use the `SupervisedSolver` for the training, and the problem will be defined as a simple problem inherited from `AbstractProblem`. +# Now, let's proceed with training the autoencoder by minimizing the mean squared error (MSE) loss and optimizing using the Adam optimizer. We'll use the `SupervisedSingleModelSolver` for the training, and the problem will be defined as a simple problem inherited from `BaseProblem`. # In[ ]: @@ -550,7 +556,7 @@ def forward(self, x): # define the solver -solver = SupervisedSolver( +solver = SupervisedSingleModelSolver( problem=problem, model=Autoencoder(), loss=torch.nn.MSELoss(), @@ -572,7 +578,7 @@ def forward(self, x): # Now, let's visualize the real solution alongside the autoencoder's reconstruction, displaying them side by side for comparison! -# In[16]: +# In[ ]: solver.eval() @@ -595,7 +601,7 @@ def forward(self, x): # As observed, the two solutions are nearly identical! We can also compute the $l_2$ error between the real solution and the autoencoder's reconstruction quite easily: -# In[17]: +# In[ ]: def l2_error(input_, target): @@ -608,12 +614,12 @@ def l2_error(input_, target): # The $l_2$ error is approximately $4\%$, which is quite low considering that we only use **one** convolutional layer and a simple feedforward network to reduce the dimension. Now, let's explore some of the unique features of the filter. -# +# # ### Upsampling with the Filter -# +# # Suppose we have a hidden representation and we want to upsample it on a different grid with more points. Let's see how we can achieve that: -# In[18]: +# In[ ]: # setting the seed @@ -647,7 +653,7 @@ def l2_error(input_, target): # As we can see, we have a very good approximation of the original function, although some noise is present. Let's now calculate the error: -# In[19]: +# In[ ]: print( @@ -656,15 +662,15 @@ def l2_error(input_, target): # ## What's Next? -# +# # Congratulations on completing the tutorial on using the Continuous Convolutional Filter in **PINA**! Now that you have the basics, there are several exciting directions you can explore: -# +# # 1. **Train using Physics-Informed strategies**: Leverage physics-based knowledge to improve model performance for solving real-world problems. -# +# # 2. **Use the filter to build an unstructured convolutional autoencoder**: Explore reduced-order modeling by implementing unstructured convolutional autoencoders. -# +# # 3. **Experiment with upsampling at different resolutions**: Try encoding or upsampling on different grids to see how the model generalizes across multiple resolutions. -# +# # 4. **...and many more!**: There are endless possibilities, from improving model architecture to testing with more complex datasets. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial5/tutorial.py b/tutorials/tutorial5/tutorial.py index 4a959a4..501bd57 100644 --- a/tutorials/tutorial5/tutorial.py +++ b/tutorials/tutorial5/tutorial.py @@ -2,13 +2,13 @@ # coding: utf-8 # # Tutorial: Modeling 2D Darcy Flow with the Fourier Neural Operator -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial5/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial5/tutorial.ipynb) +# # In this tutorial, we are going to solve the **Darcy flow problem** in two dimensions, as presented in the paper [*Fourier Neural Operator for Parametric Partial Differential Equations*](https://openreview.net/pdf?id=c8P9NQVtmnO). -# +# # We begin by importing the necessary modules for the tutorial: -# +# # In[ ]: @@ -22,11 +22,9 @@ IN_COLAB = False if IN_COLAB: get_ipython().system('pip install "pina-mathlab[tutorial]"') - get_ipython().system("pip install scipy") + get_ipython().system('pip install scipy') # get the data - get_ipython().system( - "wget https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial5/Data_Darcy.mat" - ) + get_ipython().system('wget https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial5/Data_Darcy.mat') import torch import matplotlib.pyplot as plt @@ -35,25 +33,25 @@ from scipy import io from pina.model import FNO, FeedForward from pina import Trainer -from pina.solver import SupervisedSolver +from pina.solver import SupervisedSingleModelSolver from pina.problem.zoo import SupervisedProblem warnings.filterwarnings("ignore") # ## Data Generation -# +# # We will focus on solving a specific PDE: the **Darcy Flow** equation. This is a second-order elliptic PDE given by: -# +# # $$ # -\nabla\cdot(k(x, y)\nabla u(x, y)) = f(x, y), \quad (x, y) \in D. # $$ -# +# # Here, $u$ represents the flow pressure, $k$ is the permeability field, and $f$ is the forcing function. The Darcy flow equation can be used to model various systems, including flow through porous media, elasticity in materials, and heat conduction. -# +# # In this tutorial, the domain $D$ is defined as a 2D unit square with Dirichlet boundary conditions. The dataset used is taken from the authors' original implementation in the referenced paper. -# In[2]: +# In[ ]: # download the dataset @@ -70,7 +68,7 @@ # Before diving into modeling, it's helpful to visualize some examples from the dataset. This will give us a better understanding of the input (permeability field) and the corresponding output (pressure field) that our model will learn to predict. -# In[4]: +# In[ ]: plt.subplot(1, 2, 1) @@ -84,7 +82,7 @@ # We now define the problem class for learning the Neural Operator. Since this task is essentially a supervised learning problem—where the goal is to learn a mapping from input functions to output solutions—we will use the `SupervisedProblem` class provided by **PINA**. -# In[6]: +# In[ ]: # make problem @@ -94,8 +92,8 @@ # ## Solving the Problem with a Feedforward Neural Network -# -# We begin by solving the Darcy flow problem using a standard Feedforward Neural Network (FNN). Since we are approaching this task with supervised learning, we will use the `SupervisedSolver` provided by **PINA** to train the model. +# +# We begin by solving the Darcy flow problem using a standard Feedforward Neural Network (FNN). Since we are approaching this task with supervised learning, we will use the `SupervisedSingleModelSolver` provided by **PINA** to train the model. # In[ ]: @@ -105,7 +103,7 @@ # make solver -solver = SupervisedSolver(problem=problem, model=model, use_lt=False) +solver = SupervisedSingleModelSolver(problem=problem, model=model, use_lt=False) # make the trainer and train trainer = Trainer( @@ -123,7 +121,7 @@ # The final loss is relatively high, indicating that the model might not be capturing the solution accurately. To better evaluate the model's performance, we can compute the error using the `LpLoss` metric. -# In[9]: +# In[ ]: from pina.loss import LpLoss @@ -148,7 +146,7 @@ # ## Solving the Problem with a Fourier Neural Operator -# +# # We will now solve the Darcy flow problem using a Fourier Neural Operator (FNO). Since we are learning a mapping between functions—i.e., an operator—this approach is more suitable and often yields better performance, as we will see. # In[ ]: @@ -168,7 +166,7 @@ # make solver -solver = SupervisedSolver(problem=problem, model=model, use_lt=False) +solver = SupervisedSingleModelSolver(problem=problem, model=model, use_lt=False) # make the trainer and train trainer = Trainer( @@ -185,10 +183,10 @@ # We can clearly observe that the final loss is significantly lower when using the FNO. Let's now evaluate its performance on the test set. -# +# # Note that the number of trainable parameters in the FNO is considerably higher compared to a `FeedForward` network. Therefore, we recommend using a GPU or TPU to accelerate training, especially when working with large datasets. -# In[11]: +# In[ ]: model = solver.model @@ -210,13 +208,13 @@ # As we can see, the loss is significantly lower with the Fourier Neural Operator! # ## What's Next? -# +# # Congratulations on completing the tutorial on solving the Darcy flow problem using **PINA**! There are many potential next steps you can explore: -# +# # 1. **Train the network longer or with different hyperparameters**: Experiment with different configurations of the neural network. You can try varying the number of layers, activation functions, or learning rates to improve accuracy. -# +# # 2. **Solve more complex problems**: The Darcy flow problem is just the beginning! Try solving other complex problems from the field of parametric PDEs. The original paper and **PINA** documentation offer many more examples to explore. -# +# # 3. **...and many more!**: There are countless directions to further explore. For instance, you could try to add physics informed learning! -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial6/tutorial.py b/tutorials/tutorial6/tutorial.py index 5e19177..eded0d5 100644 --- a/tutorials/tutorial6/tutorial.py +++ b/tutorials/tutorial6/tutorial.py @@ -2,18 +2,18 @@ # coding: utf-8 # # Tutorial: Building domains with PINA's `BaseDomain` class -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial6/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial6/tutorial.ipynb) +# # In this tutorial, we explore how to use and visualize PINA’s built-in geometric domains and how to construct custom ones. We will cover: # - Creating domains using `CartesianDomain`, `EllipsoidDomain`, and `SimplexDomain` # - Combining domains through set operations # - Defining custom domains # - Sampling from domains -# +# # We begin by importing the necessary modules. -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -39,12 +39,13 @@ BaseDomain, ) + # ## Built-in Geometries # We start with PINA’s built-in geometries. In particular, we define a Cartesian domain, an ellipsoid domain, and a simplex domain, all in two dimensions. Extending these constructions to higher dimensions follows the same principles. # The Cartesian domain represents rectangular regions, the ellipsoid domain models circular or elliptical shapes, and the simplex domain corresponds to triangular regions, which can be combined to form general polygonal domains. -# In[2]: +# In[ ]: # Carteisan, Ellipsoid, and Simplex domains @@ -67,7 +68,7 @@ # To visualize the shapes, we draw sample points from each domain using the `sample` method, available for all PINA domains. The argument `n` specifies how many points to generate. The optional `mode` argument selects the sampling strategy (e.g. "random"). The optional `variables` argument allows sampling over only a subset of variables; here, we sample all of them. -# In[3]: +# In[ ]: cartesian_samples = cartesian.sample(n=1000, mode="random") @@ -78,7 +79,7 @@ # We can inspect a few sampled points from each domain to get a better understanding of their structure. -# In[4]: +# In[ ]: print(f"Cartesian samples: {cartesian_samples[:4]}\n") @@ -89,7 +90,7 @@ # Now we are ready to visualize the sampled points! -# In[5]: +# In[ ]: # Basic plotting function @@ -109,7 +110,7 @@ def plot_scatter(ax, pts, title): # Similarly, we can sample and visualize boundary points by using the `partial` method. This method returns a new domain representing only the boundary of the original one, from which we can draw samples in exactly the same way. -# In[6]: +# In[ ]: # Boundary definitions @@ -145,7 +146,7 @@ def plot_scatter(ax, pts, title): # 2. `cartesian` and `ellipsoid_boundary` # 3. `ellipsoid` and `simplex_boundary` -# In[7]: +# In[ ]: union_cart_sim = Union([cartesian, simplex]) @@ -155,7 +156,7 @@ def plot_scatter(ax, pts, title): # And of course, we can sample points from these composite domains as well! -# In[8]: +# In[ ]: cart_sim_samples = union_cart_sim.sample(n=1000, mode="random") @@ -165,7 +166,7 @@ def plot_scatter(ax, pts, title): # We can now plot the samples to visualize each union. -# In[9]: +# In[ ]: fig, axs = plt.subplots(1, 3, figsize=(16, 4)) @@ -187,7 +188,7 @@ def plot_scatter(ax, pts, title): # Custom domains in PINA can be created by inheriting from the `BaseDomain` class, which provides the general structure shared by all domains. # We begin by defining the constructor: we specify the available sampling modes ("random", "grid", "chebyshev", "latin" or "lh"). Here, we default to random sampling. We also introduce the parameter `sample_surface`, which determines whether we sample the full heart or only its boundary. -# In[10]: +# In[ ]: class Heart(BaseDomain): @@ -209,7 +210,7 @@ def __init__(self, sample_surface=False): # The `is_inside` method checks whether a given point lies inside the domain. It receives the point to test and the boolean `check_border`, which indicates whether points on the boundary should be considered inside. -# In[11]: +# In[ ]: def is_inside(self, point, check_border=False): @@ -233,7 +234,7 @@ def is_inside(self, point, check_border=False): # The `sample` method closely resembles those of PINA’s built-in domains. We specify the number of points `n` and the sampling strategy mode. Note that for illustration we implement a very naive sampling approach, which is inefficient and not suitable for sampling boundary points for the heart domain! -# In[12]: +# In[ ]: def sample(self, n, mode="random"): @@ -262,7 +263,7 @@ def sample(self, n, mode="random"): # The `partial` method returns a new instance of the domain class that represents only its boundary. Implementing it is straightforward. -# In[13]: +# In[ ]: def partial(self): @@ -278,7 +279,7 @@ def partial(self): # We now have all the components needed to complete the `Heart` class. -# In[14]: +# In[ ]: # Linking the methods to the Heart class @@ -292,7 +293,7 @@ def partial(self): # Let’s generate the heart domain and draw sample points. -# In[15]: +# In[ ]: # Generate the heart domain @@ -304,7 +305,7 @@ def partial(self): # Finally, we visualize the samples. -# In[16]: +# In[ ]: fig, ax = plt.subplots() @@ -312,13 +313,13 @@ def partial(self): # ## What's Next? -# +# # In this tutorial, we introduced the construction of custom geometries and the use of domain operations to combine basic shapes. From here, you can experiment with a wide range of possibilities: -# +# # 1. **Build More Complex Geometries**: Combine multiple simple shapes using set operations to design sophisticated domains. -# +# # 2. **Optimize for Specific Applications**: Tailor domain definitions for tasks such as fluid flow, heat transfer, or structural mechanics. -# +# # 3. **...and many more!**: Implement new geometries using DomainInterface and push PINA’s capabilities further. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial7/tutorial.py b/tutorials/tutorial7/tutorial.py index 0fc00f0..da25ee0 100644 --- a/tutorials/tutorial7/tutorial.py +++ b/tutorials/tutorial7/tutorial.py @@ -2,15 +2,15 @@ # coding: utf-8 # # Tutorial: Inverse Problem Solving with Physics-Informed Neural Network -# -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial7/tutorial.ipynb) -# +# +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial7/tutorial.ipynb) +# # ## Introduction to the Inverse Problem -# -# This tutorial demonstrates how to solve an inverse Poisson problem using Physics-Informed Neural Networks (PINNs). -# +# +# This tutorial demonstrates how to solve an inverse Poisson problem using Physics-Informed Neural Networks. +# # The problem is defined as a Poisson equation with homogeneous boundary conditions: -# +# # \begin{equation} # \begin{cases} # \Delta u = e^{-2(x - \mu_1)^2 - 2(y - \mu_2)^2} \quad \text{in } \Omega, \\ @@ -18,20 +18,20 @@ # u(\mu_1, \mu_2) = \text{data} # \end{cases} # \end{equation} -# +# # Here, $\Omega$ is the square domain $[-2, 2] \times [-2, 2]$, and $\partial \Omega = \Gamma_1 \cup \Gamma_2 \cup \Gamma_3 \cup \Gamma_4$ represents the union of its boundaries. -# +# # This type of setup defines an *inverse problem*, which has two primary objectives: -# +# # - **Find the solution** $u$ that satisfies the Poisson equation, # - **Identify the unknown parameters** $(\mu_1, \mu_2)$ that best fit the given data (as described by the third equation in the system). -# +# # To tackle both objectives, we will define an `InverseProblem` using **PINA**. -# +# # Let's begin with the necessary imports: -# +# -# In[1]: +# In[ ]: ## routine needed to run the notebook on Google Colab @@ -45,12 +45,8 @@ get_ipython().system('pip install "pina-mathlab[tutorial]"') # get the data get_ipython().system('mkdir "data"') - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial7/data/pinn_solution_0.5_0.5" -O "data/pinn_solution_0.5_0.5"' - ) - get_ipython().system( - 'wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorial7/data/pts_0.5_0.5" -O "data/pts_0.5_0.5"' - ) + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial7/data/pinn_solution_0.5_0.5" -O "data/pinn_solution_0.5_0.5"') + get_ipython().system('wget "https://github.com/PINA-org/PINA-tutorials/raw/refs/heads/main/tutorials/tutorial7/data/pts_0.5_0.5" -O "data/pts_0.5_0.5"') import matplotlib.pyplot as plt import torch @@ -63,8 +59,9 @@ from pina.problem import SpatialProblem, InverseProblem from pina.operator import laplacian from pina.model import FeedForward -from pina.equation import Equation, FixedValue -from pina.solver import PINN +from pina.equation import Equation +from pina.equation.zoo import FixedValue +from pina.solver import PhysicsInformedSingleModelSolver from pina.domain import CartesianDomain from pina.optim import TorchOptimizer @@ -72,17 +69,17 @@ seed_everything(883) -# Next, we import the pre-saved data corresponding to the true parameter values $(\mu_1, \mu_2) = (0.5, 0.5)$. +# Next, we import the pre-saved data corresponding to the true parameter values $(\mu_1, \mu_2) = (0.5, 0.5)$. # These values represent the *optimal parameters* that we aim to recover through neural network training. -# +# # In particular, we load: -# +# # - `input` points — the spatial coordinates where observations are available, # - `target` points — the corresponding $u$ values (i.e., the solution evaluated at the `input` points). -# +# # This data will be used to guide the inverse problem and supervise the network’s prediction of the unknown parameters. -# In[2]: +# In[ ]: data_output = torch.load( @@ -92,13 +89,13 @@ # Next, let's visualize the data: -# +# # - We'll plot the data points, i.e., the spatial coordinates where measurements are available. # - We'll also display the reference solution corresponding to $(\mu_1, \mu_2) = (0.5, 0.5)$. -# +# # This serves as the ground truth or expected output that our neural network should learn to approximate through training. -# In[3]: +# In[ ]: points = data_input.extract(["x", "y"]).detach().numpy() @@ -111,13 +108,13 @@ # ## Inverse Problem Definition in PINA -# -# Next, we initialize the Poisson problem, which inherits from the `SpatialProblem` and `InverseProblem` classes. +# +# Next, we initialize the Poisson problem, which inherits from the `SpatialProblem` and `InverseProblem` classes. # In this step, we need to define all the variables and specify the domain in which our unknown parameters $(\mu_1, \mu_2)$ reside. -# +# # Note that the Laplace equation also takes these unknown parameters as inputs. These parameters will be treated as variables that the neural network will optimize during the training process, enabling it to learn the optimal values for $(\mu_1, \mu_2)$. -# In[4]: +# In[ ]: def laplace_equation(input_, output_, params_): @@ -161,9 +158,9 @@ class Poisson(SpatialProblem, InverseProblem): problem = Poisson() -# Next, we define the neural network model that will be used for solving the inverse problem. In this case, we use a simple FeedForeard model, but you could build one that imposes *hard constraints* on the boundary conditions, similar to the approach used in the [Wave tutorial](https://mathlab.github.io/PINA/tutorial3/tutorial.html) to have better performances! +# Next, we define the neural network model that will be used for solving the inverse problem. In this case, we use a simple FeedForeard model, but you could build one that imposes *hard constraints* on the boundary conditions, similar to the approach used in the [Wave tutorial](https://PINA-org.github.io/PINA-Tutorials/tutorial3/tutorial.html) to have better performances! -# In[5]: +# In[ ]: model = FeedForward( @@ -176,20 +173,20 @@ class Poisson(SpatialProblem, InverseProblem): # After that, we discretize the spatial domain. -# In[6]: +# In[ ]: problem.discretise_domain(20, "grid", domains=["D"]) problem.discretise_domain(1000, "random", domains="boundary") -# Here, we define a simple callback for the trainer. This callback is used to save the parameters predicted by the neural network during training. +# Here, we define a simple callback for the trainer. This callback is used to save the parameters predicted by the neural network during training. # The parameters are saved every 100 epochs as `torch` tensors in a specified directory (in our case, `tutorial_logs`). -# +# # The goal of this setup is to read the saved parameters after training and visualize their trend across the epochs. This allows us to monitor how the predicted parameters evolve throughout the training process. -# +# -# In[7]: +# In[ ]: # temporary directory for saving logs of training @@ -209,18 +206,18 @@ def on_train_epoch_end(self, trainer, __): ) -# Then, we define the `PINN` object and train the solver using the `Trainer` +# Then, we define the `PhysicsInformedSingleModelSolver` object and train the solver using the `Trainer` # In[ ]: max_epochs = 1500 -pinn = PINN( +solver = PhysicsInformedSingleModelSolver( problem, model, optimizer=TorchOptimizer(torch.optim.Adam, lr=0.005) ) # define the trainer for the solver trainer = Trainer( - solver=pinn, + solver=solver, accelerator="cpu", max_epochs=max_epochs, default_root_dir=tmp_dir, @@ -235,7 +232,7 @@ def on_train_epoch_end(self, trainer, __): # One can now see how the parameters vary during the training by reading the saved solution and plotting them. The plot shows that the parameters stabilize to their true value before reaching the epoch $1000$! -# In[9]: +# In[ ]: epochs_saved = range(99, max_epochs, 100) @@ -244,7 +241,7 @@ def on_train_epoch_end(self, trainer, __): params_torch = torch.load( "{}/parameters_epoch{}".format(tmp_dir, epoch), weights_only=False ) - for e, var in enumerate(pinn.problem.unknown_variables): + for e, var in enumerate(solver.problem.unknown_variables): parameters[i, e] = params_torch[var].data # Plot parameters @@ -260,13 +257,13 @@ def on_train_epoch_end(self, trainer, __): # ## What's Next? -# +# # We have covered the basic usage of PINNs for inverse problem modeling. Here are some possible directions for further exploration: -# -# 1. **Experiment with different Physics-Informed strategies**: Explore variations in PINN training techniques to improve performance or tackle different types of problems. -# +# +# 1. **Experiment with different Physics-Informed strategies**: Explore variations in Physics-Informed training techniques to improve performance or tackle different types of problems. +# # 2. **Apply to more complex problems**: Scale the approach to higher-dimensional or time-dependent inverse problems. -# +# # 3. **...and many more!**: The possibilities are endless, from integrating additional physical constraints to testing on real-world datasets. -# -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/). diff --git a/tutorials/tutorial8/tutorial.py b/tutorials/tutorial8/tutorial.py index 662db8e..d2dc00a 100644 --- a/tutorials/tutorial8/tutorial.py +++ b/tutorials/tutorial8/tutorial.py @@ -3,7 +3,7 @@ # # Tutorial: Reduced Order Modeling with POD-RBF and POD-NN Approaches for Fluid Dynamics # -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial8/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial8/tutorial.ipynb) # The goal of this tutorial is to demonstrate how to use the **PINA** library to apply a reduced-order modeling technique, as outlined in [1]. These methods share several similarities with machine learning approaches, as they focus on predicting the solution to differential equations, often parametric PDEs, in real-time. # @@ -289,7 +289,7 @@ def fit(self, p, x): # # 5. **...and many more!**: The potential applications of reduced order models are vast, ranging from material science simulations to real-time predictions in engineering applications. # -# For more information and advanced tutorials, refer to the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more information and advanced tutorials, refer to the [PINA Documentation](https://PINA-org.github.io/PINA/). # # ### References # 1. Rozza G., Stabile G., Ballarin F. (2022). Advanced Reduced Order Methods and Applications in Computational Fluid Dynamics, Society for Industrial and Applied Mathematics. diff --git a/tutorials/tutorial9/tutorial.py b/tutorials/tutorial9/tutorial.py index 63ad59d..0d54cec 100644 --- a/tutorials/tutorial9/tutorial.py +++ b/tutorials/tutorial9/tutorial.py @@ -3,7 +3,7 @@ # # Tutorial: Applying Periodic Boundary Conditions in PINNs to solve the Helmholtz Problem # -# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorial9/tutorial.ipynb) +# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/PINA-org/PINA-tutorials/blob/main/tutorials/tutorial9/tutorial.ipynb) # # This tutorial demonstrates how to solve a one-dimensional Helmholtz equation with periodic boundary conditions (PBC) using Physics-Informed Neural Networks (PINNs). # We will use standard Physics-Informed training, augmented with a periodic input expansion as introduced in [*An Expert’s Guide to Training Physics-Informed Neural Networks*](https://arxiv.org/abs/2308.08468). @@ -249,4 +249,4 @@ def solution(self, pts): # # 4. **...and many more!**: Extend to higher dimensions, test on other PDEs, or even develop custom embeddings tailored to your problem. # -# For more resources and tutorials, check out the [PINA Documentation](https://mathlab.github.io/PINA/). +# For more resources and tutorials, check out the [PINA Documentation](https://PINA-org.github.io/PINA/).