Inverse problem for the Poisson equation with unknown forcing field

Inverse problem for the Poisson equation with unknown forcing field#

Problem setup#

We will solve

\[ \frac{d^2u}{dx^2} = q(x), \quad x \in [-1, 1] \]

with the Dirichlet boundary conditions

\[ u(-1) = 0, \quad u(1) = 0 \]

Here, both \(u(x)\) and \(q(x)\) are unknown. Furthermore, we have the measurement of \(u(x)\) at 100 points.

The reference solution is \(u(x) = \sin(\pi x), \quad q(x) = -\pi^2 \sin(\pi x)\).

Dimensional analysis#

Assumptions on dimensions:

  • Let \([x] = L\), where \(L\) represents length.

  • Let \([u] = U\), where \(U\) represents the dimension of the unknown field \(u\). For instance, \(u\) could represent displacement, temperature, potential, etc. The exact choice of \(U\) depends on the physical context.

  • The dimension of \(q(x)\) is to be determined.

Dimensional Analysis:

  1. Consider the left-hand side (LHS): \(\frac{d^2 u}{dx^2}\) Its dimension is:

    \[ \left[\frac{d^2 u}{dx^2}\right] = \frac{[u]}{[x]^2} = \frac{U}{L^2}. \]
  2. For the equation to be dimensionally consistent, the right-hand side (RHS) \(q(x)\) must have the same dimension as the LHS: $\( [q(x)] = \frac{U}{L^2}. \)$

Summary:

  • If the dimension of \(u\) is \(U\) and the dimension of \(x\) is \(L\), then the source term \(q(x)\) must have the dimension \(U/L^2\).

In other words, the source term \(q(x)\) is the field quantity \(u\) per unit length squared. The physical meaning depends on the specific problem. For example, if \(u\) represents temperature, then \(q(x)\) might represent a thermal energy source density. If \(u\) represents an electric potential, then \(q(x)\) may relate to charge distribution (scaled appropriately by physical constants).

Implementation#

Import the necessary package:

import brainstate
import brainunit as u
import matplotlib.pyplot as plt
import numpy as np

import pinnx

Define the units for the problem:

unit_of_x = u.meter
unit_of_u = u.newton
unit_of_q = u.newton / unit_of_x ** 2

Define the problem.

def pde(x, y):
    du_xx = net.hessian(x, y='u')['u']['x']['x']
    return -du_xx + y['q']


geom = pinnx.geometry.Interval(-1, 1).to_dict_point(x=unit_of_x)

Define the Dirichlet boundary conditions.

def sol(x):
    # solution is u(x) = sin(pi*x), q(x) = -pi^2 * sin(pi*x)
    # return {'u': u.math.sin(u.math.pi * x['x']), }
    return {
        'u': u.math.sin(u.math.pi * x['x'] / unit_of_x) * unit_of_u,
        'q': -u.math.pi ** 2 * u.math.sin(u.math.pi * x['x'] / unit_of_x) * unit_of_q
    }


bc = pinnx.icbc.DirichletBC(sol)

Define the observation points and the training data generator.

def gen_traindata(num):
    # generate num equally-spaced points from -1 to 1
    xvals = np.linspace(-1, 1, num)
    uvals = np.sin(np.pi * xvals)
    return {'x': xvals * unit_of_x}, {'u': uvals * unit_of_u}


ob_x, ob_u = gen_traindata(100)
observe_u = pinnx.icbc.PointSetBC(ob_x, ob_u)

Define the neural network model, adding the unit information to the input and output:

net = pinnx.nn.Model(
    pinnx.nn.DictToArray(x=unit_of_x),
    pinnx.nn.PFNN([1, [20, 20], [20, 20], [20, 20], 2], "tanh", braintools.init.KaimingUniform()),
    pinnx.nn.ArrayToDict(u=unit_of_u, q=unit_of_q),
)

The final problem:

problem = pinnx.problem.PDE(
    geom,
    pde,
    [bc, observe_u],
    net,
    num_domain=200,
    num_boundary=2,
    anchors=ob_x,
    num_test=1000,
    loss_weights=[1, 100, 1000],
)

Train the model:

model = pinnx.Trainer(problem)
model.compile(braintools.optim.Adam(0.0001)).train(iterations=20000)
model.saveplot(issave=True, isplot=True)
Compiling trainer...
'compile' took 0.049581 s

Training trainer...

Step      Train loss                                     Test loss                                      Test metric 
0         [57.104668 * pascal2,                          [58.55549 * pascal2,                           []          
           {'ibc0': {'q': 71.43462 * pascal,              {'ibc0': {'q': 71.43462 * pascal,                         
                     'u': 21.93853 * newton}},                      'u': 21.93853 * newton}},                       
           {'ibc1': {'u': 1383.2239 * newton}}]           {'ibc1': {'u': 1383.2239 * newton}}]                      
1000      [9.099975 * pascal2,                           [9.277513 * pascal2,                           []          
           {'ibc0': {'q': 0.02584232 * pascal,            {'ibc0': {'q': 0.02584232 * pascal,                       
                     'u': 39.094223 * newton}},                     'u': 39.094223 * newton}},                      
           {'ibc1': {'u': 89.13055 * newton}}]            {'ibc1': {'u': 89.13055 * newton}}]                       
2000      [17.813923 * pascal2,                          [17.973166 * pascal2,                          []          
           {'ibc0': {'q': 0.03595128 * pascal,            {'ibc0': {'q': 0.03595128 * pascal,                       
                     'u': 22.458023 * newton}},                     'u': 22.458023 * newton}},                      
           {'ibc1': {'u': 49.399963 * newton}}]           {'ibc1': {'u': 49.399963 * newton}}]                      
3000      [20.421053 * pascal2,                          [20.553577 * pascal2,                          []          
           {'ibc0': {'q': 0.03406093 * pascal,            {'ibc0': {'q': 0.03406093 * pascal,                       
                     'u': 12.548685 * newton}},                     'u': 12.548685 * newton}},                      
           {'ibc1': {'u': 26.532593 * newton}}]           {'ibc1': {'u': 26.532593 * newton}}]                      
4000      [18.949293 * pascal2,                          [19.105434 * pascal2,                          []          
           {'ibc0': {'q': 0.03712923 * pascal,            {'ibc0': {'q': 0.03712923 * pascal,                       
                     'u': 6.2132597 * newton}},                     'u': 6.2132597 * newton}},                      
           {'ibc1': {'u': 12.868485 * newton}}]           {'ibc1': {'u': 12.868485 * newton}}]                      
5000      [15.683843 * pascal2,                          [15.849516 * pascal2,                          []          
           {'ibc0': {'q': 0.03507624 * pascal,            {'ibc0': {'q': 0.03507624 * pascal,                       
                     'u': 2.9483395 * newton}},                     'u': 2.9483395 * newton}},                      
           {'ibc1': {'u': 6.0103345 * newton}}]           {'ibc1': {'u': 6.0103345 * newton}}]                      
6000      [11.3106 * pascal2,                            [11.44179 * pascal2,                           []          
           {'ibc0': {'q': 0.02415182 * pascal,            {'ibc0': {'q': 0.02415182 * pascal,                       
                     'u': 1.4232403 * newton}},                     'u': 1.4232403 * newton}},                      
           {'ibc1': {'u': 2.9394844 * newton}}]           {'ibc1': {'u': 2.9394844 * newton}}]                      
7000      [6.889275 * pascal2,                           [6.9675927 * pascal2,                          []          
           {'ibc0': {'q': 0.01458644 * pascal,            {'ibc0': {'q': 0.01458644 * pascal,                       
                     'u': 0.7137065 * newton}},                     'u': 0.7137065 * newton}},                      
           {'ibc1': {'u': 1.5752933 * newton}}]           {'ibc1': {'u': 1.5752933 * newton}}]                      
8000      [3.0519495 * pascal2,                          [3.085175 * pascal2,                           []          
           {'ibc0': {'q': 0.00667563 * pascal,            {'ibc0': {'q': 0.00667563 * pascal,                       
                     'u': 0.33465862 * newton}},                    'u': 0.33465862 * newton}},                     
           {'ibc1': {'u': 0.84809256 * newton}}]          {'ibc1': {'u': 0.84809256 * newton}}]                     
9000      [1.0699911 * pascal2,                          [1.0805568 * pascal2,                          []          
           {'ibc0': {'q': 0.0019603 * pascal,             {'ibc0': {'q': 0.0019603 * pascal,                        
                     'u': 0.14555798 * newton}},                    'u': 0.14555798 * newton}},                     
           {'ibc1': {'u': 0.529897 * newton}}]            {'ibc1': {'u': 0.529897 * newton}}]                       
10000     [0.36260056 * pascal2,                         [0.36515456 * pascal2,                         []          
           {'ibc0': {'q': 0.00053385 * pascal,            {'ibc0': {'q': 0.00053385 * pascal,                       
                     'u': 0.06022839 * newton}},                    'u': 0.06022839 * newton}},                     
           {'ibc1': {'u': 0.4085685 * newton}}]           {'ibc1': {'u': 0.4085685 * newton}}]                      
11000     [0.18483813 * pascal2,                         [0.1858395 * pascal2,                          []          
           {'ibc0': {'q': 0.00012002 * pascal,            {'ibc0': {'q': 0.00012002 * pascal,                       
                     'u': 0.02717585 * newton}},                    'u': 0.02717585 * newton}},                     
           {'ibc1': {'u': 0.3386068 * newton}}]           {'ibc1': {'u': 0.3386068 * newton}}]                      
12000     [0.12419797 * pascal2,                         [0.12520623 * pascal2,                         []          
           {'ibc0': {'q': 6.49303e-05 * pascal,           {'ibc0': {'q': 6.49303e-05 * pascal,                      
                     'u': 0.01508655 * newton}},                    'u': 0.01508655 * newton}},                     
           {'ibc1': {'u': 0.252522 * newton}}]            {'ibc1': {'u': 0.252522 * newton}}]                       
13000     [0.07476345 * pascal2,                         [0.07559747 * pascal2,                         []          
           {'ibc0': {'q': 0.00022872 * pascal,            {'ibc0': {'q': 0.00022872 * pascal,                       
                     'u': 0.00766858 * newton}},                    'u': 0.00766858 * newton}},                     
           {'ibc1': {'u': 0.15152633 * newton}}]          {'ibc1': {'u': 0.15152633 * newton}}]                     
14000     [0.0346879 * pascal2,                          [0.0351154 * pascal2,                          []          
           {'ibc0': {'q': 1.1295616e-05 * pascal,         {'ibc0': {'q': 1.1295616e-05 * pascal,                    
                     'u': 0.00236588 * newton}},                    'u': 0.00236588 * newton}},                     
           {'ibc1': {'u': 0.07225788 * newton}}]          {'ibc1': {'u': 0.07225788 * newton}}]                     
15000     [0.01340395 * pascal2,                         [0.01357405 * pascal2,                         []          
           {'ibc0': {'q': 5.2584824e-06 * pascal,         {'ibc0': {'q': 5.2584824e-06 * pascal,                    
                     'u': 0.00018877 * newton}},                    'u': 0.00018877 * newton}},                     
           {'ibc1': {'u': 0.03148581 * newton}}]          {'ibc1': {'u': 0.03148581 * newton}}]                     
16000     [0.00504076 * pascal2,                         [0.00508512 * pascal2,                         []          
           {'ibc0': {'q': 2.1729034e-05 * pascal,         {'ibc0': {'q': 2.1729034e-05 * pascal,                    
                     'u': 3.5908473e-05 * newton}},                 'u': 3.5908473e-05 * newton}},                  
           {'ibc1': {'u': 0.01453501 * newton}}]          {'ibc1': {'u': 0.01453501 * newton}}]                     
17000     [0.00211953 * pascal2,                         [0.00213015 * pascal2,                         []          
           {'ibc0': {'q': 3.1532704e-06 * pascal,         {'ibc0': {'q': 3.1532704e-06 * pascal,                    
                     'u': 0.00025964 * newton}},                    'u': 0.00025964 * newton}},                     
           {'ibc1': {'u': 0.00791687 * newton}}]          {'ibc1': {'u': 0.00791687 * newton}}]                     
18000     [0.0009769 * pascal2,                          [0.00098521 * pascal2,                         []          
           {'ibc0': {'q': 3.8374005e-06 * pascal,         {'ibc0': {'q': 3.8374005e-06 * pascal,                    
                     'u': 0.00030924 * newton}},                    'u': 0.00030924 * newton}},                     
           {'ibc1': {'u': 0.00499536 * newton}}]          {'ibc1': {'u': 0.00499536 * newton}}]                     
19000     [0.00052247 * pascal2,                         [0.00052762 * pascal2,                         []          
           {'ibc0': {'q': 3.1709257e-07 * pascal,         {'ibc0': {'q': 3.1709257e-07 * pascal,                    
                     'u': 0.00027461 * newton}},                    'u': 0.00027461 * newton}},                     
           {'ibc1': {'u': 0.00339381 * newton}}]          {'ibc1': {'u': 0.00339381 * newton}}]                     
20000     [0.00034573 * pascal2,                         [0.00034509 * pascal2,                         []          
           {'ibc0': {'q': 8.5156114e-07 * pascal,         {'ibc0': {'q': 8.5156114e-07 * pascal,                    
                     'u': 0.00021139 * newton}},                    'u': 0.00021139 * newton}},                     
           {'ibc1': {'u': 0.00248489 * newton}}]          {'ibc1': {'u': 0.00248489 * newton}}]                     

Best trainer at step 20000:
  train loss: 3.04e-03
  test loss: 3.04e-03
  test metric: []

'train' took 42.773905 s

Saving loss history to D:\codes\projects\pinnx\docs\examples-pinn-inverse\loss.dat ...
Saving checkpoint into D:\codes\projects\pinnx\docs\examples-pinn-inverse\loss.dat
Saving training data to D:\codes\projects\pinnx\docs\examples-pinn-inverse\train.dat ...
Saving checkpoint into D:\codes\projects\pinnx\docs\examples-pinn-inverse\train.dat
Saving test data to D:\codes\projects\pinnx\docs\examples-pinn-inverse\test.dat ...
Saving checkpoint into D:\codes\projects\pinnx\docs\examples-pinn-inverse\test.dat
../_images/fdd4bcbcf912195e95de49e9e0b8e8e2203ad121a726f491145e50fb976e220b.png ../_images/ac2cb73f470729efb9a3931d6f2c341e33c065991319a4a3ecd549986a234c18.png

Visualize the results:


# view results
x = geom.uniform_points(500)
yhat = model.predict(x)
uhat = yhat['u'] / unit_of_u
qhat = yhat['q'] / unit_of_q
x = x['x'] / unit_of_x

utrue = np.sin(np.pi * x)
print("l2 relative error for u: " + str(pinnx.metrics.l2_relative_error(utrue, uhat)))
plt.figure()
plt.plot(x, utrue, "-", label="u_true")
plt.plot(x, uhat, "--", label="u_NN")
plt.legend()

qtrue = -np.pi ** 2 * np.sin(np.pi * x)
print("l2 relative error for q: " + str(pinnx.metrics.l2_relative_error(qtrue, qhat)))
plt.figure()
plt.plot(x, qtrue, "-", label="q_true")
plt.plot(x, qhat, "--", label="q_NN")
plt.legend()

plt.show()
l2 relative error for u: 0.0022317795
l2 relative error for q: 0.02959022
../_images/95524d5b28a7592101e4690958a110b0da241a6e35014bc2593dcb4b55877579.png ../_images/a1067cd7b1ee75ad748d87f607858697ec4378533e9248a81962a28b3bc6d464.png

Complete code please see the code in elliptic_inverse_field.py.