Skip to contents

Introduction

In this vignette we will introduce how to solve some basic PDEs on metric graphs.

For details on the construction of metric graphs, see Working with metric graphs

For further details on data manipulation on metric graphs, see Data manipulation on metric graphs

Constructing the graph and the mesh

We begin by loading the rSPDE and MetricGraph packages:

As an example, we consider the logo graph

  graph <- metric_graph$new(perform_merges = TRUE, 
                            tolerance = list(edge_edge = 1e-3, 
                                             vertex_vertex = 1e-3, 
                                             edge_vertex = 1e-3))
  graph$plot()

To construct a FEM approximation of a PDE on this graph, we first must construct a mesh on the graph.

  graph$build_mesh(h = 0.2)
  graph$plot(mesh=TRUE)

In the command build_mesh, the argument h decides the largest spacing between nodes in the mesh.

Example with the Helmoltz equation

As a simple first problem, let us consider solving the Helmholtz equation (κ2Δ)u=f, (\kappa^2 - \Delta) u = f, where the operator is equipped with Kirchhoff vertex conditions and we assume that κ=1\kappa = 1. Discretizing the problem through a Galerkin FEM method yields a solution uh(s)=i=1nuiφi(s)u_h(s) = \sum_{i=1}^n u_i \varphi_i(s) where {φi}\{\varphi_i\} are the hat functions induced by the mesh and 𝐮=(u1,,un)\mathbf{u} = (u_1,\ldots, u_n) are the weights, which solve (κ2𝐂+𝐆)𝐮=𝐟. (\kappa^2 \mathbf{C} + \mathbf{G}) \mathbf{u} = \mathbf{f}. Here 𝐂\mathbf{C} is the mass matrix with elements Cij=(φi,φj)L2(Γ)C_{ij} = (\varphi_i, \varphi_j)_{L_2(\Gamma)}, 𝐆\mathbf{G} is the stiffness matrix with elements Gij=(φi,φj)L2(Γ)G_{ij} = (\nabla \varphi_i, \nabla \varphi_j)_{L_2(\Gamma)}, and fi=(φi,f)L2(Γ)f_i = (\varphi_i, f)_{L_2(\Gamma)}. The mass and stiffness matrices can be computed as follows

  graph$compute_fem()
  G <- graph$mesh$G
  C <- graph$mesh$C

Suppose that f(s)=x2y2f(s) = x^2 - y^2, where (x,y)(x,y) are the Euclidean coordinates of the location ss on the graph. We approximate this function as being piecewise linear on the mesh and compute 𝐟=𝐂f\mathbf{f} = \mathbf{C}\bar{f}, where f\bar{f} is a vector with ff evaluated at the mesh nodes

x <- graph$mesh$V[, 1]
y <- graph$mesh$V[, 2]
fbar <- x^2 - y^2
C <- graph$mesh$C
f <- C%*%fbar
graph$plot_function(fbar, vertex_size = 0)

We now solve the system as

kappa <- 1
L <- kappa^2*C + G
u <- solve(L,f)
graph$plot_function(u,vertex_size=0)

Example with a Poisson equation

Let us now consider the Poisson equation Δu=f, -\Delta u = f, where the operator is equipped with Kirchhoff vertex conditions at the vertices of degree d2d\geq 2 and homogeneous Dirichlet vertex conditions at the vertices of degree d=1d=1 (to guarantee a unique solution). Again using a Galerkin FEM method yields a solution uh(s)=i=1nuiφi(s)u_h(s) = \sum_{i=1}^n u_i \varphi_i(s) where {φi}\{\varphi_i\} are the hat functions induced by the mesh and 𝐮=(u1,,un)\mathbf{u} = (u_1,\ldots, u_n) are the weights, which now solve 𝐆𝐮=𝐟. \mathbf{G} \mathbf{u} = \mathbf{f}. The difference is now that we must handle the Dirichlet conditions. By default, the stiffness matrix is computed assuming Kirchhoff vertex conditions at all nodes. Go obtain the matrix corresponding to Dirichlet vertex conditions, we simply have to remove the rows and columns corresponding to the degree 1 vertices:

  n.mesh <- dim(graph$mesh$V)[1]
  ind <- setdiff(1:n.mesh, which(graph$get_degrees()==1))
  Gd <- G[ind,ind]

Suppose that f(s)=1(x>4)f(s) = 1(x > 4), where (x,y)(x,y) are the Euclidean coordinates of the location ss on the graph. We again approximate this function as being piecewise linear on the mesh and compute 𝐟=𝐂f\mathbf{f} = \mathbf{C}\bar{f}, where 𝐂\mathbf{C} is the mass matrix with elements Cij=(φi,φj)L2(Γ)C_{ij} = (\varphi_i, \varphi_j)_{L_2(\Gamma)} and f\bar{f} is a vector with ff evaluated at the mesh nodes

fbar <- x> 4
f <- C%*%fbar
graph$plot_function(fbar, vertex_size = 0)

We now solve the system as

f.int <- f[ind]           #Get the load vector at the internal nodes
u.int <- solve(Gd,f.int)  #solve for u at the internal nodes
u <- rep(0, n.mesh)
u[ind] <- u.int           #add the solution to the internal nodes
graph$plot_function(u,vertex_size=0)

A problem with a random diffusion coefficient

As another example, let us consider the problem (κ2(eu))u=f, (\kappa^2 - \nabla\cdot(e^u\nabla)) u = f, where uu is a Gaussian process. The only difference to the first example is that we now need to evaluate a stiffness matrix with elements Gij(u)=(euφi,φj)L2(Γ) G_{ij}(u) = (e^u \nabla \varphi_i, \nabla \varphi_j)_{L_2(\Gamma)} By assuming that eue^u is piecewise linear on the mesh, we can write this matrix as 𝐆(u)=12(𝐃(u)𝐆+𝐆𝐃(u)) \mathbf{G}(u) = \frac12(\mathbf{D}(u)\mathbf{G} + \mathbf{G}\mathbf{D}(u)) where 𝐃(u)\mathbf{D}(u) is a diagonal matrix with the values of eue^u at the mesh nodes on the diagonal.

Let us now simulate a Gaussian Whittle–Matérn field uu at the mesh nodes.

u <- sample_spde(range = 4, sigma = 0.3, alpha = 1, graph = graph, type = "mesh")
graph$plot_function(X = exp(u), vertex_size = 0)

We can now build the matrix 𝐆(u)\mathbf{G}(u):

Du <- Diagonal(n.mesh, exp(u))
Gu <- (G%*%Du + Du%*%G)/2

Let us use the same right hand side as in the first example and solve the PDE with κ=1\kappa = 1:

fbar <- x^2 - y^2
f <- C%*%fbar
kappa <- 1
L <- kappa^2*C + Gu
u <- solve(L,f)
graph$plot_function(u,vertex_size=0)

Solving a fractional-order PDE using rSPDE

Finally, let us consider solving a fractional-order version of the PDE in the previous example: (κ2(eu))βu=f, (\kappa^2 - \nabla\cdot(e^u\nabla))^{\beta} u = f,

To solve this, we can use the rSPDE package, to obtain a rational approximation of the fractional power. Specifically, we use the operator-based approach by Bolin and Kirchner (2020), which results in an approximation of the original PDE which is of the form Plu(s)=Prf(s)P_l u(s) = P_r f(s), where PlP_l and PrP_r are non-fractional operators defined in terms of polynomials plp_l and prp_r. The order of prp_r is given by mm and the order of plp_l is m+mβm + m_{\beta} where mβm_{\beta} is the integer part of β\beta if β>1\beta>1 and mβ=1m_{\beta} = 1 otherwise.

Having already computed the FEM approximation of the operator L=κ2(eu)L = \kappa^2 - \nabla\cdot(e^u\nabla), we can use the function fractional.operators to define the rational approximation. For this we just have to provide the operator, mass matrix, β\beta, the order of the rational approximation, and a scale factor which is a lower bound for the eigenvalues of LL. In this case, a lower bound is given by κ2\kappa^2.

library(rSPDE)
beta <- 1.5
op <- fractional.operators(L, beta, C, scale.factor = kappa^2, m = 1)

We can now solve the problem as follows

u <- Pl.solve(op, Pr.mult(op, f))
graph$plot_function(u,vertex_size=0)

References

Bolin, David, and Kristin Kirchner. 2020. “The Rational SPDE Approach for Gaussian Random Fields with General Smoothness.” Journal of Computational and Graphical Statistics 29 (2): 274–85.