Getting Started
Usage

Usage

Here you can find the instructions to use PINNacle.

Define a PDE system

First, you need to define a PDE system. We have provided 20+ cases across many different fields in the PINNacle.src.pde module. For example, to define one of the Poisson equation cases, Poisson2D-C, you can use the Poisson2D_Classic class:

from src.pde.poisson import Poisson2D_Classic
 
pde = Poisson2D_Classic()

Many PDE problems have parameters that can be changed. For example, the Poisson2D-C case has a parameter scale that changes the scale of the domain. You can change the value of this parameter as follows:

pde = Poisson2D_Classic(scale=8)

You can also define your own PDE problem by subclassing the BasePDE class. See this page for more details.

Define a neural network

Next, you need to define a neural network. We have provided a few neural network architectures in the PINNacle.src.model module. But you can also define your own neural network architecture as long as it is a subclass of the torch.nn.Module class. For example, to define a fully-connected neural network with 5 hidden layers, you can use the one provided by DeepXDE:

import deepxde as dde
 
net = dde.nn.FNN([2] + [100] * 5 + [1], "tanh", "Glorot normal")

Define an optimizer

Then, you need to define an optimizer. We have provided a few optimizers in the PINNacle.src.optimizer module. But you can also define your own optimizer as long as it is a subclass of the torch.optim.Optimizer class. For example, to use the baseline Adam optimizer, you can use the one provided by PyTorch:

import torch
 
opt = torch.optim.Adam(net.parameters(), lr=1e-3)

Assemble the problem

Now, you can assemble the problem by using the create_model method of the PDE class:

model = pde.create_model(net)
model.compile(opt)  # Compile the model with the optimizer

Sometimes you may want to use a weighted loss function. You can define the weights as follows:

import numpy as np
 
loss_weights = np.array([1, 100, 100])
model = pde.create_model(net)
model.compile(opt, loss_weights=loss_weights)

Prepare some callbacks

To monitor the training process, you can use callbacks. We have provided a few callbacks in the PINNacle.src.utils.callbacks module. But you can also define your own callback as long as it is a subclass of the deepxde.callbacks.Callback class. For example, to use the TesterCallback (calculates the error on ground-truth data), LossCallback (records and plots the loss history) and PlotCallback (plots the prediction periodically), you can use the following code:

from src.utils.callbacks import TesterCallback, LossCallback, PlotCallback
 
callbacks = [
    TesterCallback(log_every=100),
    PlotCallback(log_every=1000),
    LossCallback(),
]

Train the model

At this time, you can train the model by calling the train method of the model. For example, to train the model for 10000 epochs, you can use the following code:

model.train(iterations=10000, display_every=100, model_save_path='runs/experiment')

And you can use the results in the runs folder.

Submit and run a task

We provide a more convenient way to submit and run training tasks on multiple GPUs. . You can use the Trainer class to manage the training tasks. For example, to train the model for 10000 epochs, you can use the following code:

from trainer import Trainer
 
if __name__ == "__main__":
    # Trainer(name, device)
    trainer = Trainer("experiment", device="0")
    trainer.add_task(
        # Use a function to avoid the model being compiled before the training starts
        lambda: model, {
            "iterations": 10000,
            "display_every": 100,
            "callbacks": callbacks,
        }
    )
 
    # Backup the source code and setup the random seed
    trainer.setup(__file__, seed=42)
    # Set the number of times to repeat the training
    trainer.set_repeat(5)

To run the training tasks, you can use the following code:

trainer.train_all()
trainer.summary()

For more advanced usage, please refer to benchmark.py.

Full code

import deepxde as dde
import numpy as np
import torch
from src.pde.poisson import Poisson2D_Classic
from src.utils.callbacks import TesterCallback, LossCallback, PlotCallback
from trainer import Trainer
 
if __name__ == "__main__":
    pde = Poisson2D_Classic()
    net = dde.nn.FNN([2] + [100] * 5 + [1], "tanh", "Glorot normal")
    opt = torch.optim.Adam(net.parameters(), lr=1e-3)
 
    loss_weights = np.array([1, 100, 100])
    model = pde.create_model(net)
    model.compile(opt, loss_weights=loss_weights)
 
    callbacks = [
        TesterCallback(log_every=100),
        PlotCallback(log_every=1000),
        LossCallback(),
    ]
 
    # Train without submitting a task
    # model.train(iterations=10000, display_every=100, model_save_path='runs/experiment')
 
    # Train with submitting a task
    trainer = Trainer("experiment", device="0")
    trainer.add_task(
        lambda: model,
        {
            "iterations": 10000,
            "display_every": 100,
            "callbacks": callbacks,
        },
    )
 
    trainer.setup(__file__, seed=42)
    trainer.set_repeat(5)
    trainer.train_all()
    trainer.summary()