\documentclass[article,nojss]{jss} \DeclareGraphicsExtensions{.pdf,.eps,.jpeg} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Add-on packages and fonts \usepackage{graphicx} \usepackage{amsmath} \newcommand{\noun}[1]{\textsc{#1}} %% Bold symbol macro for standard LaTeX users \providecommand{\boldsymbol}[1]{\mbox{\boldmath $#1$}} %% Because html converters don't know tabularnewline \providecommand{\tabularnewline}{\\} \usepackage{array} % tabel commands \setlength{\extrarowheight}{0.1cm} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% User specified LaTeX commands. \newcommand{\rt}{\textbf{\textsf{ReacTran }}} \newcommand{\rs}{\textbf{\textsf{rootSolve }}} \newcommand{\R}{\proglang{R }} \newcommand{\ds}{\textbf{\textsf{deSolve }}} \newcommand{\dd}{\textbf{\textsf{ddesolve }}} \newcommand{\rb}[1]{\raisebox{1.5ex}{#1}} \title{Solving partial differential equations, using \R package \rt} \Plaintitle{Solving partial differential equations using R package ReacTran} \Keywords{Partial Differential Equations, hyperbolic, parabolic, elliptic, \R} \Plainkeywords{Partial Differential Equations, hyperbolic, parabolic, elliptic, R} \author{Karline Soetaert and Filip Meysman\\ Centre for Estuarine and Marine Ecology\\ Netherlands Institute of Ecology\\ The Netherlands } \Plainauthor{Karline Soetaert and Filip Meysman} \Abstract{ \R-package \rt \citep{ReacTran} contains functions to solve reactive-transport equations, as used e.g. in the environmental sciences. Essentially, it \begin{enumerate} \item Provides functions that subdivide the spatial extent into a number of discrete grid cells. \item Approximates the advective-diffusive transport term by finite differences or finite volumes. \end{enumerate} The main package vignette \citep{ReacTran-vignette} explains how \rt can be used to model reaction-transport phenomena. However, the functions from \rt can be use to solve more general types of partial differential equations ($\leq$ order 2). In this vignette, show how the package can be used to solve partial differential equations of the parabolic, hyperbolic and elliptic type, providing one example each. } %% The address of (at least) one author should be given %% in the following format: \Address{ Karline Soetaert\\ Centre for Estuarine and Marine Ecology (CEME)\\ Netherlands Institute of Ecology (NIOO)\\ 4401 NT Yerseke, Netherlands\\ E-mail: \email{k.soetaert@nioo.knaw.nl}\\ URL: \url{http://www.nioo.knaw.nl/ppages/ksoetaert}\\ \\ Filip Meysman\\ Centre for Estuarine and Marine Ecology (CEME)\\ Netherlands Institute of Ecology (NIOO)\\ 4401 NT Yerseke, Netherlands\\ E-mail: \email{f.meysman@nioo.knaw.nl}\\ URL: \url{http://www.nioo.knaw.nl/ppages/fmeysman}\\ } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% R/Sweave specific LaTeX commands. %% need no \usepackage{Sweave} %\VignetteIndexEntry{Solving parabolic, hyperbolic and elliptic partial differential equations in R} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Begin of the document \begin{document} \SweaveOpts{engine=R,eps=FALSE} \SweaveOpts{keep.source=TRUE} <>= library(ReacTran) options(prompt = " ") @ \maketitle \section{Partial differential equations} In \textbf{partial differential equations }(PDE), the function has several independent variables (e.g. time and depth) and contains their partial derivatives. A first step to solve partial differential equations (PDE), is to discretise one or more of the independent variables. Usually, the independent variable ``time'' is not discretised, while other variables (e.g. spatial axes) are discetised, so that a set of ODE is obtained, which can be solved with appropriate initial values solvers from package \ds \citep{deSolve}. This technique, the method-of-lines, applies to hyperbolic and parabolic PDEs. For time-invariant problems, usually all independent variables are discretised, and the resulting algebraic equations solved with root-solving functions from package \rs \citep{rootSolve}. Functions \code{tran.1D}, \code{tran.2D}, and \code{tran.3D} from \R package \pkg{ReacTran} implement finite difference approximations of the general diffusive-advective transport equation, which for 1-D is: \begin{eqnarray*} -\frac{1}{A_x \xi_x}\cdot (\frac{\partial}{\partial x} A_x \cdot (- D \cdot \frac{\partial \xi_x C}{\partial x}) - \frac{\partial}{\partial x} (A_x \cdot v \cdot \xi_x C)) \end{eqnarray*} Here $D$ is the "diffusion coefficient", $v$ is the "advection rate", and $A_x$ and $\xi$ are the surface area and volume fraction respectively. Assuming that $A$, $\xi$, $D$ and $v$ are constant along $x$, we can rewrite this in a more general form: \begin{eqnarray*} D \frac{\partial^2C}{\partial x^2} - u \frac{\partial C}{\partial x} \end{eqnarray*} In which the first term is a second-order, the second term a first-order derivative. \R-function \code{tran.1D} is defined as (simplified): \begin{verbatim} tran.1D <- function (C, C.up = C[1], C.down = C[length(C)], flux.up = NULL, flux.down = NULL, a.bl.up = NULL, a.bl.down = NULL, D = 0, v = 0, AFDW = 1, VF = 1, A = 1, dx, ...) \end{verbatim} where \code{C.up} and \code{C.down} are the upstream and downstream boundary values, \code{flux.up} and \code{flux.down} are the upstream and downstream fluxes, \code{v} and \code{D} are the advection and diffusion coefficient respectively, \code{A} is the surface area, \code{x} contains the grid, and \code{VF} is the volume fraction ($\xi$). For the other arguments, see the help file of \code{tran.1D}. A suitable \emph{grid} can be generated with functions \code{setup.grid.1D} and \code{setup.grid.2D} (there is no corresponding 3D function), while a \emph{property} can be added to this grid using functions \code{setup.prop.1D}, and \code{setup.prop.2D}. These latter two functions are useful to have the variable surface areas, volume fractions, advection and diffusion rates being defined at the correct places of the grid. These functions are defined as (simplified): \begin{verbatim} setup.grid.1D <- function(x.up = 0, x.down = NULL, L = NULL, N = NULL, dx.1 = NULL, ...) setup.grid.2D <- function(x.grid = NULL, y.grid = NULL) setup.prop.1D <- function (func = NULL, value = NULL, xy = NULL, interpolate = "spline", grid, ...) setup.prop.2D <- function (func = NULL, value = NULL, grid, y.func = func, y.value = value, ...) \end{verbatim} \clearpage \section{A parabolic PDE} As an example of the parabolic type, consider the 1-D diffusion-reaction model, in spherical, cylindrical and cartesian coordinates, defined for $r$ in $[0, 10]$: \begin{eqnarray*} \frac{\partial C}{\partial t} &=& \frac{1}{r^2}\cdot \frac{\partial}{\partial r} \left(r^2 \cdot D \cdot \frac{\partial C}{\partial r}\right) - Q \\ \frac{\partial C}{\partial t} &=& \frac{1}{r}\cdot \frac{\partial}{\partial r} \left(r \cdot D \cdot \frac{\partial C}{\partial r}\right) - Q \\ \frac{\partial C}{\partial t} &=& \frac{\partial}{\partial r} \left(D \cdot \frac{\partial C}{\partial r}\right) - Q \end{eqnarray*} with $t$ the time, $r$ the (radial) distance from the origin, $Q$, the consumption rate, and with boundary conditions (values at the model edges): \begin{eqnarray*} \frac{\partial C}{\partial r}_{r=0} &=& 0 \\ C_{r=10} &=& Cext \end{eqnarray*} To solve this model in \R{}, first the 1-D model \code{Grid} is defined; it divides 10 cm (\code{L}) into 1000 equally-sized boxes (\code{N}). <<>>= Grid <- setup.grid.1D(N = 1000, L = 10) @ Next the properties $r$ and $r^2$ are defined on this grid: \SweaveOpts{keep.source=TRUE} <<>>= r <- setup.prop.1D(grid = Grid, func = function(r) r) r2 <- setup.prop.1D(grid = Grid, func = function(r) r^2) @ \SweaveOpts{keep.source=FALSE} The model equation includes a transport term, approximated by \pkg{ReacTran} function \code{tran.1D} and a consumption term (\code{Q}); only the downstream boundary condition, prescribed as a concentration (\code{C.down}) needs to be specified, as the zero-gradient at the upstream boundary is the default: <<>>= library(ReacTran) pde1D <-function(t, C, parms, A = 1) { tran <- tran.1D (C = C, A = A, D = D, C.down = Cext, dx = Grid)$dC list(tran - Q) # the return value: rate of change } @ The model parameters are defined: <<>>= D <- 1 # diffusion constant Q <- 1 # uptake rate Cext <- 20 @ \subsection{Steady-state solution} In a first application, the model is solved to \emph{steady-state}, which retrieves the condition where the concentrations are invariant, e.g. for the cylindrical coordinate case: \[ 0 = \frac{1}{r^2}\cdot \frac{\partial}{\partial r} \left(r^2 \cdot D \cdot \frac{\partial C}{\partial r}\right) - Q \] In \R, steady-state conditions can be estimated using functions from package \rs which implement amongst others a Newton-Raphson algorithm \citep{Press92}. For 1-dimensional models, \code{steady.1D} should be used. The initial ``guess'' of the steady-state solution (\code{y}) is unimportant; here we take simply \code{N} random numbers. Argument \code{nspec = 1} informs the solver that only one component is described. Although a system of 1000 equations needs to be solved, this takes only a fraction of a second: \SweaveOpts{keep.source=TRUE} <<>>= library(rootSolve) Cartesian <- steady.1D(y = runif(Grid$N), func = pde1D, parms = NULL, nspec = 1, A = 1) Cylindrical <- steady.1D(y = runif(Grid$N), func = pde1D, parms = NULL, nspec = 1, A = r) @ <<>>= print(system.time( Spherical <- steady.1D(y = runif(Grid$N), func = pde1D, parms = NULL, nspec = 1, A = r2) )) @ The values of the state-variables (\code{y}) are plotted against the radial distance, in the middle of the grid cells (\code{Grid\$x.mid}). <>= plot (Grid$x.mid, Cartesian$y, type = "l", main = "steady-state PDE", lwd = 3, xlab = "x", ylab = "C", col = "darkgreen", lty = 1) lines(Grid$x.mid, Cylindrical$y, lwd = 3, col="blue", lty = 2) lines(Grid$x.mid, Spherical$y, lwd = 3, col = "red", lty = 3) legend("bottomright", c("cartesian", "cylindrical", "spherical"), col = c("darkgreen", "blue", "red"), lwd = 3, lty = 1:3) @ \setkeys{Gin}{width=0.6\textwidth} \begin{figure} \begin{center} <>= <> @ \end{center} \caption{Steady-state solution of the 1-D diffusion-reaction model} \label{fig:pde} \end{figure} The analytical solutions compare well with the numerical approximation for all three cases: \SweaveOpts{keep.source=TRUE} <<>>= max(abs(Q/6/D*(r2$mid - 10^2) + Cext - Spherical$y)) max(abs(Q/4/D*(r2$mid - 10^2) + Cext - Cylindrical$y)) max(abs(Q/2/D*(r2$mid - 10^2) + Cext - Cartesian$y)) @ Note that there is no automatic error checking/control here, so to reduce this error, the number of boxes can be increased. \subsection{The method of lines} Next the model (for spherical coordinates) is run dynamically for 100 time units using \ds function \code{ode.1D}, and starting with an initially uniform distribution (\code{y = rep(1, Grid$N)}): <<>>= require(deSolve) times <- seq(0, 100, by = 1) system.time( out <- ode.1D(y = rep(1, Grid$N), times = times, func = pde1D, parms = NULL, nspec = 1, A = r2) ) @ Here, \code{out} is a matrix, whose $1^{st}$ column contains the output times, and the next columns the values of the state variables in the different boxes: <<>>= tail(out[, 1:4], n = 3) @ We plot the result using a blue-yellow-red color scheme, and using deSolve's S3 method \code{image}: <>= image(out, grid = Grid$x.mid, xlab = "time, days", ylab = "Distance, cm", main = "PDE", add.contour = TRUE) @ \setkeys{Gin}{width=0.6\textwidth} \begin{figure} %%<