Create Your Own PDE Class
We'll use the BasePDE
class as a foundation and demonstrate how to implement custom PDEs using examples from our library.
Step 0: Import Required Libraries
First, import the necessary libraries and the BasePDE
class:
import numpy as np
import torch
import deepxde as dde
from PINNacle.src.pde import baseclass
Step 1: Define Your PDE Class
Create a new class that inherits from baseclass.BasePDE
:
class YourCustomPDE(baseclass.BasePDE):
def __init__(self, param1, param2, ...):
super().__init__()
# Initialize your PDE-specific parameters here
Step 2: Set Output Configuration
Define the name of each output dimension for your PDE:
self.output_config = [{'name': 'output1'}, {'name': 'output2'}, ...]
For example, in the NS2D_Classic
class, the output are velocity components u
, v
, and pressure p
. So the output configuration is:
self.output_config = [{'name': 'u'}, {'name': 'v'}, {'name': 'p'}]
Step 3: Define the Geometry
Set up the geometry for your PDE:
self.bbox = [x_min, x_max, y_min, y_max]
self.geom = dde.geometry.Rectangle(xmin=[x_min, y_min], xmax=[x_max, y_max])
The geom
should always be inside the bbox
. The geom
is used to sample data points and the bbox
is used to make plots.
For more complex geometries, you can use CSG operations as shown in our codes.
Step 4: Define the PDE
Implement the PDE function:
def your_pde(x, u):
# Define your PDE equations here
# Use dde.grad.jacobian, dde.grad.hessian, etc., for derivatives
return [eq1, eq2, ...]
self.pde = your_pde
self.set_pdeloss(names=["eq1_name", "eq2_name", ...])
Step 5: Provide Reference Solution
If you have reference data, load it:
self.load_ref_data("path/to/your/data.dat")
Each row of ref_data
should contain the input and output for a data point, formatted as [input1, input2, ..., output1, output2, ...]
.
Or you can define a reference solution function:
def ref_solution(x):
# Define your reference solution here
return [output1, output2, ...]
self.ref_sol = ref_solution
Step 6: Define Boundary Conditions
Set up the boundary conditions for your PDE:
def boundary_condition_value(x, on_boundary):
# Define your boundary value here
pass
def is_on_boundary_condition(x, on_boundary):
# Define your boundary condition here
# on_boundary is True means x is directly sampled on the boundaries
# But you may need to judge which boundary it is
pass
self.add_bcs([{
'component': 0,
'function': boundary_condition_value,
'bc': is_on_boundary_condition,
'type': 'dirichlet'
}])
We support DirichletBC, NeumannBC, RobinBC, OperatorBC, PeriodicBC, PointSetBC, IC
from the deepxde
library.
Step 7: Set Training Points
Configure the number of training points:
self.training_points(domain=8192, boundary=2048, test=8192)
And you're done! You can now use this class to solve your PDEs.
Example
Here's a simplified 2D Navier-Stokes PDE example:
class SimplifiedNS2D(baseclass.BasePDE):
def __init__(self, nu=1, bbox=[0, 1, 0, 1]):
super().__init__()
self.nu = nu
self.bbox = bbox
self.output_config = [{'name': s} for s in ['u', 'v', 'p']]
self.geom = dde.geometry.Rectangle(xmin=[bbox[0], bbox[2]], xmax=[bbox[1], bbox[3]])
def ns_pde(x, u):
u_vel, v_vel, _ = u[:, 0:1], u[:, 1:2], u[:, 2:]
u_vel_x = dde.grad.jacobian(u, x, i=0, j=0)
v_vel_y = dde.grad.jacobian(u, x, i=1, j=1)
p_x = dde.grad.jacobian(u, x, i=2, j=0)
p_y = dde.grad.jacobian(u, x, i=2, j=1)
continuity = u_vel_x + v_vel_y
return [p_x, p_y, continuity]
self.pde = ns_pde
self.set_pdeloss(names=["momentum_x", "momentum_y", "continuity"])
def boundary(x, on_boundary):
return on_boundary
self.add_bcs([{
'component': 0,
'function': (lambda _: 0),
'bc': boundary,
'type': 'dirichlet'
}, {
'component': 1,
'function': (lambda _: 0),
'bc': boundary,
'type': 'dirichlet'
}])
self.training_points()
This example creates a simplified 2D Navier-Stokes PDE with dirichlet boundary conditions. You can use this as a template to create your own custom PDEs.