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 baseclassStep 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 hereStep 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_solutionStep 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.