Co-authored-by: dario-coscia <dario-coscia@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9c60f616b7
commit
ef75f13bcb
11524
docs/source/tutorials/tutorial22/tutorial.html
Normal file
11524
docs/source/tutorials/tutorial22/tutorial.html
Normal file
File diff suppressed because one or more lines are too long
100
tutorials/tutorial16/tutorial.py
vendored
100
tutorials/tutorial16/tutorial.py
vendored
@@ -2,16 +2,16 @@
|
||||
# coding: utf-8
|
||||
|
||||
# # Tutorial: How to build a Problem in PINA
|
||||
#
|
||||
#
|
||||
# [](https://colab.research.google.com/github/mathLab/PINA/blob/master/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:
|
||||
#
|
||||
#
|
||||
# - **Building a Problem**: Learn how to construct a problem using the built-in PINA classes.
|
||||
# - **Generating Data for Physics-Informed Training**: Understand how to generate the necessary data for training.
|
||||
# - **Exploring the `problem.zoo` Module**: Get familiar with the `problem.zoo` module, which collects pre-built problems for easy use.
|
||||
#
|
||||
#
|
||||
# By the end of this tutorial, you'll be able to write **data-driven** or **differential problems** in **PINA** and prepare them for model training!
|
||||
|
||||
# In[1]:
|
||||
@@ -37,31 +37,31 @@ warnings.filterwarnings("ignore")
|
||||
# ## Build a PINA problem
|
||||
|
||||
# In **PINA**, defining a problem is done by creating a Python `class` that inherits from one or more problem classes, such as `SpatialProblem`, `TimeDependentProblem`, or `ParametricProblem`, depending on the nature of the problem. We refer to the `model` as the object that solves the problem, e.g., a **Neural Network**.
|
||||
#
|
||||
#
|
||||
# We can have two types of problems:
|
||||
# 1. ***Data-Driven Problems***: The model is trained using data, such as in classification networks or autoencoders.
|
||||
# 2. ***Physics-Driven Problems***: The model is trained using physical laws representing the problem, such as in **PINNs**.
|
||||
# Let's start by building the first type, the data driven type.
|
||||
#
|
||||
# Let's start by building the first type, the data driven type.
|
||||
#
|
||||
# ### Data driven modelling
|
||||
# In data-driven modelling, we always have an **input** and a **target**. The model's objective is to reconstruct the target from the input. Examples include:
|
||||
# - Image reconstruction (perturbed image as input, clear image as target)
|
||||
# - Classification (e.g., input: molecule, target: chemical properties)
|
||||
#
|
||||
#
|
||||
# To build a data-driven problem in **PINA**, you can inherit from the `AbstractProblem` class. Below is an example of a regression problem where the input is a scalar value `x` and the target is a scalar value `y`.
|
||||
#
|
||||
#
|
||||
# ```python
|
||||
# from pina.problem import AbstractProblem
|
||||
#
|
||||
#
|
||||
# class SupervisedProblem(AbstractProblem):
|
||||
#
|
||||
#
|
||||
# input_variables = ['x']
|
||||
# output_variables = ['y']
|
||||
#
|
||||
#
|
||||
# # 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``.
|
||||
#
|
||||
#
|
||||
# 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:
|
||||
|
||||
# In[2]:
|
||||
@@ -92,10 +92,10 @@ problem = SupervisedProblem()
|
||||
|
||||
|
||||
# You can define as many conditions as needed, and the model will attempt to minimize all of them simultaneously! You can access the data in various ways:
|
||||
#
|
||||
#
|
||||
# - `problem.conditions['<condition name>'].input`, `problem.conditions['<condition name>'].target` – Access the input and output data for the specified condition `<condition name>`.
|
||||
# - `problem.input_pts` – Access the input points for all conditions.
|
||||
#
|
||||
#
|
||||
# To ensure that the problem is ready, you can check if all domains have been discretized, meaning all conditions have input points available to pass to the model:
|
||||
|
||||
# In[3]:
|
||||
@@ -109,7 +109,7 @@ problem.are_all_domains_discretised
|
||||
|
||||
# ### 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):
|
||||
#
|
||||
#
|
||||
# $$
|
||||
# \begin{equation}
|
||||
# \begin{cases}
|
||||
@@ -118,39 +118,39 @@ problem.are_all_domains_discretised
|
||||
# \end{cases}
|
||||
# \end{equation}
|
||||
# $$
|
||||
#
|
||||
#
|
||||
# with the analytical solution $u(x) = e^x$. This problem is a spatial problem because the ODE depends only on the spatial variable $x\in(0,1)$. In PINA, differential problems are categorized by their nature, e.g.:
|
||||
# * `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.
|
||||
#
|
||||
#
|
||||
# In our case, the physical ODE inherits from the `SpatialProblem` class, since only spatial variables define the ODE.
|
||||
#
|
||||
#
|
||||
# ```python
|
||||
# class SimpleODE(SpatialProblem):
|
||||
#
|
||||
#
|
||||
# output_variables = ['u']
|
||||
# spatial_domain = CartesianDomain{'x': [0, 1]})
|
||||
#
|
||||
#
|
||||
# # other stuff ...
|
||||
# ```
|
||||
#
|
||||
#
|
||||
# What if our equation is was also time-dependent, e.g. Partial Differential Equations (PDE)? In this case, our `class` will inherit from both `SpatialProblem` and `TimeDependentProblem`:
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
# ```python
|
||||
# class TimeSpaceODE(SpatialProblem, TimeDependentProblem):
|
||||
#
|
||||
#
|
||||
# output_variables = ["u"]
|
||||
# spatial_domain = CartesianDomain({"x": [0, 1]})
|
||||
# temporal_domain = CartesianDomain({"t": [0, 1]})
|
||||
#
|
||||
#
|
||||
# # 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:
|
||||
#
|
||||
#
|
||||
# | Problem Type | Required Domain |
|
||||
# |-------------------------|--------------------------------|
|
||||
# | `SpatialProblem` | `spatial_domain` |
|
||||
@@ -203,25 +203,25 @@ class SimpleODE(SpatialProblem):
|
||||
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://mathlab.github.io/PINA/_tutorial.html#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:
|
||||
# - 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.
|
||||
# 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:
|
||||
# - 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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# Finally, it's possible to define a `solution` function, which can be useful if we want to plot the results and see how the real solution compares to the expected (true) solution. Notice that the `solution` function is a method of the `Problem` class, but it is not mandatory for problem definition.
|
||||
#
|
||||
#
|
||||
|
||||
# ## Generate data for Physical Problems
|
||||
#
|
||||
#
|
||||
# When training physics based models, data can come in form of direct numerical simulation results (tensors, graph), or points in the domains which need to be sampled. In case we perform unsupervised learning, we just need the collocation points for training, i.e. points where we want to evaluate the neural network. Sampling point in **PINA** is very easy. But first, let's check if the domains are dicsretized by using the `are_all_domains_discretised` method.
|
||||
|
||||
# In[5]:
|
||||
@@ -232,7 +232,7 @@ problem.are_all_domains_discretised
|
||||
|
||||
|
||||
# This is false becase the input points are not available (we need to discretize!). If you call `problem.input_points` at this stage you will get an error due to point missing in the condition.
|
||||
#
|
||||
#
|
||||
# ```bash
|
||||
# >>> problem.input_pts
|
||||
# ```
|
||||
@@ -241,13 +241,13 @@ problem.are_all_domains_discretised
|
||||
# KeyError Traceback (most recent call last)
|
||||
# Cell In[32], line 1
|
||||
# ----> 1 problem.input_pts
|
||||
#
|
||||
#
|
||||
# File ~/GitHub/PINA/pina/problem/abstract_problem.py:78, in AbstractProblem.input_pts(self)
|
||||
# 76 to_return[cond_name] = cond.input
|
||||
# 77 elif hasattr(cond, "domain"):
|
||||
# ---> 78 to_return[cond_name] = self._discretised_domains[cond.domain]
|
||||
# 79 return to_return
|
||||
#
|
||||
#
|
||||
# KeyError: 'x0'
|
||||
# ```
|
||||
|
||||
@@ -300,9 +300,9 @@ plt.legend()
|
||||
|
||||
|
||||
# ## 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)!
|
||||
#
|
||||
#
|
||||
# Let's see now a physics based example, the advection equation
|
||||
|
||||
# In[10]:
|
||||
@@ -324,13 +324,13 @@ print(
|
||||
|
||||
|
||||
# ## What's Next?
|
||||
#
|
||||
#
|
||||
# Congratulations on completing the introductory tutorial of **PINA** problems! There are several directions you can explore next:
|
||||
#
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# 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/).
|
||||
|
||||
118
tutorials/tutorial21/tutorial.py
vendored
118
tutorials/tutorial21/tutorial.py
vendored
@@ -2,15 +2,15 @@
|
||||
# coding: utf-8
|
||||
|
||||
# # Tutorial: Introductory Tutorial: Neural Operator Learning with PINA
|
||||
#
|
||||
#
|
||||
# [](https://colab.research.google.com/github/mathLab/PINA/blob/master/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.
|
||||
#
|
||||
#
|
||||
# Let's start by importing the useful modules:
|
||||
|
||||
# In[ ]:
|
||||
@@ -40,55 +40,55 @@ 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]:
|
||||
@@ -123,59 +123,59 @@ plt.grid(True)
|
||||
|
||||
|
||||
# ## 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:
|
||||
#
|
||||
#
|
||||
# <p align="center">
|
||||
# <img src="http://raw.githubusercontent.com/mathLab/PINA/master/tutorials/static/neural_operator.png" alt="Neural Operators" width="800"/>
|
||||
# </p>
|
||||
#
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# - **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.
|
||||
#
|
||||
# - **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)**:
|
||||
#
|
||||
# - **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.
|
||||
#
|
||||
# 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).
|
||||
#
|
||||
#
|
||||
# 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:
|
||||
#
|
||||
#
|
||||
# 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]:
|
||||
|
||||
@@ -236,9 +236,9 @@ model = KernelNeuralOperator(
|
||||
|
||||
|
||||
# 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](http://mathlab.github.io/PINA/_rst/tutorials/tutorial18/tutorial.html) for an overview of Solvers in PINA — have a look if you're interested!**
|
||||
|
||||
# In[24]:
|
||||
@@ -265,7 +265,7 @@ _ = 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]:
|
||||
@@ -288,15 +288,15 @@ plt.grid(True)
|
||||
|
||||
|
||||
# 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/).
|
||||
|
||||
47
tutorials/tutorial22/tutorial.ipynb
vendored
47
tutorials/tutorial22/tutorial.ipynb
vendored
@@ -40,7 +40,10 @@
|
||||
"import torch\n",
|
||||
"from torch import nn\n",
|
||||
"from torch_geometric.nn import GMMConv\n",
|
||||
"from torch_geometric.data import Data, Batch # alternatively, from pina.graph import Graph, LabelBatch\n",
|
||||
"from torch_geometric.data import (\n",
|
||||
" Data,\n",
|
||||
" Batch,\n",
|
||||
") # alternatively, from pina.graph import Graph, LabelBatch\n",
|
||||
"from torch_geometric.utils import to_dense_batch\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
@@ -105,17 +108,17 @@
|
||||
"# u, params -> solution field, parameters\n",
|
||||
"\n",
|
||||
"data = torch.load(\"holed_poisson.pt\")\n",
|
||||
"x = data['x']\n",
|
||||
"y = data['y']\n",
|
||||
"edge_index = data['edge_index']\n",
|
||||
"u = data['u']\n",
|
||||
"triang = data['triang']\n",
|
||||
"params = data['mu']\n",
|
||||
"x = data[\"x\"]\n",
|
||||
"y = data[\"y\"]\n",
|
||||
"edge_index = data[\"edge_index\"]\n",
|
||||
"u = data[\"u\"]\n",
|
||||
"triang = data[\"triang\"]\n",
|
||||
"params = data[\"mu\"]\n",
|
||||
"\n",
|
||||
"# simple plot\n",
|
||||
"plt.figure(figsize=(4, 4))\n",
|
||||
"plt.tricontourf(x[:, 10], y[:, 10], triang, u[:, 10], 100, cmap='jet')\n",
|
||||
"plt.scatter(params[10, 0], params[10, 1], c='r', marker=\"x\", s=100)\n",
|
||||
"plt.tricontourf(x[:, 10], y[:, 10], triang, u[:, 10], 100, cmap=\"jet\")\n",
|
||||
"plt.scatter(params[10, 0], params[10, 1], c=\"r\", marker=\"x\", s=100)\n",
|
||||
"plt.tight_layout()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
@@ -267,7 +270,7 @@
|
||||
" # edge attributes and weights\n",
|
||||
" ei, ej = pos[edge_index[0]], pos[edge_index[1]] # [num_edges, 2]\n",
|
||||
" edge_attr = torch.abs(ej - ei) # relative offsets\n",
|
||||
" edge_weight = edge_attr.norm(p=2, dim=1, keepdim=True) # Euclidean distance\n",
|
||||
" edge_weight = edge_attr.norm(p=2, dim=1, keepdim=True) # Euclidean distance\n",
|
||||
" # node features (solution values)\n",
|
||||
" node_features = u[:, g].unsqueeze(-1) # [num_nodes, 1]\n",
|
||||
" # build PyG graph\n",
|
||||
@@ -327,7 +330,11 @@
|
||||
" hidden_channels=[1, 1], bottleneck=8, input_size=1352, ffn=200, act=nn.ELU\n",
|
||||
")\n",
|
||||
"interpolation_network = FeedForward(\n",
|
||||
" input_dimensions=2, output_dimensions=8, n_layers=2, inner_size=200, func=nn.Tanh\n",
|
||||
" input_dimensions=2,\n",
|
||||
" output_dimensions=8,\n",
|
||||
" n_layers=2,\n",
|
||||
" inner_size=200,\n",
|
||||
" func=nn.Tanh,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
@@ -361,6 +368,7 @@
|
||||
" output, target, reduction=self.reduction\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the solver\n",
|
||||
"solver = ReducedOrderModelSolver(\n",
|
||||
" problem=problem,\n",
|
||||
@@ -393,7 +401,7 @@
|
||||
" max_epochs=300,\n",
|
||||
" train_size=0.3,\n",
|
||||
" val_size=0.7,\n",
|
||||
" test_size=0.,\n",
|
||||
" test_size=0.0,\n",
|
||||
" shuffle=True,\n",
|
||||
")\n",
|
||||
"trainer.train()"
|
||||
@@ -481,10 +489,10 @@
|
||||
" vmin=vmin,\n",
|
||||
" vmax=vmax,\n",
|
||||
")\n",
|
||||
"plt.title('GCA-ROM')\n",
|
||||
"plt.title(\"GCA-ROM\")\n",
|
||||
"plt.colorbar()\n",
|
||||
"plt.subplot(1, 3, 2)\n",
|
||||
"plt.title('True')\n",
|
||||
"plt.title(\"True\")\n",
|
||||
"plt.tricontourf(\n",
|
||||
" x[:, idx_to_plot],\n",
|
||||
" y[:, idx_to_plot],\n",
|
||||
@@ -497,8 +505,15 @@
|
||||
")\n",
|
||||
"plt.colorbar()\n",
|
||||
"plt.subplot(1, 3, 3)\n",
|
||||
"plt.title('Square Error')\n",
|
||||
"plt.tricontourf(x[:, idx_to_plot], y[:, idx_to_plot], triang, (u-out).pow(2)[:, idx_to_plot], 100, cmap='jet')\n",
|
||||
"plt.title(\"Square Error\")\n",
|
||||
"plt.tricontourf(\n",
|
||||
" x[:, idx_to_plot],\n",
|
||||
" y[:, idx_to_plot],\n",
|
||||
" triang,\n",
|
||||
" (u - out).pow(2)[:, idx_to_plot],\n",
|
||||
" 100,\n",
|
||||
" cmap=\"jet\",\n",
|
||||
")\n",
|
||||
"plt.colorbar()\n",
|
||||
"plt.ticklabel_format()\n",
|
||||
"plt.show()"
|
||||
|
||||
409
tutorials/tutorial22/tutorial.py
vendored
Normal file
409
tutorials/tutorial22/tutorial.py
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# # Tutorial: Reduced Order Model with Graph Neural Networks
|
||||
#
|
||||
# [](https://colab.research.google.com/github/mathLab/PINA/blob/master/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.
|
||||
#
|
||||
# In this tutorial, we will demonstrate a typical use case of **PINA** for Reduced Order Modelling using Graph Convolutional Neural Network. The tutorial is largely inspired by the paper [A graph convolutional autoencoder approach to model order reduction for parametrized PDEs](https://www.sciencedirect.com/science/article/pii/S0021999124000111).
|
||||
#
|
||||
# Let's start by importing the useful modules:
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
## routine needed to run the notebook on Google Colab
|
||||
try:
|
||||
import google.colab
|
||||
|
||||
IN_COLAB = True
|
||||
except:
|
||||
IN_COLAB = False
|
||||
if IN_COLAB:
|
||||
get_ipython().system('pip install "pina-mathlab[tutorial]"')
|
||||
get_ipython().system('wget "https://github.com/mathLab/PINA/raw/refs/heads/master/tutorials/tutorial22/holed_poisson.pt" -O "holed_poisson.pt"')
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch_geometric.nn import GMMConv
|
||||
from torch_geometric.data import (
|
||||
Data,
|
||||
Batch,
|
||||
) # alternatively, from pina.graph import Graph, LabelBatch
|
||||
from torch_geometric.utils import to_dense_batch
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
from pina import Trainer
|
||||
from pina.model import FeedForward
|
||||
from pina.optim import TorchOptimizer
|
||||
from pina.solver import ReducedOrderModelSolver
|
||||
from pina.problem.zoo import SupervisedProblem
|
||||
|
||||
|
||||
# ## Data Generation
|
||||
#
|
||||
# In this tutorial, we will focus on solving the parametric **Poisson** equation, a linear PDE. The equation is given by:
|
||||
#
|
||||
# $$
|
||||
# \begin{cases}
|
||||
# -\frac{1}{10}\Delta u = 1, &\Omega(\boldsymbol{\mu}),\\
|
||||
# u = 0, &\partial \Omega(\boldsymbol{\mu}).
|
||||
# \end{cases}
|
||||
# $$
|
||||
#
|
||||
# In this equation, $\Omega(\boldsymbol{\mu}) = [0, 1]\times[0,1] \setminus [\mu_1, \mu_2]\times[\mu_1+0.3, \mu_2+0.3]$ represents the spatial domain characterized by a parametrized hole defined via $\boldsymbol{\mu} = (\mu_1, \mu_2) \in \mathbb{P} = [0.1, 0.6]\times[0.1, 0.6]$. Thus, the geometrical parameters define the left bottom corner of a square obstacle of dimension $0.3$. The problem is coupled with homogenous Dirichlet conditions on both internal and external boundaries. In this setting, $u(\mathbf{x}, \boldsymbol{\mu})\in \mathbb{R}$ is the value of the function $u$ at each point in space for a specific parameter $\boldsymbol{\mu}$.
|
||||
#
|
||||
# We have already generated data for different parameters. The dataset is obtained via $\mathbb{P}^1$ FE method, and an equispaced sampling with 11 points in each direction of the parametric space.
|
||||
#
|
||||
# The goal is to build a Reduced Order Model that given a new parameter $\boldsymbol{\mu}^*$, is able to get the solution $u$ *for any discretization* $\mathbf{x}$. To this end, we will train a Graph Convolutional Autoencoder Reduced Order Model (GCA-ROM), as presented in [A graph convolutional autoencoder approach to model order reduction for parametrized PDEs](https://www.sciencedirect.com/science/article/pii/S0021999124000111). We will cover the architecture details later, but for now, let’s start by importing the data.
|
||||
#
|
||||
# **Note:**
|
||||
# The numerical integration is obtained using a finite element method with the [RBniCS library](https://www.rbnicsproject.org/).
|
||||
|
||||
# In[21]:
|
||||
|
||||
|
||||
# === load the data ===
|
||||
# x, y -> spatial discretization
|
||||
# edge_index, triang -> connectivity matrix, triangulation
|
||||
# u, params -> solution field, parameters
|
||||
|
||||
data = torch.load("holed_poisson.pt")
|
||||
x = data["x"]
|
||||
y = data["y"]
|
||||
edge_index = data["edge_index"]
|
||||
u = data["u"]
|
||||
triang = data["triang"]
|
||||
params = data["mu"]
|
||||
|
||||
# simple plot
|
||||
plt.figure(figsize=(4, 4))
|
||||
plt.tricontourf(x[:, 10], y[:, 10], triang, u[:, 10], 100, cmap="jet")
|
||||
plt.scatter(params[10, 0], params[10, 1], c="r", marker="x", s=100)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
|
||||
# ## Graph-Based Reduced Order Modeling
|
||||
#
|
||||
# In this problem, the geometry of the spatial domain is **unstructured**, meaning that classical grid-based methods (e.g., CNNs) are not well suited. Instead, we represent the mesh as a **graph**, where nodes correspond to spatial degrees of freedom and edges represent connectivity. This makes **Graph Neural Networks (GNNs)**, and in particular **Graph Convolutional Networks (GCNs)**, a natural choice to process the data.
|
||||
#
|
||||
# <p align="center">
|
||||
# <img src="http://raw.githubusercontent.com/mathLab/PINA/master/tutorials/static/gca_off_on_3_pina.png" alt="GCA-ROM" width="800"/>
|
||||
# </p>
|
||||
#
|
||||
# To reduce computational complexity while preserving accuracy, we employ a **Reduced Order Modeling (ROM)** strategy (see picture above). The idea is to map high-dimensional simulation data $u(\mathbf{x}, \boldsymbol{\mu})$ to a compact **latent space** using a **graph convolutional encoder**, and then reconstruct it back via a **decoder** (offline phase). The latent representation captures the essential features of the solution manifold. Moreover, we can learn a **parametric map** $\mathcal{M}$ from the parameter space $\boldsymbol{\mu}$ directly into the latent space, enabling predictions for new unseen parameters.
|
||||
#
|
||||
# Formally, the autoencoder consists of an **encoder** $\mathcal{E}$, a **decoder** $\mathcal{D}$, and a **parametric mapping** $\mathcal{M}$:
|
||||
# $$
|
||||
# z = \mathcal{E}(u(\mathbf{x}, \boldsymbol{\mu})),
|
||||
# \quad
|
||||
# \hat{u}(\mathbf{x}, \boldsymbol{\mu}) = \mathcal{D}(z),
|
||||
# \quad
|
||||
# \hat{z} = \mathcal{M}(\boldsymbol{\mu}),
|
||||
# $$
|
||||
# where $z \in \mathbb{R}^r$ is the latent representation with $r \ll N$ (the number of degrees of freedom) and the **hat notation** ($\hat{u}, \hat{z}$) indicates *learned or approximated quantities*.
|
||||
#
|
||||
# The training objective balances two terms:
|
||||
# 1. **Reconstruction loss**: ensuring the autoencoder can faithfully reconstruct $u$ from $z$.
|
||||
# 2. **Latent consistency loss**: enforcing that the parametric map $\mathcal{M}(\boldsymbol{\mu})$ approximates the encoder’s latent space.
|
||||
#
|
||||
# The combined loss function is:
|
||||
# $$
|
||||
# \mathcal{L}(\theta) = \frac{1}{N} \sum_{i=1}^N
|
||||
# \big\| u(\mathbf{x}, \boldsymbol{\mu}_i) -
|
||||
# \mathcal{D}\!\big(\mathcal{E}(u(\mathbf{x}, \boldsymbol{\mu}_i))\big)
|
||||
# \big\|_2^2
|
||||
# \;+\; \frac{1}{N} \sum_{i=1}^N
|
||||
# \big\| \mathcal{E}(u(\mathbf{x}, \boldsymbol{\mu}_i)) - \mathcal{M}(\boldsymbol{\mu}_i) \big\|_2^2.
|
||||
# $$
|
||||
# This framework leverages the expressive power of GNNs for unstructured geometries and the efficiency of ROMs for handling parametric PDEs.
|
||||
#
|
||||
# We will now build the autoencoder network, which is a `nn.Module` with two methods: `encode` and `decode`.
|
||||
#
|
||||
|
||||
# In[3]:
|
||||
|
||||
|
||||
class GraphConvolutionalAutoencoder(nn.Module):
|
||||
def __init__(
|
||||
self, hidden_channels, bottleneck, input_size, ffn, act=nn.ELU
|
||||
):
|
||||
super().__init__()
|
||||
self.hidden_channels, self.input_size = hidden_channels, input_size
|
||||
self.act = act()
|
||||
self.current_graph = None
|
||||
|
||||
# Encoder GMM layers
|
||||
self.fc_enc1 = nn.Linear(input_size * hidden_channels[-1], ffn)
|
||||
self.fc_enc2 = nn.Linear(ffn, bottleneck)
|
||||
self.encoder_convs = nn.ModuleList(
|
||||
[
|
||||
GMMConv(
|
||||
hidden_channels[i],
|
||||
hidden_channels[i + 1],
|
||||
dim=1,
|
||||
kernel_size=5,
|
||||
)
|
||||
for i in range(len(hidden_channels) - 1)
|
||||
]
|
||||
)
|
||||
# Decoder GMM layers
|
||||
self.fc_dec1 = nn.Linear(bottleneck, ffn)
|
||||
self.fc_dec2 = nn.Linear(ffn, input_size * hidden_channels[-1])
|
||||
self.decoder_convs = nn.ModuleList(
|
||||
[
|
||||
GMMConv(
|
||||
hidden_channels[-i - 1],
|
||||
hidden_channels[-i - 2],
|
||||
dim=1,
|
||||
kernel_size=5,
|
||||
)
|
||||
for i in range(len(hidden_channels) - 1)
|
||||
]
|
||||
)
|
||||
|
||||
def encode(self, data):
|
||||
self.current_graph = data
|
||||
x = data.x
|
||||
h = x
|
||||
for conv in self.encoder_convs:
|
||||
x = self.act(conv(x, data.edge_index, data.edge_weight) + h)
|
||||
x = x.reshape(
|
||||
data.num_graphs, self.input_size * self.hidden_channels[-1]
|
||||
)
|
||||
return self.fc_enc2(self.act(self.fc_enc1(x)))
|
||||
|
||||
def decode(self, z, decoding_graph=None):
|
||||
data = decoding_graph or self.current_graph
|
||||
x = self.act(self.fc_dec2(self.act(self.fc_dec1(z)))).reshape(
|
||||
data.num_graphs * self.input_size, self.hidden_channels[-1]
|
||||
)
|
||||
h = x
|
||||
for i, conv in enumerate(self.decoder_convs):
|
||||
x = conv(x, data.edge_index, data.edge_weight) + h
|
||||
if i != len(self.decoder_convs) - 1:
|
||||
x = self.act(x)
|
||||
return x
|
||||
|
||||
|
||||
# Great! We now need to build the graph structure (a PyTorch Geometric `Data` object) from the numerical solver outputs.
|
||||
#
|
||||
# The solver provides the solution values $u(\mathbf{x}, \boldsymbol{\mu})$ for each parameter instance $\boldsymbol{\mu}$, along with the node coordinates $(x, y)$ of the unstructured mesh. Because the geometry is not defined on a regular grid, we naturally represent the mesh as a graph:
|
||||
#
|
||||
# - **Nodes** correspond to spatial points in the mesh. Each node stores the **solution value** $u$ at that point as a feature.
|
||||
# - **Edges** represent mesh connectivity. For each edge, we compute:
|
||||
# - **Edge attributes**: the relative displacement vector between the two nodes.
|
||||
# - **Edge weights**: the Euclidean distance between the connected nodes.
|
||||
# - **Positions** store the physical $(x, y)$ coordinates of the nodes.
|
||||
#
|
||||
# For each parameter realization $\boldsymbol{\mu}_i$, we therefore construct a PyTorch Geometric `Data` object:
|
||||
#
|
||||
|
||||
# In[4]:
|
||||
|
||||
|
||||
# number of nodes and number of graphs (parameter realizations)
|
||||
num_nodes, num_graphs = u.shape
|
||||
|
||||
graphs = []
|
||||
for g in range(num_graphs):
|
||||
# node positions
|
||||
pos = torch.stack([x[:, g], y[:, g]], dim=1) # shape [num_nodes, 2]
|
||||
# edge attributes and weights
|
||||
ei, ej = pos[edge_index[0]], pos[edge_index[1]] # [num_edges, 2]
|
||||
edge_attr = torch.abs(ej - ei) # relative offsets
|
||||
edge_weight = edge_attr.norm(p=2, dim=1, keepdim=True) # Euclidean distance
|
||||
# node features (solution values)
|
||||
node_features = u[:, g].unsqueeze(-1) # [num_nodes, 1]
|
||||
# build PyG graph
|
||||
graphs.append(
|
||||
Data(
|
||||
x=node_features,
|
||||
edge_index=edge_index,
|
||||
edge_weight=edge_weight,
|
||||
edge_attr=edge_attr,
|
||||
pos=pos,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# ## Training with PINA
|
||||
#
|
||||
# Everything is now ready! 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 parameter tensor $\boldsymbol{\mu}$ describing each scenario.
|
||||
# - **Output**: the corresponding graph structure (PyTorch Geometric `Data` object) that we aim to reconstruct.
|
||||
|
||||
# In[5]:
|
||||
|
||||
|
||||
problem = SupervisedProblem(params, graphs)
|
||||
|
||||
|
||||
# Next, we build the **autoencoder network** and the **interpolation network**.
|
||||
#
|
||||
# - The **Graph Convolutional Autoencoder (GCA)** encodes the high-dimensional graph data into a compact latent space and reconstructs the graphs from this latent representation.
|
||||
# - The **interpolation network** (or parametric map) learns to map a new parameter $\boldsymbol{\mu}^*$ directly into the latent space, enabling the model to predict solutions for unseen parameter instances without running the full encoder.
|
||||
|
||||
# In[6]:
|
||||
|
||||
|
||||
reduction_network = GraphConvolutionalAutoencoder(
|
||||
hidden_channels=[1, 1], bottleneck=8, input_size=1352, ffn=200, act=nn.ELU
|
||||
)
|
||||
interpolation_network = FeedForward(
|
||||
input_dimensions=2,
|
||||
output_dimensions=8,
|
||||
n_layers=2,
|
||||
inner_size=200,
|
||||
func=nn.Tanh,
|
||||
)
|
||||
|
||||
|
||||
# Finally, we will use the [`ReducedOrderModelSolver`](https://mathlab.github.io/PINA/_rst/solver/supervised_solver/reduced_order_model.html#pina.solver.supervised_solver.reduced_order_model.ReducedOrderModelSolver) to perform the training, as discussed earlier.
|
||||
#
|
||||
# This solver requires two components:
|
||||
# - an **interpolation network**, which maps parameters $\boldsymbol{\mu}$ to the latent space, and
|
||||
# - a **reduction network**, which in our case is the **autoencoder** that compresses and reconstructs the graph data.
|
||||
|
||||
# In[7]:
|
||||
|
||||
|
||||
# This loss handles both Data and Torch.Tensors
|
||||
class CustomMSELoss(nn.MSELoss):
|
||||
def forward(self, output, target):
|
||||
if isinstance(output, Data):
|
||||
output = output.x
|
||||
if isinstance(target, Data):
|
||||
target = target.x
|
||||
return torch.nn.functional.mse_loss(
|
||||
output, target, reduction=self.reduction
|
||||
)
|
||||
|
||||
|
||||
# Define the solver
|
||||
solver = ReducedOrderModelSolver(
|
||||
problem=problem,
|
||||
reduction_network=reduction_network,
|
||||
interpolation_network=interpolation_network,
|
||||
use_lt=False,
|
||||
loss=CustomMSELoss(),
|
||||
optimizer=TorchOptimizer(torch.optim.Adam, lr=0.001, weight_decay=1e-05),
|
||||
)
|
||||
|
||||
|
||||
# Training is performed as usual using the **`Trainer`** API. In this tutorial, we will use only **30% of the data** for training, and only $300$ epochs of training to illustrate the workflow.
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
trainer = Trainer(
|
||||
solver=solver,
|
||||
accelerator="cpu",
|
||||
max_epochs=300,
|
||||
train_size=0.3,
|
||||
val_size=0.7,
|
||||
test_size=0.0,
|
||||
shuffle=True,
|
||||
)
|
||||
trainer.train()
|
||||
|
||||
|
||||
# Once the model is trained, we can test the reconstruction by following two steps:
|
||||
#
|
||||
# 1. **Interpolate**: Use the `interpolation_network` to map a new parameter $\boldsymbol{\mu}^*$ to the latent space.
|
||||
# 2. **Decode**: Pass the interpolated latent vector through the autoencoder (`reduction_network`) to reconstruct the corresponding graph data.
|
||||
|
||||
# In[9]:
|
||||
|
||||
|
||||
# interpolate
|
||||
z = interpolation_network(params)
|
||||
|
||||
# decode
|
||||
batch = Batch.from_data_list(graphs)
|
||||
out = reduction_network.decode(z, decoding_graph=batch)
|
||||
out, _ = to_dense_batch(out, batch.batch)
|
||||
out = out.squeeze(-1).T.detach()
|
||||
|
||||
|
||||
# Let's compute the total error, and plot a sample solution:
|
||||
|
||||
# In[11]:
|
||||
|
||||
|
||||
# compute error
|
||||
l2_error = (torch.norm(out - u, dim=0) / torch.norm(u, dim=0)).mean()
|
||||
print(f"L2 relative error {l2_error:.2%}")
|
||||
|
||||
# plot solution
|
||||
idx_to_plot = 42
|
||||
# Determine min and max values for color scaling
|
||||
vmin = min(out[:, idx_to_plot].min(), u[:, idx_to_plot].min())
|
||||
vmax = max(out[:, idx_to_plot].max(), u[:, idx_to_plot].max())
|
||||
plt.figure(figsize=(16, 4))
|
||||
plt.subplot(1, 3, 1)
|
||||
plt.tricontourf(
|
||||
x[:, idx_to_plot],
|
||||
y[:, idx_to_plot],
|
||||
triang,
|
||||
out[:, idx_to_plot],
|
||||
100,
|
||||
cmap="jet",
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
)
|
||||
plt.title("GCA-ROM")
|
||||
plt.colorbar()
|
||||
plt.subplot(1, 3, 2)
|
||||
plt.title("True")
|
||||
plt.tricontourf(
|
||||
x[:, idx_to_plot],
|
||||
y[:, idx_to_plot],
|
||||
triang,
|
||||
u[:, idx_to_plot],
|
||||
100,
|
||||
cmap="jet",
|
||||
vmin=vmin,
|
||||
vmax=vmax,
|
||||
)
|
||||
plt.colorbar()
|
||||
plt.subplot(1, 3, 3)
|
||||
plt.title("Square Error")
|
||||
plt.tricontourf(
|
||||
x[:, idx_to_plot],
|
||||
y[:, idx_to_plot],
|
||||
triang,
|
||||
(u - out).pow(2)[:, idx_to_plot],
|
||||
100,
|
||||
cmap="jet",
|
||||
)
|
||||
plt.colorbar()
|
||||
plt.ticklabel_format()
|
||||
plt.show()
|
||||
|
||||
|
||||
# Nice! We can see that the network is correctly learning the solution operator, and the workflow was very straightforward.
|
||||
#
|
||||
# You may notice that the network outputs are not as smooth as the actual solution. Don’t worry — training for longer (e.g., ~5000 epochs) will produce a smoother, more accurate reconstruction.
|
||||
#
|
||||
# ## What's Next?
|
||||
#
|
||||
# Congratulations on completing the introductory tutorial on **Graph Convolutional Reduced Order Modeling**! Now that you have a solid foundation, here are a few directions to explore:
|
||||
#
|
||||
# 1. **Experiment with Training Duration** — Try different training durations and adjust the network architecture to optimize performance. Explore different integral kernels and observe how the results vary.
|
||||
#
|
||||
# 2. **Explore Physical Constraints** — Incorporate physics-informed terms or constraints during training to improve model generalization and ensure physically consistent predictions.
|
||||
#
|
||||
# 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/).
|
||||
Reference in New Issue
Block a user