PINN as a PDE Solver


By Prof. Seungchul Lee
http://iai.postech.ac.kr/
Industrial AI Lab at POSTECH

Table of Contents

1. Lab 3: Euler Beam (Solid Mechanics)

1.1. Problem Setup

  • We will solve a Euler beam problem:



  • Problem properties
$$E = 1 \operatorname{pa}, \quad I = 1 \operatorname{kg\cdot m^2}, \quad q = 1 \operatorname{N/m}, \quad l = 1 \operatorname{m}$$


  • Partial differential equations & boundary conditions


$${\partial^4 y \over \partial x^4} + 1 = 0, \qquad \text{where} \quad x \in [0,1]$$

  • One Dirichlet boundary condition on the left boundary:


$$y(0) = 0$$

  • One Neumann boundary condition on the left boundary:


$$y'(0) = 0$$

  • Two boundary conditions on the right boundary:


$$y''(1) = 0, \quad y'''(1) = 0$$

  • The exact solution is


$$y(x) = -{1 \over 24}x^4 + {1 \over 6}x^3 - {1 \over 4}x^2$$

  • Make a neural network and loss functions like below :




1.2. Solve the Euler Beam problem

In [ ]:
!pip install deepxde
Collecting deepxde
  Downloading DeepXDE-1.4.0-py3-none-any.whl (132 kB)
     |████████████████████████████████| 132 kB 4.9 MB/s 
Collecting scikit-optimize
  Downloading scikit_optimize-0.9.0-py2.py3-none-any.whl (100 kB)
     |████████████████████████████████| 100 kB 11.4 MB/s 
Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (from deepxde) (3.2.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from deepxde) (1.21.6)
Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from deepxde) (1.4.1)
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.7/dist-packages (from deepxde) (1.0.2)
Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->deepxde) (2.8.2)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->deepxde) (3.0.9)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->deepxde) (1.4.2)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib->deepxde) (0.11.0)
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from kiwisolver>=1.0.1->matplotlib->deepxde) (4.2.0)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib->deepxde) (1.15.0)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->deepxde) (3.1.0)
Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->deepxde) (1.1.0)
Collecting pyaml>=16.9
  Downloading pyaml-21.10.1-py2.py3-none-any.whl (24 kB)
Requirement already satisfied: PyYAML in /usr/local/lib/python3.7/dist-packages (from pyaml>=16.9->scikit-optimize->deepxde) (3.13)
Installing collected packages: pyaml, scikit-optimize, deepxde
Successfully installed deepxde-1.4.0 pyaml-21.10.1 scikit-optimize-0.9.0
In [1]:
# from deepxde.backend.set_default_backend import set_default_backend
# set_default_backend("tensorflow")

1.2.1. Import Library

In [ ]:
import deepxde as dde
import numpy as np

1.2.2. Define PDE System

In [ ]:
def dy(x, y):
    return dde.grad.jacobian(y, x)

def ddy(x, y):
    return dde.grad.hessian(y, x)

def dddy(x, y):
    return dde.grad.jacobian(ddy(x, y), x)

def pde(x, y):
    dy_xx = ddy(x, y)
    dy_xxxx = dde.grad.hessian(dy_xx, x)
    return dy_xxxx + 1

1.2.3. Define Boundary Condition

In [ ]:
def boundary_left(x, on_boundary):
    return on_boundary and np.isclose(x[0], 0)

def boundary_right(x, on_boundary):
    return on_boundary and np.isclose(x[0], 1)

1.2.4. Define Geometry, Implement Boundary Condition

In [ ]:
geom = dde.geometry.Interval(0, 1)

bc1 = dde.DirichletBC(geom, lambda x: 0, boundary_left) # u(0) = 0
bc2 = dde.OperatorBC(geom, lambda x, y, _: dy(x, y), boundary_left) # u'(0) = 0
bc3 = dde.OperatorBC(geom, lambda x, y, _: ddy(x, y), boundary_right) # u''(1) = 0
bc4 = dde.OperatorBC(geom, lambda x, y, _: dddy(x, y), boundary_right) # u'''(1) = 0

# Reference solution to compute the error
def true_solution(x):
    return -(x ** 4) / 24 + x ** 3 / 6 - x ** 2 / 4

data = dde.data.PDE(geom, 
                    pde, 
                    [bc1, bc2, bc3, bc4], 
                    num_domain = 10, 
                    num_boundary = 2, 
                    solution = true_solution, 
                    num_test = 100)
/usr/local/lib/python3.7/dist-packages/skopt/sampler/sobol.py:250: UserWarning: The balance properties of Sobol' points require n to be a power of 2. 0 points have been previously generated, then: n=0+12=12. 
  total_n_samples))

1.2.5. Define Network and Hyper-parameters

In [ ]:
layer_size = [1] + [20] * 3 + [1]
activation = "tanh"
initializer = "Glorot uniform"

net = dde.maps.FNN(layer_size, activation, initializer)
In [ ]:
model = dde.Model(data, net)
model.compile("adam", lr = 0.001)
Compiling model...
Building feed-forward neural network...
'build' took 0.063513 s

/usr/local/lib/python3.7/dist-packages/deepxde/nn/tensorflow_compat_v1/fnn.py:110: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  kernel_constraint=self.kernel_constraint,
/usr/local/lib/python3.7/dist-packages/keras/legacy_tf_layers/core.py:261: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)
'compile' took 5.903579 s

1.2.6. Train & Prediction

In [ ]:
losshistory, train_state = model.train(epochs = 5000)
dde.saveplot(losshistory, train_state, issave = False, isplot = True)
Initializing variables...
Training model...

Step      Train loss                                            Test loss                                             Test metric
0         [5.11e-01, 0.00e+00, 1.11e-01, 4.55e-02, 1.08e-03]    [4.86e-01, 0.00e+00, 1.11e-01, 4.55e-02, 1.08e-03]    []  
1000      [5.87e-04, 2.58e-08, 5.07e-07, 7.73e-06, 5.58e-09]    [4.74e-04, 2.58e-08, 5.07e-07, 7.73e-06, 5.58e-09]    []  
2000      [1.05e-05, 1.64e-12, 1.54e-11, 8.36e-09, 6.07e-10]    [8.13e-06, 1.64e-12, 1.54e-11, 8.36e-09, 6.07e-10]    []  
3000      [5.08e-05, 4.41e-07, 1.37e-05, 2.81e-06, 1.66e-06]    [4.13e-05, 4.41e-07, 1.37e-05, 2.81e-06, 1.66e-06]    []  
4000      [3.47e-06, 1.17e-11, 1.53e-10, 3.46e-10, 6.12e-12]    [5.71e-06, 1.17e-11, 1.53e-10, 3.46e-10, 6.12e-12]    []  
5000      [3.01e-06, 7.76e-12, 1.17e-07, 5.04e-08, 1.34e-09]    [5.46e-06, 7.76e-12, 1.17e-07, 5.04e-08, 1.34e-09]    []  

Best model at step 5000:
  train loss: 3.18e-06
  test loss: 5.63e-06
  test metric: []

'train' took 26.575453 s

2. Lab 4: Navier-Stokes Equations (Fluid Mechanics)

  • Note: strongly recommend you to use GPU rather than CPU. (you can use CoLab GPU for free)
  • If you use Google Colab, please implement below codes
In [ ]:
# !pip install deepxde
In [ ]:
import deepxde as dde
import numpy as np
import matplotlib.pyplot as plt
In [ ]:
# from deepxde.backend.set_default_backend import set_default_backend
# set_default_backend("tensorflow")

2.1. Problem Setup

  • We will solve 2D Navier-Stokes Equations to find velocity profile in infinite parallel plates flow
    • Any fluid flowing in plates had to enter at some location. The region of flow near where the fluid enters the plates is termed the entrance region and is illustrated in below figure
    • The fluid typically enters the plates with a nearly uniform velocity profile
    • As the fluid moves through the plates, viscous effects cause it to stick to the plates wall (no-slip boundary condition)
    • Thus, a boundary layer is produced along the plates wall such that the initial velocity profile changes with distance along the plates, $x$, until the fluid reaches the end of the hydrodynamic entrance region (which is also called entrance length) beyond which the velocity profile does not vary with $x$





  • Problem properties


$$\rho = 1\operatorname{kg/m^3}, \quad \mu = 1\operatorname{N\cdot s/m^2}, \quad D = 2h = 1\operatorname{m}, \quad L = 2\operatorname{m}, \quad u_{in} = 1\operatorname{m/s}, \quad \nu = \frac{\mu}{\rho}$$


  • Hydraulic diameter is


$$\quad D_h = \lim\limits_{b\to\infty} {4(2bh) \over {2b+4h}} = 4h = 2\operatorname{m}$$

  • So, the Reynolds number of this system is


$$Re = \frac{\rho u_{in} D_h}{\mu} = 2 $$

  • 2D Navier-Stokes Equations & boundary conditions (for steady state)


$$ \begin{align*} u{\partial u \over \partial x} + v{\partial u \over \partial y} + {1 \over \rho}{\partial p \over \partial x} - \nu \ \left({\partial^2 u \over {\partial x^2}} + {\partial^2 u \over {\partial y^2}}\right) &= 0\\\\ u{\partial v \over \partial x} + v{\partial v \over \partial y} + {1 \over \rho}{\partial p \over \partial y} - \nu \ \left({\partial^2 v \over {\partial x^2}} + {\partial^2 v \over {\partial y^2}}\right) &= 0\\\\ {\partial u \over \partial x} + {\partial v \over \partial y} &= 0 \end{align*} $$


  • Two Dirichlet boundary conditions on the plate boundary (no-slip condition),


$$u(x,y) = 0, \quad v(x,y) = 0 \qquad \text{at} \quad y = \frac{D}{2} \ \; \text{or} \; -\frac{D}{2}$$

  • Two Dirichlet boundary conditions at the inlet boundary


$$u(-1,y) = u_{\text{in}}, \quad v(-1,y) = 0$$

  • Two Dirichlet boundary conditions at the outlet boundary


$$p(1,y) = 0, \quad v(1,y) = 0$$

  • Make a neural network and loss functions like below :




2.2. CFD Solution

  • CFD solution of this problem is illustrated in below figures
    • Velocity $u$ and velocity $v$, and pressure $p$, respectively
  • Solve this problem using PINN and then compare with CFD solutions







2.3. Solve the Navier-Stokes Equations

2.3.1. Define Parameters

In [ ]:
# Properties
rho = 1
mu = 1
u_in = 1
D = 1
L = 2

2.3.2. Define PDE with Boundary & Initial Conditions

In [ ]:
def boundary_wall(X, on_boundary):
    on_wall = np.logical_and(np.logical_or(np.isclose(X[1], -D/2), np.isclose(X[1], D/2)), on_boundary)
    return on_wall

def boundary_inlet(X, on_boundary):
    return on_boundary and np.isclose(X[0], -L/2)

def boundary_outlet(X, on_boundary):
    return on_boundary and np.isclose(X[0], L/2)
In [ ]:
def pde(X, Y):
    du_x = dde.grad.jacobian(Y, X, i = 0, j = 0)
    du_y = dde.grad.jacobian(Y, X, i = 0, j = 1)
    dv_x = dde.grad.jacobian(Y, X, i = 1, j = 0)
    dv_y = dde.grad.jacobian(Y, X, i = 1, j = 1)
    dp_x = dde.grad.jacobian(Y, X, i = 2, j = 0)
    dp_y = dde.grad.jacobian(Y, X, i = 2, j = 1)
    du_xx = dde.grad.hessian(Y, X, i = 0, j = 0, component = 0)
    du_yy = dde.grad.hessian(Y, X, i = 1, j = 1, component = 0)
    dv_xx = dde.grad.hessian(Y, X, i = 0, j = 0, component = 1)
    dv_yy = dde.grad.hessian(Y, X, i = 1, j = 1, component = 1)
    
    pde_u = Y[:,0:1]*du_x + Y[:,1:2]*du_y + 1/rho * dp_x - (mu/rho)*(du_xx + du_yy)
    pde_v = Y[:,0:1]*dv_x + Y[:,1:2]*dv_y + 1/rho * dp_y - (mu/rho)*(dv_xx + dv_yy)
    pde_cont = du_x + dv_y

    return [pde_u, pde_v, pde_cont]

2.3.3. Define Geometry, Implement Boundary Condition

In [ ]:
geom = dde.geometry.Rectangle(xmin=[-L/2, -D/2], xmax=[L/2, D/2])

bc_wall_u = dde.DirichletBC(geom, lambda X: 0., boundary_wall, component = 0)
bc_wall_v = dde.DirichletBC(geom, lambda X: 0., boundary_wall, component = 1)

bc_inlet_u = dde.DirichletBC(geom, lambda X: u_in, boundary_inlet, component = 0)
bc_inlet_v = dde.DirichletBC(geom, lambda X: 0., boundary_inlet, component = 1)

bc_outlet_p = dde.DirichletBC(geom, lambda X: 0., boundary_outlet, component = 2)
bc_outlet_v = dde.DirichletBC(geom, lambda X: 0., boundary_outlet, component = 1)
In [ ]:
data = dde.data.PDE(geom, 
                    pde, 
                    [bc_wall_u, bc_wall_v, bc_inlet_u, bc_inlet_v, bc_outlet_p, bc_outlet_v], 
                    num_domain = 3000, 
                    num_boundary = 500, 
                    num_test = 1000,
                    train_distribution = 'LHS' )
Warning: 1000 points required, but 1035 points sampled.
In [ ]:
plt.figure(figsize = (10,8))
plt.scatter(data.train_x_all[:,0], data.train_x_all[:,1], s = 0.5)
plt.xlabel('x-direction length')
plt.ylabel('Distance from the middle of plates (m)')
plt.show()

2.3.4. Define Network and Hyper-parameters

In [ ]:
layer_size = [2] + [64] * 5 + [3]
activation = "tanh"
initializer = "Glorot uniform"

net = dde.maps.FNN(layer_size, activation, initializer)

model = dde.Model(data, net)
model.compile("adam", lr = 1e-3)
Compiling model...
Building feed-forward neural network...
'build' took 0.085033 s

/usr/local/lib/python3.7/dist-packages/deepxde/nn/tensorflow_compat_v1/fnn.py:110: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  kernel_constraint=self.kernel_constraint,
/usr/local/lib/python3.7/dist-packages/keras/legacy_tf_layers/core.py:261: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)
'compile' took 1.837211 s

2.3.5. Train (Adam Optimizer)

In [ ]:
losshistory, train_state = model.train(epochs = 10000)
dde.saveplot(losshistory, train_state, issave = False, isplot = True)
Initializing variables...
Training model...

Step      Train loss                                                                                    Test loss                                                                                     Test metric
0         [4.78e-02, 5.12e-02, 6.69e-02, 1.96e-02, 5.71e-04, 5.90e-01, 8.83e-04, 3.95e-02, 8.77e-04]    [4.84e-02, 5.19e-02, 6.93e-02, 1.96e-02, 5.71e-04, 5.90e-01, 8.83e-04, 3.95e-02, 8.77e-04]    []  
1000      [7.40e-04, 9.72e-04, 7.43e-03, 3.25e-02, 1.50e-02, 4.05e-02, 1.59e-03, 3.07e-04, 1.22e-05]    [3.68e-04, 6.34e-04, 4.99e-03, 3.25e-02, 1.50e-02, 4.05e-02, 1.59e-03, 3.07e-04, 1.22e-05]    []  
2000      [8.95e-04, 6.71e-04, 4.33e-03, 2.53e-02, 1.23e-02, 2.98e-02, 1.24e-03, 4.43e-05, 2.24e-05]    [7.45e-04, 3.99e-04, 3.08e-03, 2.53e-02, 1.23e-02, 2.98e-02, 1.24e-03, 4.43e-05, 2.24e-05]    []  
3000      [1.06e-02, 4.64e-03, 3.38e-03, 2.21e-02, 1.08e-02, 2.34e-02, 2.84e-03, 7.04e-04, 2.96e-06]    [1.02e-02, 3.62e-03, 2.31e-03, 2.21e-02, 1.08e-02, 2.34e-02, 2.84e-03, 7.04e-04, 2.96e-06]    []  
4000      [9.53e-04, 6.16e-04, 2.98e-03, 2.08e-02, 9.72e-03, 2.05e-02, 3.97e-03, 5.96e-05, 8.92e-06]    [8.32e-04, 3.50e-04, 1.68e-03, 2.08e-02, 9.72e-03, 2.05e-02, 3.97e-03, 5.96e-05, 8.92e-06]    []  
5000      [3.50e-03, 1.36e-03, 2.80e-03, 1.96e-02, 8.70e-03, 1.90e-02, 4.90e-03, 4.36e-05, 9.82e-06]    [2.92e-03, 6.52e-04, 1.69e-03, 1.96e-02, 8.70e-03, 1.90e-02, 4.90e-03, 4.36e-05, 9.82e-06]    []  
6000      [3.35e-03, 1.88e-03, 2.52e-03, 1.95e-02, 8.00e-03, 1.80e-02, 5.32e-03, 9.88e-05, 3.17e-05]    [2.76e-03, 1.81e-03, 1.24e-03, 1.95e-02, 8.00e-03, 1.80e-02, 5.32e-03, 9.88e-05, 3.17e-05]    []  
7000      [1.18e-02, 2.30e-03, 2.94e-03, 1.90e-02, 7.26e-03, 1.75e-02, 5.53e-03, 7.06e-03, 3.17e-05]    [1.13e-02, 2.34e-03, 1.68e-03, 1.90e-02, 7.26e-03, 1.75e-02, 5.53e-03, 7.06e-03, 3.17e-05]    []  
8000      [2.89e-03, 6.07e-04, 2.66e-03, 1.90e-02, 6.87e-03, 1.67e-02, 5.78e-03, 8.93e-04, 7.66e-06]    [2.87e-03, 3.79e-04, 1.45e-03, 1.90e-02, 6.87e-03, 1.67e-02, 5.78e-03, 8.93e-04, 7.66e-06]    []  
9000      [6.83e-04, 4.02e-04, 2.28e-03, 1.88e-02, 6.58e-03, 1.61e-02, 5.98e-03, 2.18e-05, 9.60e-06]    [6.12e-04, 3.24e-04, 1.22e-03, 1.88e-02, 6.58e-03, 1.61e-02, 5.98e-03, 2.18e-05, 9.60e-06]    []  
10000     [1.69e-03, 9.05e-04, 2.44e-03, 1.91e-02, 6.31e-03, 1.51e-02, 6.11e-03, 1.50e-04, 1.81e-05]    [1.42e-03, 8.21e-04, 1.27e-03, 1.91e-02, 6.31e-03, 1.51e-02, 6.11e-03, 1.50e-04, 1.81e-05]    []  

Best model at step 9000:
  train loss: 5.09e-02
  test loss: 4.97e-02
  test metric: []

'train' took 87.536283 s

2.3.6. Plot Results (Adam Optimizer)

In [ ]:
samples = geom.random_points(500000)
result = model.predict(samples)
color_legend = [[0, 1.5], [-0.3, 0.3], [0, 35]]
for idx in range(3):
    plt.figure(figsize = (20, 4))
    plt.scatter(samples[:, 0],
                samples[:, 1],
                c = result[:, idx],
                cmap = 'jet',
                s = 2)
    plt.colorbar()
    plt.clim(color_legend[idx])
    plt.xlim((0-L/2, L-L/2))
    plt.ylim((0-D/2, D-D/2))
    plt.tight_layout()
    plt.show()

2.3.7. Train More (L-BFGS Optimizer)

In [ ]:
dde.optimizers.config.set_LBFGS_options(maxiter=3000)

model.compile("L-BFGS")
losshistory, train_state = model.train()
dde.saveplot(losshistory, train_state, issave = False, isplot = True)
Compiling model...
'compile' took 1.961743 s

Training model...

Step      Train loss                                                                                    Test loss                                                                                     Test metric
15263     [4.14e-04, 3.41e-04, 3.30e-04, 5.13e-03, 1.37e-03, 2.48e-03, 1.33e-03, 1.36e-05, 4.69e-06]    [5.67e-04, 5.56e-04, 2.98e-04, 5.13e-03, 1.37e-03, 2.48e-03, 1.33e-03, 1.36e-05, 4.69e-06]    []  
INFO:tensorflow:Optimization terminated with:
  Message: b'CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH'
  Objective function value: 0.011398
  Number of iterations: 1
  Number of functions evaluations: 17
15280     [4.14e-04, 3.41e-04, 3.30e-04, 5.13e-03, 1.37e-03, 2.48e-03, 1.33e-03, 1.36e-05, 4.69e-06]    [5.67e-04, 5.56e-04, 2.98e-04, 5.13e-03, 1.37e-03, 2.48e-03, 1.33e-03, 1.36e-05, 4.69e-06]    []  

Best model at step 15263:
  train loss: 1.14e-02
  test loss: 1.17e-02
  test metric: []

'train' took 6.829720 s

2.3.8. Plot Results (Adam + L-BFGS)

In [ ]:
samples = geom.random_points(500000)
result = model.predict(samples)
color_legend = [[0, 1.5], [-0.3, 0.3], [0, 35]]
for idx in range(3):
    plt.figure(figsize = (20, 4))
    plt.scatter(samples[:, 0],
                samples[:, 1],
                c = result[:, idx],
                cmap = 'jet',
                s = 2)
    plt.colorbar()
    plt.clim(color_legend[idx])
    plt.xlim((0-L/2, L-L/2))
    plt.ylim((0-D/2, D-D/2))
    plt.tight_layout()
    plt.show()

2.3.9. Validation: Plot Velocity Profile at the End of the Plate

  • Fully developed velocity profile at the infinite parallel plates flow are known as


$$u(y) = {3V_{avg} \over 2} \left[ 1- \left( \frac{y}{h} \right)^2 \right]$$




In [ ]:
# Analytic solution
x = np.ones([1000,1])
y = np.linspace(-0.5, 0.5, 1000).reshape(1000,1)
outlet = np.hstack([x, y])

analytic_solution = u_in * 1.5 * (1 - ((y)/(D/2))**2)

PINN_solution = model.predict(outlet)
In [ ]:
plt.figure(figsize = (10,8))
plt.plot(y, PINN_solution[:, 0], c = 'tomato', linewidth = 3, label = 'PINN solution')
plt.plot(y, analytic_solution, c = 'k', linestyle = '--', label = 'Analytic solution')
plt.xticks(np.linspace(-0.5, 0.5, 5), fontsize = 15)
plt.yticks(np.linspace(0, 1.5, 11), fontsize = 15)
plt.legend(fontsize = 12)
plt.xlabel('Distance from the middle of plates (m)', fontsize = 15)
plt.ylabel('Velocity ($u$)', fontsize = 15)
plt.show()
In [ ]:
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')