Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

2. PDE-based BIP using CUQIpy and CUQIpy-FEniCS plugin

Here we build a Bayesian inverse problem to infer the conductivity in a 2D unit-square domain modelled by the Poisson equation (applications include EIT problems).

The PDE model is built using FEniCS, then we use CUQIpy-FEniCS to wrap the PDE model to interface it with CUQIpy. We use CUQIpy samplers to solve the PDE-based Bayesian problem.

Table of contents

  • 2.1. Learning objectives

  • 2.2. Building a FEniCS based Poisson problem

  • 2.3. Building and solving the PDE-based Bayesian problem in CUQIpy

  • 2.4. Using gradient-based sampler

2.1. Learning objectives

  • Build a FEniCS-based Poisson problem

  • Build and solve the corresponding PDE-based Bayesian problem in CUQIpy

    • Use Matern covariance to specify the prior

    • Use pCN sampler

  • Use gradient-based sampler

    • Identify the chain rule needed to compute the gradient of the log-likelihood

    • Use NUTS sampler

⚠️ Note:
  • This notebook is under the GPLv3.0 license.

  • This notebook was run on a machine locally and not using github actions for this book. To run this notebook on your machine, you need to have CUQIpy-FEniCS installed.

Import required libraries and classes

2.2. Building a FEniCS based Poisson problem

In this section, we use FEniCS python library to build a PDE model.

The PDE model we consider here is a 2D steady-state problem (Poisson):

−∇⋅(κ(ξ)∇u(ξ))=f(ξ)        ξ=[ξ1,ξ2]T∈(0,1)×(0,1),u(ξ)=0        on  ξ1=0u(ξ)=0        on  ξ1=1κ(ξ)∇u(ξ)⋅n=0        on  ξ2=0κ(ξ)∇u(ξ)⋅n=0        on  ξ2=1\begin{align*} -\nabla \cdot \left(\kappa(\xi) \nabla u(\xi)\right) &= f(\xi) \;\;\;\;\xi=[\xi^1, \xi^2]^\mathrm{T} \in (0,1)\times(0,1),\\ u(\xi)&=0 \;\;\;\;\mathrm{on}\; \xi^1=0\\ u(\xi)&=0 \;\;\;\;\mathrm{on}\; \xi^1=1 \\ \kappa(\xi)\nabla u(\xi)\cdot n&=0 \;\;\;\;\mathrm{on}\; \xi^2=0 \\ \kappa(\xi)\nabla u(\xi)\cdot n&=0 \;\;\;\;\mathrm{on}\; \xi^2=1 \end{align*}
  • where κ(ξ)\kappa(\xi) is the conductivity, u(ξ)u(\xi) is the PDE solution (potential), f(ξ)f(\xi) is the source term.

  • We use the parameterization κ(ξ)=em(ξ)\kappa(\xi) = e^{m(\xi)}, to ensure positivity of the inferred conductivity (more on this later).

  • We denote the discretized system that we need to solve as A(m)U=F\mathbf{A}(\mathbf{m})\mathbf{U} = \mathbf{F}

    • A\mathbf{A} is the discretized diffusion differential operator

    • m\mathbf{m} is the discretized unknown parameter (log conductivity)

    • U\mathbf{U} is the discretized solution (the potential)

    • F\mathbf{F} is the discretized RHS (the source term)

2.2.1. The discretization

We use finite element discretization of the model above where the solution and the parameters are approximated in a second and first order Lagrange polynomial space, respectively.

Using finite element formulation requires building the weak form of the PDE, Demkowicz (2023). To formulate the weak form, we multiply the PDE by a test function and integrate by parts and substitute the Neumann boundary conditions above (the last two equations above).

For formulating the weak form, see for example this reference.

2.2.2. Set up mesh

We create a 2D FEniCS mesh (unit square mesh) on which the finite element solution is discretized.

<Figure size 640x480 with 1 Axes>

2.2.3. Set up function spaces

We define the function spaces on which the PDE solution uu and the quantity we want to quantify (log conductivity mm) are discretized. The function spaces are, respectively, second order Lagrange polynomial space and first order Lagrange polynomial space.

2.2.4. Set up Dirichlet boundary conditions

We create the Dirichlet boundary conditions:

u(ξ)=0        on  ξ1=0u(ξ)=0        on  ξ1=1\begin{align*} u(\xi)&=0 \;\;\;\;\mathrm{on}\; \xi^1=0\\ u(\xi)&=0 \;\;\;\;\mathrm{on}\; \xi^1=1 \\ \end{align*}

2.2.5. Set up source term

We set the source term f(ξ)f(\xi) to a constant value 1.

2.2.6. Set up PDE variational form

After parametrizing conductivity using κ(ξ)=em(ξ)\kappa(\xi) = e^{m(\xi)}, the variational form of the Poisson PDE above is:

∫(0,1)×(0,1)(em(ξ)∇u(ξ)⋅∇p(ξ)−f(ξ)p(ξ))dξ\begin{align*} \int_{(0,1)\times(0,1)} \left( e^{m(\xi)} \nabla u(\xi) \cdot \nabla p(\xi) - f(\xi)p(\xi) \right){d\xi} \end{align*}

where p(ξ)p(\xi) is a test function. We create a function that takes the unknown parameters m, and a representation of the solution function u and a test function p and returns the weak form.

2.2.7. Create CUQIpy PDE object

We bundle the FEniCS PDE model that we built in a SteadyStateLinearFEniCSPDE object:

Let us try solving this PDE for m(ξ)=1m(\xi)=1, first we create the parameter:

Let us check m_1 value at a given point (0.5, 0.8)

1.0

Now let us use the object we created PDE to assemble (build the discretized linear system) and solve the PDE

Plot the solution

<Figure size 640x480 with 2 Axes>

2.3. Building and solving the Bayesian inverse problem in CUQIpy

The goal is to infer the log conductivity profile m(ξ)m(\xi) given observed data yobsy^\mathrm{obs}. These observation can be of the potential directly, i.e. yobs=u(ξ)y^\mathrm{obs}=u(\xi), or a function of the potential.

The data yobsy^\mathrm{obs} is then given by:

yobs=G(m)+ηy^\mathrm{obs} = \mathcal{G}(m) + \eta

where

  • η\eta is the measurement noise

  • G\mathcal{G} is the forward model operator which maps mm to the observations.

2.3.1. Create domain geometry

We model mm as a Matern-class random field which is a Gaussian random field with a Matern covariance function that induces spatial correlation, Roininen et al. (2014). This lead to the parametrization (Karhunen-Loève (KL) expansion):

m(ξ)=∑i=0∞λixiei(ξ)≈∑i=0nKLλixiei(ξ)m(\xi) = \sum_{i=0}^{\infty} \sqrt{\lambda_i}x_i e_i(\xi) \approx \sum_{i=0}^{n_\mathrm{KL}} \sqrt{\lambda_i}x_i e_i(\xi)
  • λi \lambda_i and ei e_i are the eigenvalues and eigenvectors of the Matern covariance operator.

  • nKLn_\mathrm{KL} is the number of KL terms used to approximate the random field m(ξ)m(\xi) (we choose nKL=32n_\mathrm{KL}=32 here).

  • xi∼Gaussian(0,1)x_i\sim \mathrm{Gaussian}(0,1) are i.i.d. standard normal random variables.

  • Now, xix_i are the unknown parameters that parameterize the conductivity field m(ξ)m(\xi).

To define the Matern field (which represents the domain of our forward model), we use MaternKLExpansion geometry and define the field as below, see Alghamdi et al. (2024) for further information about the Matern field geometry implementation in the CUQIpy-FEniCS plugin.

2.3.2. Create range geometry

We create the range geometry which represents the forward model output (the solution uu in the entire domain in this case)

2.3.3. Create CUQIpy forward model

Now we use PDEModel which is an object that belongs to the CUQIpy library and is agnostic to the FEniCS code (FEniCS code is abstracted away in the PDE object and the geometries).

2.3.4. Create prior

We create the prior distribution, which is a distribution of the expansion coefficients xix_i

We can plot prior samples (realizations of Matern class Gaussian random field)

<Figure size 1920x1440 with 5 Axes>

2.3.5. Create exact solution and exact data

We create an exact solution (for simplification in this notebook, the exact solution is created from a prior sample):

<Figure size 640x480 with 2 Axes>

Create synthesized data that corresponds to the exact_solution

<Figure size 640x480 with 2 Axes>

2.3.6. Create likelihood and data

We create the data distribution

CUQI Gaussian. Conditioning variables ['x'].

And we create the data

We plot the data

<Figure size 640x480 with 2 Axes>

We create the likelihood function:

2.3.7. Create the posterior

We create the posterior distribution

2.3.8. Sample the posterior

Create a preconditioned Crank-Nicolson (pCN) sampler

Sample the posterior

Warmup: 100%|██████████| 10/10 [00:00<00:00, 60.83it/s, acc rate: 40.00%]
Sample: 100%|██████████| 100/100 [00:01<00:00, 61.14it/s, acc rate: 2.00%]

We plot the samples mean (and the exact solution for reference)

<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>

We look at the trace plot

Selecting 5 randomly chosen variables
array([[<Axes: title={'center': 'v15'}>, <Axes: title={'center': 'v15'}>], [<Axes: title={'center': 'v23'}>, <Axes: title={'center': 'v23'}>], [<Axes: title={'center': 'v31'}>, <Axes: title={'center': 'v31'}>], [<Axes: title={'center': 'v4'}>, <Axes: title={'center': 'v4'}>], [<Axes: title={'center': 'v7'}>, <Axes: title={'center': 'v7'}>]], dtype=object)
<Figure size 1200x1000 with 10 Axes>

We plot the credible interval

<Figure size 640x480 with 1 Axes>

The sampling did not go so well. We need to use a better sampling technique.

2.4. Using gradient-based sampler

Having additional information about the (log of the) posterior PDF, such as its gradient, can be utilized to improve the sampling efficiency. In section 1. Sampling with CUQIpy: five little stories, we present a number of sampling techniques that utilize the gradient information including ULA, MALA and NUTS samplers. Here we explore the NUTS sampler which is a gradient-based sampler to sample the posterior distribution.

2.4.1. The chain rule

We compute the gradient of the log-posterior with respect to the unknown parameter xx using the chain rule:

∇xlogppost(x)∝∇xlogplikelihood(G(m(x)))+∇xlogpprior(x),\nabla_x \mathrm{log}p_\mathrm{post}(x) \propto \nabla_x \mathrm{log}p_\mathrm{likelihood}(\mathcal{G}(m(x))) + \nabla_x \mathrm{log}p_\mathrm{prior}(x),

where plikelihoodp_\mathrm{likelihood} is the likelihood density function, ppriorp_\mathrm{prior} is the prior probability density function, ppostp_\mathrm{post} is the posterior probability density function and mm is the Matern field.

We have the maps:

  • z:=m(x)z := m(x), implemented by the domain geometry MaternKLExpansion.

  • y:=G(z)y := \mathcal{G}(z) , implemented by the forward model PDEModel.

By the chain rule we have (for the likelihood part):

∇xlogplikelihood(y)=Jz,xT(x)Jy,zT(z)∇ylogplikelihood(y),\nabla_x \mathrm{log}p_\mathrm{likelihood}(y) = J_{z,x}^T(x) J_{y, z}^T(z) \nabla_y \mathrm{log}p_\mathrm{likelihood}(y),

where Jz,xJ_{z,x} is the Jacobian of the map z=m(x)z=m(x) with respect to xx, Jy,zJ_{y, z} is the Jacobian of the map y=G(z)y=\mathcal{G}(z) with respect to zz and ∇ylogplikelihood(y)\nabla_y \mathrm{log}p_\mathrm{likelihood}(y) is the gradient of the log-likelihood with respect to yy.

  • We use adjoint-based method to compute the matrix vector product Jy,zT(z)vJ_{y, z}^T(z)v for some given vector vv.

    • Costs one forward solve and one adjoint solve (cheaper than finite difference approximation)

This is done automatically by CUQIpy-FEniCS

2.4.2. Set the adjoint problem boundary conditions

To compute the gradient using adjoint based method, we need to define the adjoint problem (which the PDE object infers) and derive the adjoint problem boundary conditions, see Gunzburger (2002) for adjoint-based gradient derivation.

We create the adjoint problem boundary conditions.

We recreate the PDE object to use the adjoint boundary conditions. We then again create the PDEModel, the data distribution, and the posterior distribution to use the new PDE object.

2.4.3. Check the gradient correctness at an input xtestx_\mathrm{test}

We check the log posterior gradient correctness at an input xtestx_\mathrm{test} by comparing the gradient computed by CUQIpy-FEniCS using adjoint based method and the gradient computed using scipy optimize.approx_fprime method.

We first create the input vector xtestx_\mathrm{test}

Compute the posterior gradient using CUQIpy-FEniCS

Posterior gradient (cuqi.model)

Compute the approximate gradient using optimize.approx_fprime

Scipy approx

Plot both gradients

<Figure size 640x480 with 1 Axes>

2.4.4. Use gradient based sampler (NUTS)

Specify a gradient-based sampler (we use NUTS here)

Sample using NUTS (this may take a little while)

Warmup: 100%|██████████| 10/10 [00:04<00:00,  2.47it/s, acc rate: 60.00%]
Sample: 100%|██████████| 200/200 [13:03<00:00,  3.92s/it, acc rate: 100.00%]

Plot the mean and the exact solution

<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>

Plot trace

Selecting 5 randomly chosen variables
array([[<Axes: title={'center': 'v1'}>, <Axes: title={'center': 'v1'}>], [<Axes: title={'center': 'v14'}>, <Axes: title={'center': 'v14'}>], [<Axes: title={'center': 'v31'}>, <Axes: title={'center': 'v31'}>], [<Axes: title={'center': 'v6'}>, <Axes: title={'center': 'v6'}>], [<Axes: title={'center': 'v7'}>, <Axes: title={'center': 'v7'}>]], dtype=object)
<Figure size 1200x1000 with 10 Axes>

We also plot the credible interval

<Figure size 640x480 with 1 Axes>

We note that the results have improved compared to using the pCN sampler in the sense that the inferred mean is closer to the exact solution and the credible intervals for the KL expansion coefficients are narrower and mostly contain the coefficients of the exact solution. This improvement is due to utilizing gradient information of the problem.

References
  1. Demkowicz, L. F. (2023). Mathematical theory of finite elements. SIAM. 10.1137/1.9781611977738
  2. Roininen, L., Huttunen, J. M., & Lasanen, S. (2014). WHITTLE-MATÉRN PRIORS FOR BAYESIAN STATISTICAL INVERSION WITH APPLICATIONS IN ELECTRICAL IMPEDANCE TOMOGRAPHY. Inverse Problems & Imaging, 8(2), 561.
  3. Alghamdi, A. M. A., Riis, N. A. B., Afkham, B. M., Uribe, F., Christensen, S. L., Hansen, P. C., & Jørgensen, J. S. (2024). CUQIpy: II. Computational uncertainty quantification for PDE-based inverse problems in Python. Inverse Problems, 40(4), 045010. 10.1088/1361-6420/ad22e8
  4. Gunzburger, M. D. (2002). Perspectives in flow control and optimization. SIAM.