Inferring the space-dependent reaction rate in a diffusion-reaction system

Inferring the space-dependent reaction rate in a diffusion-reaction system#

The problem we need to solve#

We consider a one-dimensional diffusion-reaction system in which the reaction rate \(k(x)\) is a space-dependent function:

\[\lambda\frac{\partial^2u}{\partial x^2}-k(x)u=f,\quad x\in[0,1],\]

where \(\lambda=0.01\) is the diffusion coefficient, \(u\) is the solute concentration, and \(f=\sin(2\pi x)\) is the source term. The objective is to infer \(k(x)\) given measurements on \(u.\) The exact unknown reaction rate is

\[k(x)=0.1+\exp\left[-0.5\frac{(x-0.5)^2}{0.15^2}\right].\]

In addition, the condition \(u(x)=0\) is imposed at \(x=0\) and 1.

Dimensional analysis#

Below is a dimensional analysis for each variable and parameter in the given one-dimensional diffusion-reaction equation:

Governing equation: $\( \lambda \frac{\partial^2 u}{\partial x^2} - k(x)u = f, \quad x \in [0,1]. \)$

Physical meanings and typical dimensions:

  1. Spatial coordinate, \( x \)

    • Typically, a spatial coordinate has the dimension of length:

      \[ [x] = L. \]
  2. Solute concentration, \( u(x) \)

    • Concentration is commonly expressed as mass per unit volume (assuming a mass-based concentration):

      \[ [u] = M / L^3. \]
  3. Diffusion coefficient, \( \lambda \)

    • A diffusion coefficient typically has dimensions of length squared per time:

      \[ [\lambda] = L^2 / T. \]
  4. Reaction rate, \( k(x) \)

    • Consider the term \( k(x)u \). This must have the same dimension as \(\lambda \frac{\partial^2 u}{\partial x^2}\).

    • The Laplacian term \(\frac{\partial^2 u}{\partial x^2}\) has dimension \((M/L^3) / L^2 = M / (L^5)\).

    • Multiplying by \(\lambda = L^2/T\) gives \((L^2/T)*(M/L^5) = M/(T L^3)\).

    • Therefore, \(k(x)u\) must also have dimension \(M/(T L^3)\). Given \( [u] = M/L^3 \), it follows:

      \[ [k(x)] [u] = M/(T L^3) \implies [k(x)] (M/L^3) = M/(T L^3) \implies [k(x)] = 1/T. \]

    Thus, the reaction rate \(k(x)\) has units of inverse time, which makes sense physically.

  5. Source term, \( f \)

    • The source term \( f \) appears on the right-hand side of the equation and must have the same dimension as the left-hand side terms. We have already found that \(\lambda \frac{\partial^2 u}{\partial x^2}\) and \(k(x)u\) both have dimensions \( M/(T L^3) \). Hence:

      \[ [f] = M/(T L^3). \]

Summary of Dimensions:

  • \([x] = L\)

  • \([u] = M/L^3\)

  • \([\lambda] = L^2/T\)

  • \([k(x)] = 1/T\)

  • \([f] = M/(T L^3)\)

These dimensions are consistent with a diffusion-reaction system where:

  • \(u\) is a concentration (mass per volume),

  • \(\lambda\) is a diffusion coefficient (length²/time),

  • \(k(x)\) is a reaction rate (1/time),

  • \(f\) is a source term with the dimension of a generation/consumption rate of mass per volume per time.

Implementation#

First, import the necessary libraries:

import brainstate
import brainunit as u
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import solve_bvp

import pinnx

Define the units for the variables and parameters:

unit_of_x = u.meter
unit_of_u = u.mole / u.meter ** 3
unit_of_f = u.mole / (u.second * u.meter ** 3)

l = 0.01 * unit_of_x ** 2 / u.second

Define the solution for the solute concentration:


def k(x):
    return 0.1 + np.exp(-0.5 * (x - 0.5) ** 2 / 0.15 ** 2)


def fun(x, y):
    return np.vstack((y[1], (k(x) * y[0] + np.sin(2 * np.pi * x)) / l.mantissa))


def bc(ya, yb):
    return np.array([ya[0], yb[0]])


num = 100
xvals = np.linspace(0, 1, num)
y = np.zeros((2, xvals.size))
res = solve_bvp(fun, bc, xvals, y)

Define the PDE for the neural network model:

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


def pde(x, y):
    hessian = net.hessian(x, y='u')
    du_xx = hessian["u"]["x"]["x"]
    f = u.math.sin(2 * np.pi * x['x'].mantissa) * unit_of_f
    return l * du_xx - y['u'] * y['k'] - f

Define the neural network model:

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

Define the training data:

def gen_traindata():
    x = {'x': xvals * unit_of_x}
    y = {'u': res.sol(xvals)[0] * unit_of_u}
    return x, y


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

Define the boundary conditions:

bc = pinnx.icbc.DirichletBC(lambda x: {'u': 0 * unit_of_u})

Define the PDE problem:

problem = pinnx.problem.PDE(
    geom,
    pde,
    constraints=[bc, observe_u],
    approximator=net,
    num_domain=50,
    num_boundary=8,
    train_distribution="uniform",
    num_test=1000,
)

Train the model:

model = pinnx.Trainer(problem)
model.compile(braintools.optim.Adam(1e-3)).train(iterations=20000)
Compiling trainer...
'compile' took 0.046593 s

Training trainer...

Step      Train loss                                                                         Test loss                                                                          Test metric 
0         [0.42841953 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          [0.48738545 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          []          
           {'ibc0': {'u': 0.04377487 * mmolar}},                                              {'ibc0': {'u': 0.04377487 * mmolar}},                                                         
           {'ibc1': {'u': 0.76479465 * mmolar}}]                                              {'ibc1': {'u': 0.76479465 * mmolar}}]                                                         
1000      [0.00161212 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          [0.00137106 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          []          
           {'ibc0': {'u': 0.00100709 * mmolar}},                                              {'ibc0': {'u': 0.00100709 * mmolar}},                                                         
           {'ibc1': {'u': 0.0077876 * mmolar}}]                                               {'ibc1': {'u': 0.0077876 * mmolar}}]                                                          
2000      [0.00086259 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          [0.00051533 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          []          
           {'ibc0': {'u': 4.4753404e-05 * mmolar}},                                           {'ibc0': {'u': 4.4753404e-05 * mmolar}},                                                      
           {'ibc1': {'u': 0.00127289 * mmolar}}]                                              {'ibc1': {'u': 0.00127289 * mmolar}}]                                                         
3000      [0.00062482 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          [0.00038163 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          []          
           {'ibc0': {'u': 1.1394843e-05 * mmolar}},                                           {'ibc0': {'u': 1.1394843e-05 * mmolar}},                                                      
           {'ibc1': {'u': 0.00050347 * mmolar}}]                                              {'ibc1': {'u': 0.00050347 * mmolar}}]                                                         
4000      [0.00029539 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          [0.00020541 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          []          
           {'ibc0': {'u': 1.0836469e-06 * mmolar}},                                           {'ibc0': {'u': 1.0836469e-06 * mmolar}},                                                      
           {'ibc1': {'u': 0.0001564 * mmolar}}]                                               {'ibc1': {'u': 0.0001564 * mmolar}}]                                                          
5000      [0.00010554 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,          [8.63514e-05 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,         []          
           {'ibc0': {'u': 5.5338234e-08 * mmolar}},                                           {'ibc0': {'u': 5.5338234e-08 * mmolar}},                                                      
           {'ibc1': {'u': 4.1387873e-05 * mmolar}}]                                           {'ibc1': {'u': 4.1387873e-05 * mmolar}}]                                                      
6000      [3.261345e-05 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        [3.0046363e-05 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 4.189261e-08 * mmolar}},                                            {'ibc0': {'u': 4.189261e-08 * mmolar}},                                                       
           {'ibc1': {'u': 1.1832751e-05 * mmolar}}]                                           {'ibc1': {'u': 1.1832751e-05 * mmolar}}]                                                      
7000      [1.0917592e-05 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [1.06402085e-05 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,      []          
           {'ibc0': {'u': 6.6463612e-09 * mmolar}},                                           {'ibc0': {'u': 6.6463612e-09 * mmolar}},                                                      
           {'ibc1': {'u': 3.1595891e-06 * mmolar}}]                                           {'ibc1': {'u': 3.1595891e-06 * mmolar}}]                                                      
8000      [5.7016923e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [5.549621e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        []          
           {'ibc0': {'u': 1.4118614e-06 * mmolar}},                                           {'ibc0': {'u': 1.4118614e-06 * mmolar}},                                                      
           {'ibc1': {'u': 2.000358e-06 * mmolar}}]                                            {'ibc1': {'u': 2.000358e-06 * mmolar}}]                                                       
9000      [3.0069625e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [2.992862e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        []          
           {'ibc0': {'u': 6.584769e-09 * mmolar}},                                            {'ibc0': {'u': 6.584769e-09 * mmolar}},                                                       
           {'ibc1': {'u': 5.5307254e-07 * mmolar}}]                                           {'ibc1': {'u': 5.5307254e-07 * mmolar}}]                                                      
10000     [2.0530574e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [2.0282348e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 1.3601655e-08 * mmolar}},                                           {'ibc0': {'u': 1.3601655e-08 * mmolar}},                                                      
           {'ibc1': {'u': 5.196718e-07 * mmolar}}]                                            {'ibc1': {'u': 5.196718e-07 * mmolar}}]                                                       
11000     [1.5368364e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [1.4977886e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 8.276815e-08 * mmolar}},                                            {'ibc0': {'u': 8.276815e-08 * mmolar}},                                                       
           {'ibc1': {'u': 5.301843e-07 * mmolar}}]                                            {'ibc1': {'u': 5.301843e-07 * mmolar}}]                                                       
12000     [1.1885431e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [1.1730367e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 1.5201218e-08 * mmolar}},                                           {'ibc0': {'u': 1.5201218e-08 * mmolar}},                                                      
           {'ibc1': {'u': 5.8795325e-07 * mmolar}}]                                           {'ibc1': {'u': 5.8795325e-07 * mmolar}}]                                                      
13000     [9.736607e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        [9.640654e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        []          
           {'ibc0': {'u': 1.3303917e-08 * mmolar}},                                           {'ibc0': {'u': 1.3303917e-08 * mmolar}},                                                      
           {'ibc1': {'u': 6.254742e-07 * mmolar}}]                                            {'ibc1': {'u': 6.254742e-07 * mmolar}}]                                                       
14000     [8.2432285e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [8.188007e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        []          
           {'ibc0': {'u': 1.3247805e-08 * mmolar}},                                           {'ibc0': {'u': 1.3247805e-08 * mmolar}},                                                      
           {'ibc1': {'u': 6.5696383e-07 * mmolar}}]                                           {'ibc1': {'u': 6.5696383e-07 * mmolar}}]                                                      
15000     [6.123539e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        [6.9687558e-06 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 8.313561e-06 * mmolar}},                                            {'ibc0': {'u': 8.313561e-06 * mmolar}},                                                       
           {'ibc1': {'u': 8.073111e-06 * mmolar}}]                                            {'ibc1': {'u': 8.073111e-06 * mmolar}}]                                                       
16000     [6.50423e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,         [6.7078685e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 8.236504e-08 * mmolar}},                                            {'ibc0': {'u': 8.236504e-08 * mmolar}},                                                       
           {'ibc1': {'u': 8.032191e-07 * mmolar}}]                                            {'ibc1': {'u': 8.032191e-07 * mmolar}}]                                                       
17000     [5.692829e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        [5.758455e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        []          
           {'ibc0': {'u': 1.7372976e-09 * mmolar}},                                           {'ibc0': {'u': 1.7372976e-09 * mmolar}},                                                      
           {'ibc1': {'u': 7.4665815e-07 * mmolar}}]                                           {'ibc1': {'u': 7.4665815e-07 * mmolar}}]                                                      
18000     [5.349737e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        [5.5260807e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 4.742464e-09 * mmolar}},                                            {'ibc0': {'u': 4.742464e-09 * mmolar}},                                                       
           {'ibc1': {'u': 8.103133e-07 * mmolar}}]                                            {'ibc1': {'u': 8.103133e-07 * mmolar}}]                                                       
19000     [4.710544e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,        [4.7181476e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 1.8345938e-08 * mmolar}},                                           {'ibc0': {'u': 1.8345938e-08 * mmolar}},                                                      
           {'ibc1': {'u': 7.362613e-07 * mmolar}}]                                            {'ibc1': {'u': 7.362613e-07 * mmolar}}]                                                       
20000     [4.3603515e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       [4.3779076e-07 * 10.0^0 * (meter2 / second * (mmolar / meter) / meter) ** 2,       []          
           {'ibc0': {'u': 1.614938e-08 * mmolar}},                                            {'ibc0': {'u': 1.614938e-08 * mmolar}},                                                       
           {'ibc1': {'u': 7.4421376e-07 * mmolar}}]                                           {'ibc1': {'u': 7.4421376e-07 * mmolar}}]                                                      

Best trainer at step 20000:
  train loss: 1.20e-06
  test loss: 1.20e-06
  test metric: []

'train' took 8.478413 s
<pinnx.Trainer at 0x1b2eee86c10>

Verify the results:

x = geom.uniform_points(500)
yhat = model.predict(x)
uhat, khat = yhat['u'].mantissa, yhat['k'].mantissa
x = x['x'].mantissa

ktrue = k(x)
print("l2 relative error for k: " + str(pinnx.metrics.l2_relative_error(khat, ktrue)))

plt.figure()
plt.plot(x, ktrue, "-", label="k_true")
plt.plot(x, khat, "--", label="k_NN")
plt.legend()
plt.show()

utrue = res.sol(x)[0]
print("l2 relative error for u: " + str(pinnx.metrics.l2_relative_error(uhat, utrue)))

plt.figure()
plt.plot(x, utrue, "-", label="u_true")
plt.plot(x, uhat, "--", label="u_NN")
plt.legend()
plt.show()
l2 relative error for k: 0.030291751
../_images/633df16d6471316840242d84c9b1ad1f4063aaff9409d38554b217cfa26e8fe1.png
l2 relative error for u: 0.00095011527
../_images/56754add229d7cca9cb3e74febd73bffb2accf754a62311cf0bd7ea9c41f7010.png