There is a fairly large literature on reaction-diffusion modelling using partial differential equations (PDEs). There is also a fairly large literature on stochastic modelling of coupled chemical reactions, which account for the discreteness of reacting species at low concentrations. There is some literature on combining the two, to form stochastic reaction-diffusion systems, but much less.

In this post we will look at one approach to the stochastic reaction-diffusion problem, based on an underlying stochastic process often described by the *reaction diffusion master equation* (RDME). We will start by generating exact realisations from this process using the *spatial Gillespie algorithm*, before switching to a continuous stochastic approximation often known as the *spatial chemical Langevin equation* (spatial CLE). For fine discretisations, this spatial CLE is just an explicit numerical scheme for an associated reaction-diffusion *stochastic partial differential equation* (SPDE), and we can easily contrast such SPDE dynamics with their deterministic PDE approximation. We will investigate using simulation, based on my Scala library, scala-smfsb, which accompanies the third edition of my textbook, Stochastic modelling for systems biology, as discussed in previous posts.

All of the code used to generate the plots and movies in this post is available in my blog repo, and is very simple to build and run.

My favourite toy coupled chemical reaction network is the Lotka-Volterra predator-prey system, presented as the three reactions

with representing the *prey* species and the *predator*. I showed how to simulate realisations from this process using the Scala library in the previous post. Here we will consider simulation of this model in 2d, and simulate exact realisation from the appropriate RDME using the spatial Gillespie algorithm. Full runnable code for this simulation is here, but the key lines are:

val r = 100; val c = 120 val model = SpnModels.lv[IntState]() val step = Spatial.gillespie2d(model, DenseVector(0.6, 0.6), maxH=1e12) val x00 = DenseVector(0, 0) val x0 = DenseVector(50, 100) val xx00 = PMatrix(r, c, Vector.fill(r*c)(x00)) val xx0 = xx00.updated(c/2, r/2, x0) val s = Stream.iterate(xx0)(step(_,0.0,0.1))

which sets up an infinite lazy `Stream`

of states on a 100×120 grid over time steps of 0.1 units with diffusion rates of 0.6 for both species. We can then map this to a stream of images and visualise it using my scala-view library (described in this post). Running gives the following output:

The above image is the final frame of a movie which can be viewed by clicking on the image. In the simulation, blue represents the prey species, , and red represents the predator, . The simulation is initialised with a few prey and predators in the central pixel. At each time step of the simulation, either a reaction or a diffusion event may occur. If diffusion occurs, an individual moves from its current location to one of the four adjacent pixels. This algorithm is extremely computationally intensive, however well it is implemented. The implementation used here (using the function `Spatial.gillespie2d`

in the `scala-smfsb`

library) is quite inefficient. A more efficient implementation would use the next subvolume method or similar algorithm. But since every reaction event is simulated sequentially, this algorithm is always going to be intolerably slow for most interesting problems.

The spatial CLE effectively approximates the true RDME dynamics with a set of coupled stochastic differential equations (SDEs) on the spatial grid. This can be interpreted as an explicit scheme for numerically integrating an SPDE. But this numerical scheme is *much* more efficient, allowing sensible time-stepping of the process, and vectorises and parallelises nicely. The details are in my book, but the Scala implementation is here. Diffusion is implemented efficiently and in parallel using the comonadic approach that I’ve described previously. We can quickly and easily generate large simulations using the spatial CLE. Here is a movie generated on a 250×300 grid.

Again, clicking on the frame should give the movie. We see that although the quantitative details are slightly different to the exact algorithm, the essential qualitative behaviour of the system is captured well by the spatial CLE. Full code for this simulation is here.

If we remove all of the noise terms from the spatial CLE, we get a set of coupled ODEs, which again, may be interpreted as a numerical scheme for integrating a reaction-diffusion PDE model. Below are the dynamics on the same 250×300 grid.

It seems a bit harsh to describe a reaction-diffusion PDE as “boring”, but it certainly isn’t as interesting as the stochastic dynamics. Also, it has qualitatively quite different behaviour to the stochastic models, with wavefronts being less pronounced and less well separated. The code for this one is here.

Instead of just seeding the simulation with some individuals in the central pixel, we can initialise 3 pixels. We can look first at a spatial CLE simulation.

Code here.

We can look at the same problem, but now using a PDE.

Code here.

Alternatively, we can initialise every pixel independently with random numbers of predator and prey. A movie for this is given below, following a short warm-up.

Code here.

Again, we can look at the corresponding deterministic integration.

Code here.

Let’s now turn attention to a spatial epidemic process model, the spatial susceptible-infectious-recovered model. Again, we’ll start from the discrete reaction formulation.

I’ll add this model to the next release of `scala-smfsb`

, but in the meantime we can easily define it ourselves with:

def sir[S: State](p: DenseVector[Double] = DenseVector(0.1, 0.5)): Spn[S] = UnmarkedSpn[S]( List("S", "I", "R"), DenseMatrix((1, 1, 0), (0, 1, 0)), DenseMatrix((0, 2, 0), (0, 0, 1)), (x, t) => { val xd = x.toDvd DenseVector( xd(0) * xd(1) * p(0), xd(1) * p(1) )} )

We can seed a simulation with a few infectious individuals in the centre of a roughly homogeneous population of susceptibles.

This time we’ll skip the exact simulation, since it’s very slow, and go straight to the spatial CLE. A simulation on a 250×300 grid is given below.

Here, green represents , red and blue . In this simulation, diffuses more slowly than , and doesn’t diffuse at all.

Code here.

If we ditch the noise to get a reaction-diffusion PDE model, the dynamics are as follows.

Again, we see that the deterministic model is quite different to the stochastic version, and kind-of boring. Code here.

All of the code used to generate the plots and movies in this post is available in an easily runnable form in my blog repo. It is very easy to adapt the examples to vary parameters and initial conditions, and to study other reaction systems. Further details relating to stochastic reaction-diffusion modelling based on the RDME can be found in Chapter 9 of my textbook, Stochastic modelling for systems biology, third edition.

]]>`smfsb`

R package. As mentioned in that post, although good for teaching and learning, R isn’t a great language for serious scientific computing or computational statistics. So for the publication of the third edition of my textbook, Stochastic modelling for systems biology, I have created a library in the Scala programming language replicating the functionality provided by the R package. Here I will give a very quick introduction to the scala-smfsb library. Some familiarity with both Scala and the `smfsb`

R package will be helpful, but is not strictly necessary. Note that the library relies on the Scala Breeze library for linear algebra and probability distributions, so some familiarity with that library can also be helpful.
To follow the along you need to have Sbt installed, and this in turn requires a recent JDK. If you are new to Scala, you may find the setup page for my Scala course to be useful, but note that on many Linux systems it can be as simple as installing the packages `openjdk-8-jdk`

and `sbt`

.

Once you have Sbt installed, you should be able to run it by entering `sbt`

at your OS command line. You now need to use Sbt to create a Scala REPL with a dependency on the `scala-smfsb`

library. There are many ways to do this, but if you are new to Scala, the simplest way is probably to start up Sbt from an *empty* or temporary directory (which doesn’t contain any Scala code), and then paste the following into the Sbt prompt:

set libraryDependencies += "com.github.darrenjw" %% "scala-smfsb" % "0.6" set libraryDependencies += "org.scalanlp" %% "breeze-viz" % "0.13.2" set scalaVersion := "2.12.6" set scalacOptions += "-Yrepl-class-based" console

The first time you run this it will take a little while to download and cache various library dependencies. But everything is cached, so it should be much quicker in future. When it is finished, you should have a Scala REPL ready to enter Scala code.

`scala-smfsb`

It should be possible to type or copy-and-paste the commands below one-at-a-time into the Scala REPL. We need to start with a few imports.

import smfsb._ import breeze.linalg.{Vector => BVec, _} import breeze.numerics._ import breeze.plot._

Note that I’ve renamed Breeze’s `Vector`

type to `BVec`

to avoid clashing with that in the Scala standard library. We are now ready to go.

Let’s begin by instantiating a Lotka-Volterra model, simulating a single realisation of the process, and then plotting it.

// Simulate LV with Gillespie val model = SpnModels.lv[IntState]() val step = Step.gillespie(model) val ts = Sim.ts(DenseVector(50, 100), 0.0, 20.0, 0.05, step) Sim.plotTs(ts, "Gillespie simulation of LV model with default parameters")

The library comes with a few other models. There’s a Michaelis-Menten enzyme kinetics model:

// Simulate other models with Gillespie val stepMM = Step.gillespie(SpnModels.mm[IntState]()) val tsMM = Sim.ts(DenseVector(301,120,0,0), 0.0, 100.0, 0.5, stepMM) Sim.plotTs(tsMM, "Gillespie simulation of the MM model")

and an auto-regulatory genetic network model, for example.

val stepAR = Step.gillespie(SpnModels.ar[IntState]()) val tsAR = Sim.ts(DenseVector(10, 0, 0, 0, 0), 0.0, 500.0, 0.5, stepAR) Sim.plotTs(tsAR, "Gillespie simulation of the AR model")

If you know the book and/or the R package, these models should all be familiar.

We are not restricted to exact stochastic simulation using the Gillespie algorithm. We can use an approximate Poisson time-stepping algorithm.

// Simulate LV with other algorithms val stepPts = Step.pts(model) val tsPts = Sim.ts(DenseVector(50, 100), 0.0, 20.0, 0.05, stepPts) Sim.plotTs(tsPts, "Poisson time-step simulation of the LV model")

Alternatively, we can instantiate the example models using a continuous state rather than a discrete state, and then simulate using algorithms based on continous approximations, such as Euler-Maruyama simulation of a chemical Langevin equation (CLE) approximation.

val stepCle = Step.cle(SpnModels.lv[DoubleState]()) val tsCle = Sim.ts(DenseVector(50.0, 100.0), 0.0, 20.0, 0.05, stepCle) Sim.plotTs(tsCle, "Euler-Maruyama/CLE simulation of the LV model")

If we want to ignore noise temporarily, there’s also a simple continuous deterministic Euler integrator built-in.

val stepE = Step.euler(SpnModels.lv[DoubleState]()) val tsE = Sim.ts(DenseVector(50.0, 100.0), 0.0, 20.0, 0.05, stepE) Sim.plotTs(tsE, "Continuous-deterministic Euler simulation of the LV model")

We can do 1d reaction-diffusion simulation with something like:

val N = 50; val T = 40.0 val model = SpnModels.lv[IntState]() val step = Spatial.gillespie1d(model,DenseVector(0.8, 0.8)) val x00 = DenseVector(0, 0) val x0 = DenseVector(50, 100) val xx00 = Vector.fill(N)(x00) val xx0 = xx00.updated(N/2,x0) val output = Sim.ts(xx0, 0.0, T, 0.2, step) Spatial.plotTs1d(output)

For 2d simulation, we use `PMatrix`

, a comonadic matrix/image type defined within the library, with parallelised `map`

and `coflatMap`

(cobind) operations. See my post on comonads for scientific computing for further details on the concepts underpinning this, though note that it isn’t necessary to understand comonads to use the library.

val r = 20; val c = 30 val model = SpnModels.lv[DoubleState]() val step = Spatial.cle2d(model, DenseVector(0.6, 0.6), 0.05) val x00 = DenseVector(0.0, 0.0) val x0 = DenseVector(50.0, 100.0) val xx00 = PMatrix(r, c, Vector.fill(r*c)(x00)) val xx0 = xx00.updated(c/2, r/2, x0) val output = step(xx0, 0.0, 8.0) val f = Figure("2d LV reaction-diffusion simulation") val p0 = f.subplot(2, 1, 0) p0 += image(PMatrix.toBDM(output map (_.data(0)))) val p1 = f.subplot(2, 1, 1) p1 += image(PMatrix.toBDM(output map (_.data(1))))

The library also includes functions for carrying out parameter inference for stochastic dynamical systems models, using particle MCMC, ABC and ABC-SMC. See the examples directory for further details.

Having worked through this post, the next step is to work through the tutorial. There is some overlap of content with this blog post, but the tutorial goes into more detail regarding the basics. It also finishes with suggestions for how to proceed further.

This post started out as a tut document (the Scala equivalent of an RMarkdown document). The source can be found here.

]]>In the previous post I gave a brief introduction to the third edition of my textbook, Stochastic modelling for systems biology. The algorithms described in the book are illustrated by implementations in R. These implementations are collected together in an R package on CRAN called `smfsb`

. This post will provide a brief introduction to the package and its capabilities.

The package is on CRAN – see the CRAN package page for details. So the simplest way to install it is to enter

install.packages("smfsb")

at the R command prompt. This will install the latest version that is on CRAN. Once installed, the package can be loaded with

library(smfsb)

The package is well-documented, so further information can be obtained with the usual R mechanisms, such as

vignette(package="smfsb") vignette("smfsb") help(package="smfsb") ?StepGillespie example(StepCLE1D)

The version of the package on CRAN is almost certainly what you want. However, the package is developed on R-Forge – see the R-Forge project page for details. So the very latest version of the package can always be installed with

install.packages("smfsb", repos="http://R-Forge.R-project.org")

if you have a reason for wanting it.

The vignette gives a quick introduction the the library, which I don’t need to repeat verbatim here. If you are new to the package, I recommend working through that before continuing. Here I’ll concentrate on some of the new features associated with the third edition.

Much of the book is concerned with the simulation of stochastic kinetic models using exact and approximate algorithms. Although the primary focus of the text is the application to modelling of intra-cellular processes, the methods are also appropriate for population modelling of ecological and epidemic processes. For example, we can start by simulating a simple susceptible-infectious-recovered (SIR) disease epidemic model.

set.seed(2) data(spnModels) stepSIR = StepGillespie(SIR) plot(simTs(SIR$M, 0, 8, 0.05, stepSIR), main="Exact simulation of the SIR model")

The focus of the text is stochastic simulation of discrete models, so that is the obvious place to start. But there is also support for continuous deterministic simulation.

plot(simTs(SIR$M, 0, 8, 0.05, StepEulerSPN(SIR)), main="Euler simulation of the SIR model")

My favourite toy population dynamics model is the Lotka-Volterra (LV) model, so I tend to use this frequently as a running example throughout the book. We can simulate this (exactly) as follows.

stepLV = StepGillespie(LV) plot(simTs(LV$M, 0, 30, 0.2, stepLV), main="Exact simulation of the LV model")

The first two editions of the book were almost exclusively concerned with *well-mixed* systems, where spatial effects are ignorable. One of the main new features of the third edition is the inclusion of a new chapter on spatially extended systems. The focus is on models related to the reaction diffusion master equation (RDME) formulation, rather than individual particle-based simulations. For these models, space is typically divided into a regular grid of voxels, with reactions taking place as normal within each voxel, and additional reaction events included, corresponding to the diffusion of particles to adjacent voxels. So to specify such models, we just need an initial condition, a reaction model, and diffusion coefficients (one for each reacting species). So, we can carry out exact simulation of an RDME model for a 1D spatial domain as follows.

N=20; T=30 x0=matrix(0, nrow=2, ncol=N) rownames(x0) = c("x1", "x2") x0[,round(N/2)] = LV$M stepLV1D = StepGillespie1D(LV, c(0.6, 0.6)) xx = simTs1D(x0, 0, T, 0.2, stepLV1D, verb=TRUE) image(xx[1,,], main="Prey", xlab="Space", ylab="Time")

image(xx[2,,], main="Predator", xlab="Space", ylab="Time")

Exact simulation of discrete stochastic reaction diffusion systems is very expensive (and the reference implementation provided in the package is very inefficient), so we will often use diffusion approximations based on the CLE.

stepLV1DC = StepCLE1D(LV, c(0.6, 0.6)) xx = simTs1D(x0, 0, T, 0.2, stepLV1D) image(xx[1,,], main="Prey", xlab="Space", ylab="Time")

image(xx[2,,], main="Predator", xlab="Space", ylab="Time")

We can think of this algorithm as an explicit numerical integration of the obvious SPDE approximation to the exact model.

The package also includes support for simulation of 2D systems. Again, we can use the Spatial CLE to speed things up.

m=70; n=50; T=10 data(spnModels) x0=array(0, c(2,m,n)) dimnames(x0)[[1]]=c("x1", "x2") x0[,round(m/2),round(n/2)] = LV$M stepLV2D = StepCLE2D(LV, c(0.6,0.6), dt=0.05) xx = simTs2D(x0, 0, T, 0.5, stepLV2D) N = dim(xx)[4] image(xx[1,,,N],main="Prey",xlab="x",ylab="y")

image(xx[2,,,N],main="Predator",xlab="x",ylab="y")

Although much of the book is concerned with the problem of forward simulation, the final chapters are concerned with the inverse problem of estimating model parameters, such as reaction rate constants, from data. A computational Bayesian approach is adopted, with the main emphasis being placed on “likelihood free” methods, which rely on forward simulation to avoid explicit computation of sample path likelihoods. The second edition included some rudimentary code for a likelihood free particle marginal Metropolis-Hastings (PMMH) particle Markov chain Monte Carlo (pMCMC) algorithm. The third edition includes a more complete and improved implementation, in addition to approximate inference algorithms based on approximate Bayesian computation (ABC).

The key function underpinning the PMMH approach is `pfMLLik`

, which computes an estimate of marginal model log-likelihood using a (bootstrap) particle filter. There is a new implementation of this function with the third edition. There is also a generic implementation of the Metropolis-Hastings algorithm, `metropolisHastings`

, which can be combined with `pfMLLik`

to create a PMMH algorithm. PMMH algorithms are very slow, but a full demo of how to use these functions for parameter inference is included in the package and can be run with

demo(PMCMC)

Simple rejection-based ABC methods are facilitated by the (very simple) function `abcRun`

, which just samples from a prior and then carries out independent simulations in parallel before computing summary statistics. A simple illustration of the use of the function is given below.

data(LVdata) rprior <- function() { exp(c(runif(1, -3, 3),runif(1,-8,-2),runif(1,-4,2))) } rmodel <- function(th) { simTs(c(50,100), 0, 30, 2, stepLVc, th) } sumStats <- identity ssd = sumStats(LVperfect) distance <- function(s) { diff = s - ssd sqrt(sum(diff*diff)) } rdist <- function(th) { distance(sumStats(rmodel(th))) } out = abcRun(10000, rprior, rdist) q=quantile(out$dist, c(0.01, 0.05, 0.1)) print(q)

## 1% 5% 10% ## 772.5546 845.8879 881.0573

accepted = out$param[out$dist < q[1],] print(summary(accepted))

## V1 V2 V3 ## Min. :0.06498 Min. :0.0004467 Min. :0.01887 ## 1st Qu.:0.16159 1st Qu.:0.0012598 1st Qu.:0.04122 ## Median :0.35750 Median :0.0023488 Median :0.14664 ## Mean :0.68565 Mean :0.0046887 Mean :0.36726 ## 3rd Qu.:0.86708 3rd Qu.:0.0057264 3rd Qu.:0.36870 ## Max. :4.76773 Max. :0.0309364 Max. :3.79220

print(summary(log(accepted)))

## V1 V2 V3 ## Min. :-2.7337 Min. :-7.714 Min. :-3.9702 ## 1st Qu.:-1.8228 1st Qu.:-6.677 1st Qu.:-3.1888 ## Median :-1.0286 Median :-6.054 Median :-1.9198 ## Mean :-0.8906 Mean :-5.877 Mean :-1.9649 ## 3rd Qu.:-0.1430 3rd Qu.:-5.163 3rd Qu.:-0.9978 ## Max. : 1.5619 Max. :-3.476 Max. : 1.3329

Naive rejection-based ABC algorithms are notoriously inefficient, so the library also includes an implementation of a more efficient, sequential version of ABC, often known as ABC-SMC, in the function `abcSmc`

. This function requires specification of a perturbation kernel to “noise up” the particles at each algorithm sweep. Again, the implementation is parallel, using the `parallel`

package to run the required simulations in parallel on multiple cores. A simple illustration of use is given below.

rprior <- function() { c(runif(1, -3, 3), runif(1, -8, -2), runif(1, -4, 2)) } dprior <- function(x, ...) { dunif(x[1], -3, 3, ...) + dunif(x[2], -8, -2, ...) + dunif(x[3], -4, 2, ...) } rmodel <- function(th) { simTs(c(50,100), 0, 30, 2, stepLVc, exp(th)) } rperturb <- function(th){th + rnorm(3, 0, 0.5)} dperturb <- function(thNew, thOld, ...){sum(dnorm(thNew, thOld, 0.5, ...))} sumStats <- identity ssd = sumStats(LVperfect) distance <- function(s) { diff = s - ssd sqrt(sum(diff*diff)) } rdist <- function(th) { distance(sumStats(rmodel(th))) } out = abcSmc(5000, rprior, dprior, rdist, rperturb, dperturb, verb=TRUE, steps=6, factor=5)

## 6 5 4 3 2 1

print(summary(out))

## V1 V2 V3 ## Min. :-2.9961 Min. :-7.988 Min. :-3.999 ## 1st Qu.:-1.9001 1st Qu.:-6.786 1st Qu.:-3.428 ## Median :-1.2571 Median :-6.167 Median :-2.433 ## Mean :-1.0789 Mean :-6.014 Mean :-2.196 ## 3rd Qu.:-0.2682 3rd Qu.:-5.261 3rd Qu.:-1.161 ## Max. : 2.1128 Max. :-2.925 Max. : 1.706

We can then plot some results with

hist(out[,1],30,main="log(c1)")

hist(out[,2],30,main="log(c2)")

hist(out[,3],30,main="log(c3)")

Although the inference methods are illustrated in the book in the context of parameter inference for stochastic kinetic models, their implementation is generic, and can be used with any appropriate parameter inference problem.

`smfsbSBML`

package`smfsbSBML`

is another R package associated with the third edition of the book. This package is not on CRAN due to its dependency on a package not on CRAN, and hence is slightly less straightforward to install. Follow the available installation instructions to install the package. Once installed, you should be able to load the package with

library(smfsbSBML)

This package provides a function for reading in SBML files and parsing them into the simulatable stochastic Petri net (SPN) objects used by the main `smfsb`

R package. Examples of suitable SBML models are included in the main smfsb GitHub repo. An appropriate SBML model can be read and parsed with a command like:

model = sbml2spn("mySbmlModel.xml")

The resulting value, `model`

is an SPN object which can be passed in to simulation functions such as `StepGillespie`

for constructing stochastic simulation algorithms.

In addition to the above R packages, I also have some Python scripts for converting between SBML and the SBML-shorthand notation I use in the book. See the SBML-shorthand page for further details.

Although R is a convenient language for teaching and learning about stochastic simulation, it isn’t ideal for serious research-level scientific computing or computational statistics. So for the third edition of the book I have also developed scala-smfsb, a library written in the Scala programming language, which re-implements all of the models and algorithms from the third edition of the book in Scala, a fast, efficient, strongly-typed, compiled, functional programming language. I’ll give an introduction to this library in a subsequent post, but in the meantime, it is already well documented, so see the scala-smfsb repo for further details, including information on installation, getting started, a tutorial, examples, API docs, etc.

This blog post started out as an RMarkdown document, the source of which can be found here.

]]>The third edition of my textbook, Stochastic Modelling for Systems Biology has recently been published by Chapman & Hall/CRC Press. The book has ISBN-10 **113854928-2** and ISBN-13 **978-113854928-9**. It can be ordered from CRC Press, Amazon.com, Amazon.co.uk and similar book sellers.

I was fairly happy with the way that the second edition, published in 2011, turned out, and so I haven’t substantially re-written any of the text for the third edition. Instead, I’ve concentrated on adding in new material and improving the associated on-line resources. Those on-line resources are all free and open source, and hence available to everyone, irrespective of whether you have a copy of the new edition. I’ll give an introduction to those resources below (and in subsequent posts). The new material can be briefly summarised as follows:

- New chapter on spatially extended systems, covering the spatial Gillespie algorithm for reaction diffusion master equation (RDME) models in 1- and 2-d, the next subvolume method, spatial CLE, scaling issues, etc.
- Significantly expanded chapter on inference for stochastic kinetic models from data, covering approximate methods of inference (ABC), including ABC-SMC. The material relating to particle MCMC has also been improved and extended.
- Updated R package, including code relating to all of the new material
- New R package for parsing SBML models into simulatable stochastic Petri net models
- New software library, written in Scala, replicating most of the functionality of the R packages in a fast, compiled, strongly typed, functional language

Although some minor edits and improvements have been made throughout the text, there are two substantial new additions to the text in this new edition. The first is an entirely new chapter on spatially extended systems. The first two editions of the text focused on the implications of discreteness and stochasticity in chemical reaction systems, but maintained the well-mixed assumption throughout. This is a reasonable first approach, since discreteness and stochasticity are most pronounced in very small volumes where diffusion should be rapid. In any case, even these non-spatial models have very interesting behaviour, and become computationally challenging very quickly for non-trivial reaction networks. However, we know that, in fact, the cell is a very crowded environment, and so even at small spatial scales, many interesting processes are diffusion limited. It therefore seems appropriate to dedicate one chapter (the new Chapter 9) to studying some of the implications of relaxing the well-mixed assumption. Entire books can be written on stochastic reaction-diffusion systems, so here only a brief introduction is provided, based mainly around models in the reaction-diffusion master equation (RDME) style. Exact stochastic simulation algorithms are discussed, and implementations provided in the 1- and 2-d cases, and an appropriate Langevin approximation is examined, the spatial CLE.

The second major addition is to the chapter on inference for stochastic kinetic models from data (now Chapter 11). The second edition of the book included a discussion of “likelihood free” Bayesian MCMC methods for inference, and provided a working implementation of likelihood free particle marginal Metropolis-Hastings (PMMH) for stochastic kinetic models. The third edition improves on that implementation, and discusses approximate Bayesian computation (ABC) as an alternative to MCMC for likelihood free inference. Implementation issues are discussed, and sequential ABC approaches are examined, concentrating in particular on the method known as ABC-SMC.

Accompanying the text are new and improved on-line resources, all well-documented, free, and open source.

Information and materials relating to the previous editions were kept on my University website. All materials relating to this new edition are kept in a public GitHub repo: darrenjw/smfsb. This will be simpler to maintain, and will make it much easier for people to make copies of the material for use and studying off-line.

Along with the second edition of the book I released an accompanying R package, “smfsb”, published on CRAN. This was a very popular feature, allowing anyone with R to trivially experiment with all of the models and algorithms discussed in the text. This R package has been updated, and a new version has been published to CRAN. The updates are all backwards-compatible with the version associated with the second edition of the text, so owners of that edition can still upgrade safely. I’ll give a proper introduction to the package, including the new features, in a subsequent post, but in the meantime, you can install/upgrade the package from a running R session with

install.packages("smfsb")

and then pop up a tutorial vignette with:

vignette("smfsb")

This should be enough to get you started.

In addition to the main R package, there is an additional R package for parsing SBML models into models that can be simulated within R. This package is not on CRAN, due to its dependency on a non-CRAN package. See the repo for further details.

There are also Python scripts available for converting SBML models to and from the shorthand SBML notation used in the text.

Another major new resource associated with the third edition of the text is a software library written in the Scala programming language. This library provides Scala implementations of all of the algorithms discussed in the book and implemented in the associated R packages. This then provides example implementations in a fast, efficient, compiled language, and is likely to be most useful for people wanting to use the methods in the book for research. Again, I’ll provide a tutorial introduction to this library in a subsequent post, but it is well-documented, with all necessary information needed to get started available at the scala-smfsb repo/website, including a step-by-step tutorial and some additional examples.

]]>In the previous post I gave a brief introduction to Rainier, a new HMC-based probabilistic programming library/DSL for Scala. In that post I assumed that people were using the latest source version of the library. Since then, version 0.1.1 of the library has been released, so in this post I will demonstrate use of the released version of the software (using the binaries published to Sonatype), and will walk through a slightly more interesting example – a dynamic linear state space model with unknown static parameters. This is similar to, but slightly different from, the DLM example in the Rainier library. So to follow along with this post, all that is required is SBT.

First run SBT from an empty directory, and paste the following at the SBT prompt:

set libraryDependencies += "com.stripe" %% "rainier-plot" % "0.1.1" set scalaVersion := "2.12.4" console

This should give a Scala REPL with appropriate dependencies (`rainier-plot`

has all of the relevant transitive dependencies). We’ll begin with some imports, and then simulating some synthetic data from a dynamic linear state space model with an AR(1) latent state and Gaussian noise on the observations.

import com.stripe.rainier.compute._ import com.stripe.rainier.core._ import com.stripe.rainier.sampler._ implicit val rng = ScalaRNG(1) val n = 60 // number of observations/time points val mu = 3.0 // AR(1) mean val a = 0.95 // auto-regressive parameter val sig = 0.2 // AR(1) SD val sigD = 3.0 // observational SD val state = Stream. iterate(0.0)(x => mu + (x - mu) * a + sig * rng.standardNormal). take(n).toVector val obs = state.map(_ + sigD * rng.standardNormal)

Now we have some synthetic data, let’s think about building a probabilistic program for this model. Start with a prior.

case class Static(mu: Real, a: Real, sig: Real, sigD: Real) val prior = for { mu <- Normal(0, 10).param a <- Normal(1, 0.1).param sig <- Gamma(2,1).param sigD <- Gamma(2,2).param sp <- Normal(0, 50).param } yield (Static(mu, a, sig, sigD), List(sp))

Note the use of a case class for wrapping the static parameters. Next, let’s define a function to add a state and associated observation to an existing model.

def addTimePoint(current: RandomVariable[(Static, List[Real])], datum: Double) = for { tup <- current static = tup._1 states = tup._2 os = states.head ns <- Normal(((Real.one - static.a) * static.mu) + (static.a * os), static.sig).param _ <- Normal(ns, static.sigD).fit(datum) } yield (static, ns :: states)

Given this, we can generate the probabilistic program for our model as a *fold* over the data initialised with the prior.

val fullModel = obs.foldLeft(prior)(addTimePoint(_, _))

If we don’t want to keep samples for all of the variables, we can focus on the parameters of interest, wrapping the results in a `Map`

for convenient sampling and plotting.

val model = for { tup <- fullModel static = tup._1 states = tup._2 } yield Map("mu" -> static.mu, "a" -> static.a, "sig" -> static.sig, "sigD" -> static.sigD, "SP" -> states.reverse.head)

We can sample with

val out = model.sample(HMC(3), 100000, 10000 * 500, 500)

(this will take several minutes) and plot some diagnostics with

import com.cibo.evilplot.geometry.Extent import com.stripe.rainier.plot.EvilTracePlot._ val truth = Map("mu" -> mu, "a" -> a, "sigD" -> sigD, "sig" -> sig, "SP" -> state(0)) render(traces(out, truth), "traceplots.png", Extent(1200, 1400)) render(pairs(out, truth), "pairs.png")

This generates the following diagnostic plots:

Everything looks good.

Rainier is a monadic embedded DSL for probabilistic programming in Scala. We can use standard functional combinators and for-expressions for building models to sample, and then run an efficient HMC algorithm on the resulting probability monad in order to obtain samples from the posterior distribution of the model.

See the Rainier repo for further details.

]]>Rainier is an interesting new probabilistic programming library for Scala recently open-sourced by Stripe. Probabilistic programming languages provide a computational framework for building and fitting Bayesian models to data. There are many interesting probabilistic programming languages, and there is currently a lot of interesting innovation happening with probabilistic programming languages embedded in strongly typed functional programming languages such as Scala and Haskell. However, most such languages tend to be developed by people lacking expertise in statistics and numerics, leading to elegant, composable languages which work well for toy problems, but don’t scale well to the kinds of practical problems that applied statisticians are interested in. Conversely, there are a few well-known probabilistic programming languages developed by and for statisticians which have efficient inference engines, but are hampered by inflexible, inelegant languages and APIs. Rainier is interesting because it is an attempt to bridge the gap between these two worlds: it has a functional, composable, extensible, monadic API, yet is backed by a very efficient, high-performance scalable inference engine, using HMC and a static compute graph for reverse-mode AD. Clearly there will be some loss of generality associated with choosing an efficient inference algorithm (eg. for HMC, there needs to be a fixed number of parameters and they must all be continuous), but it still covers a large proportion of the class of hierarchical models commonly used in applied statistical modelling.

In this post I’ll give a quick introduction to Rainier using an interactive session requiring only that SBT is installed and the Rainier repo is downloaded or cloned.

To follow along with this post just clone, or download and unpack, the Rainier repo, and run SBT from the top-level Rainier directory and paste commands. First start a Scala REPL.

project rainierPlot console

Before we start building models, we need some data. For this post we will focus on a simple logistic regression model, and so we will begin by simulating some synthetic data consistent with such a model.

val r = new scala.util.Random(0) val N = 1000 val beta0 = 0.1 val beta1 = 0.3 val x = (1 to N) map { i => 3.0 * r.nextGaussian } val theta = x map { xi => beta0 + beta1 * xi } def expit(x: Double): Double = 1.0 / (1.0 + math.exp(-x)) val p = theta map expit val y = p map (pi => (r.nextDouble < pi))

Now we have some synthetic data, we can fit the model and see if we are able to recover the “true” parameters used to generate the synthetic data. In Rainier, we build models by declaring probabilistic programs for the model and the data, and then run an inference engine to generate samples from the posterior distribution.

Start with a bunch of Rainier imports:

import com.stripe.rainier.compute._ import com.stripe.rainier.core._ import com.stripe.rainier.sampler._ import com.stripe.rainier.repl._

Now we want to build a model. We do so by describing the joint distribution of parameters and data. Rainier has a few built-in distributions, and these can be combined using standard functional monadic combinators such as `map`

, `zip`

, `flatMap`

, etc., to create a probabilistic program representing a probability monad for the model. Due to the monadic nature of such probabilistic programs, it is often most natural to declare them using a `for`

-expression.

val model = for { beta0 <- Normal(0, 5).param beta1 <- Normal(0, 5).param _ <- Predictor.from{x: Double => { val theta = beta0 + beta1 * x val p = Real(1.0) / (Real(1.0) + (Real(0.0) - theta).exp) Categorical.boolean(p) } }.fit(x zip y) } yield Map("b0"->beta0, "b1"->beta1)

This kind of construction is very natural for anyone familiar with monadic programming in Scala, but will no doubt be a little mysterious otherwise. `RandomVariable`

is the probability monad used for HMC sampling, and these can be constructed from `Distributions`

using `.param`

(for unobserved parameters) and `.fit`

(for variables with associated observations). `Predictor`

is just a convenience for observations corresponding to covariate information. `model`

is therefore a `RandomVariable`

over `beta0`

and `beta1`

, the two unobserved parameters of interest. Note that I briefly discussed this kind of pure functional approach to describing probabilistic programs (using `Rand`

from Breeze) in my post on MCMC as a stream.

Now we have our probabilistic program, we can sample from it using HMC as follows.

implicit val rng = ScalaRNG(3) val its = 10000 val thin = 5 val out = model.sample(HMC(5), 10000, its*thin, thin) println(out.take(10))

The argument to `HMC()`

is the number of leapfrog steps to take per iteration.

Finally, we can use EvilPlot to look at the HMC output and check that we have managed to reasonably recover the true parameters associated with our synthetic data.

import com.cibo.evilplot.geometry.Extent import com.stripe.rainier.plot.EvilTracePlot._ render(traces(out, truth = Map("b0" -> beta0, "b1" -> beta1)), "traceplots.png", Extent(1200, 1000)) render(pairs(out, truth = Map("b0" -> beta0, "b1" -> beta1)), "pairs.png")

Everything looks good, and the sampling is very fast!

For further information, see the Rainier repo. In particular, start with the tour of Rainier’s core, which gives a more detailed introduction to how Rainier works than this post. Those interested in how the efficient AD works may want to read about the compute graph, and the implementation notes explain how it all fits together. There is some basic ScalaDoc for the core package, and also some examples (including this one), and there’s a gitter channel for asking questions. This is a very new project, so there are a few minor bugs and wrinkles in the initial release, but development is progressing rapidly, so I fully expect the library to get properly battle-hardened over the next few months.

For those unfamiliar with the monadic approach to probabilistic programming, then Ĺcibior et al (2015) is probably a good starting point.

]]>EvilPlot is a new functional data visualisation library for Scala. Although there are several data viz libraries for Scala, this new library has a nice functional API for producing attractive, flexible, compositional plots which can be rendered in JVM applications and in web applications (via Scala.js). For a quick introduction, see this blog post from one of the library’s creators. For further information, see the official documentation and the github repo. For a quick overview of the kinds of plots that the library is capable of generating, see the plot catalog.

The library is designed to produce plots which can be rendered into applications. However, when doing data analysis in the REPL on the JVM, it is often convenient to be able to just pop up a plot in a window on the desktop. EvilPlot doesn’t seem to contain code for on-screen rendering, but the plots can be rendered to a bitmap image. In the previous post I described a small library, scala-view, which renders such images, and image sequences on the desktop. In this post I’ll walk through using scala-view to render EvilPlot plots on-screen.

To follow this session, you just need to run SBT from an empty directory. Just run `sbt`

and paste the following at the SBT prompt:

set libraryDependencies += "com.cibo" %% "evilplot" % "0.2.0" set libraryDependencies += "com.github.darrenjw" %% "scala-view" % "0.6-SNAPSHOT" set resolvers += Resolver.bintrayRepo("cibotech", "public") set resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" set scalaVersion := "2.12.4" set fork := true console

This will give a Scala REPL prompt. First we need some imports:

import com.cibo.evilplot.plot._ import com.cibo.evilplot.colors._ import com.cibo.evilplot.plot.aesthetics.DefaultTheme._ import com.cibo.evilplot.numeric.Point import java.awt.Image.SCALE_SMOOTH import scalaview.Utils._

We can simulate some data an produce a simple line chart:

val data = Seq.tabulate(100) { i => Point(i.toDouble, scala.util.Random.nextDouble()) } val plot = LinePlot.series(data, "Line graph", HSL(210, 100, 56)). xAxis().yAxis().frame(). xLabel("x").yLabel("y").render()

This `plot`

object contains the rendering instructions, but doesn’t actually produce a plot. We can use scala-view to display it as follows:

scalaview.SfxImageViewer(biResize(plot.asBufferedImage,1000,800,SCALE_SMOOTH))

This will produce a window on screen something like the following:

Don’t close this plot yet, as this will confuse the REPL. Just switch back to the REPL and continue.

Sometimes we want to produce a sequence of plots. Let’s now suppose that the data above arises sequentially as a stream, and that we want to produce a sequence of plots with each observation as it arrives. First create a stream of partial datasets and map a function which turns a dataset into a plot to get a stream of images representing the plots. Then pass the stream of images into the viewer to get an animated sequence of plots on-screen:

val dataStream = data.toStream val cumulStream = dataStream.scanLeft(Nil: List[Point])((l,p) => p :: l).drop(1) def dataToImage(data: List[Point]) = LinePlot. series(data, "Line graph", HSL(210, 100, 56)). xAxis().yAxis().frame(). xLabel("x").yLabel("y").render().asBufferedImage val plotStream = cumulStream map (d => biResize(dataToImage(d),1000,800,SCALE_SMOOTH)) scalaview.SfxImageViewer.bi(plotStream, 100000, autoStart=true)]]>

In the previous post I discussed how comonads can be useful for structuring certain kinds of scientific and statistical computations. Two of the examples I gave were concerned with the time-evolution of 2-d images. In that post I used Breeze to animate the sequence of computed images. In this post I want to describe an alternative that is better suited to animating an image sequence.

Scala-view is a small Scala library for animating a Stream of Images on-screen in a separate window managed by your window manager. It works with both ScalaFX `Images`

(recommended) and Scala Swing/AWT `BufferedImages`

(legacy). The stream of images is animated in a window with some simple controls to start and stop the animation, and to turn on and off the saving of image frames to disk (typically for the purpose of turning the image sequence into a movie). An example of what a window might look like is given below.

More comprehensive documentation is available from the scala-view github repo, but here I give a quick introduction to the library to outline its capabilities.

This brief tutorial gives a quick introduction to using the Scala-view library for viewing a ScalaFX Image Stream. It assumes only that you have SBT installed, and that you run SBT from an empty directory.

Start by running SBT from an empty or temporary directory to get an SBT prompt:

$ sbt >

Now we need to configure SBT to use the Scala-view library, and start a console. From the SBT prompt:

set libraryDependencies += "com.github.darrenjw" %% "scala-view" % "0.5" set scalaVersion := "2.12.4" console

The should result in a `scala>`

REPL prompt. We can now use Scala and the Scala-view library interactively.

You should be able to paste the code snippets below directly into the REPL. You may find `:paste`

mode helpful.

We will replicate the heat equation example from the examples-sfx directory, which is loosely based on the example from my blog post on comonads. We will start by defining a simple parallel Image and corresponding comonadic pointed image `PImage`

type. If you aren’t familiar with comonads, you may find it helpful to read through that post.

import scala.collection.parallel.immutable.ParVector case class Image[T](w: Int, h: Int, data: ParVector[T]) { def apply(x: Int, y: Int): T = data(x * h + y) def map[S](f: T => S): Image[S] = Image(w, h, data map f) def updated(x: Int, y: Int, value: T): Image[T] = Image(w, h, data.updated(x * h + y, value)) } case class PImage[T](x: Int, y: Int, image: Image[T]) { def extract: T = image(x, y) def map[S](f: T => S): PImage[S] = PImage(x, y, image map f) def coflatMap[S](f: PImage[T] => S): PImage[S] = PImage( x, y, Image(image.w, image.h, (0 until (image.w * image.h)).toVector.par.map(i => { val xx = i / image.h val yy = i % image.h f(PImage(xx, yy, image)) }))) def up: PImage[T] = { val py = y - 1 val ny = if (py >= 0) py else (py + image.h) PImage(x, ny, image) } def down: PImage[T] = { val py = y + 1 val ny = if (py < image.h) py else (py - image.h) PImage(x, ny, image) } def left: PImage[T] = { val px = x - 1 val nx = if (px >= 0) px else (px + image.w) PImage(nx, y, image) } def right: PImage[T] = { val px = x + 1 val nx = if (px < image.w) px else (px - image.w) PImage(nx, y, image) } }

We will need a function to convert this image into a ScalaFX `WritableImage`

.

import scalafx.scene.image.WritableImage import scalafx.scene.paint._ def toSfxI(im: Image[Double]): WritableImage = { val wi = new WritableImage(im.w, im.h) val pw = wi.pixelWriter (0 until im.w) foreach (i => (0 until im.h) foreach (j => pw.setColor(i, j, Color.gray(im(i,j))) )) wi }

We will need a starting image representing the initial condition for the heat equation.

val w = 600 val h = 500 val pim0 = PImage(0, 0, Image(w, h, ((0 until w*h).toVector map {i: Int => { val x = i / h val y = i % h 0.1*math.cos(0.1*math.sqrt((x*x+y*y))) + 0.1 + 0.8*math.random }}).par ))

We can define a kernel associated with the update of a single image pixel based on a single time step of a finite difference solution of the heat equation.

def kernel(pi: PImage[Double]): Double = (2*pi.extract+ pi.up.extract+pi.down.extract+pi.left.extract+pi.right.extract)/6.0

We can now create a `Stream`

of `PImage`

with

def pims = Stream.iterate(pim0)(_.coflatMap(kernel))

We can turn this into a `Stream[WritableImage]`

with

def sfxis = pims map (im => toSfxI(im.image))

Note that we are essentially finished at this point, but so far everything we have done has been purely functional with no side effects. We haven’t even computed our solution to the heat equation. All we have constructed are lazy infinite streams representing the solution of the heat equation.

Finally, we can render our Stream of Images on screen with

scalaview.SfxImageViewer(sfxis,1e7.toInt)

which has a delay of 1e7 nanoseconds (10 milliseconds) between frames.

This should pop up a window on your display containing the initial image. Click on the Start button to animate the solution of the heat equation. See the API docs for SfxImageViewer for additional options. The ScalaFX API docs may also be useful, especially the docs for Image and WritableImage.

]]>In a previous post I’ve given a brief introduction to *monads* in Scala, aimed at people interested in scientific and statistical computing. Monads are a concept from category theory which turn out to be exceptionally useful for solving many problems in functional programming. But most categorical concepts have a *dual*, usually prefixed with “co”, so the dual of a monad is the *comonad*. Comonads turn out to be especially useful for formulating algorithms from scientific and statistical computing in an elegant way. In this post I’ll illustrate their use in signal processing, image processing, numerical integration of PDEs, and Gibbs sampling (of an Ising model). Comonads enable the *extension* of a *local computation* to a *global computation*, and this pattern crops up all over the place in statistical computing.

Simplifying massively, from the viewpoint of a Scala programmer, a monad is a mappable (functor) type class augmented with the methods `pure`

and `flatMap`

:

trait Monad[M[_]] extends Functor[M] { def pure[T](v: T): M[T] def flatMap[T,S](v: M[T])(f: T => M[S]): M[S] }

In category theory, the dual of a concept is typically obtained by “reversing the arrows”. Here that means reversing the direction of the methods `pure`

and `flatMap`

to get `extract`

and `coflatMap`

, respectively.

trait Comonad[W[_]] extends Functor[W] { def extract[T](v: W[T]): T def coflatMap[T,S](v: W[T])(f: W[T] => S): W[S] }

So, while `pure`

allows you to wrap plain values in a monad, `extract`

allows you to get a value out of a comonad. So you can always get a value out of a comonad (unlike a monad). Similarly, while `flatMap`

allows you to transform a monad using a function returning a monad, `coflatMap`

allows you to transform a comonad using a function which collapses a comonad to a single value. It is `coflatMap`

(sometimes called `extend`

) which can extend a local computation (producing a single value) to the entire comonad. We’ll look at how that works in the context of some familiar examples.

One of the simplest examples of a comonad is an *infinite* stream of data. I’ve discussed streams in a previous post. By focusing on infinite streams we know the stream will never be empty, so there will always be a value that we can `extract`

. Which value does `extract`

give? For a `Stream`

encoded as some kind of lazy list, the only value we actually know is the value at the head of the stream, with subsequent values to be lazily computed as required. So the head of the list is the only reasonable value for `extract`

to return.

Understanding `coflatMap`

is a bit more tricky, but it is `coflatMap`

that provides us with the power to apply a non-trivial statistical computation to the stream. The input is a function which transforms a stream into a value. In our example, that will be a function which computes a weighted average of the first few values and returns that weighted average as the result. But the return type of `coflatMap`

must be a stream of such computations. Following the types, a few minutes thought reveals that the only reasonable thing to do is to return the stream formed by applying the weighted average function to all sub-streams, recursively. So, for a `Stream`

`s`

(of type `Stream[T]`

) and an input function `f: W[T] => S`

, we form a stream whose head is `f(s)`

and whose tail is `coflatMap(f)`

applied to `s.tail`

. Again, since we are working with an infinite stream, we don’t have to worry about whether or not the `tail`

is empty. This gives us our comonadic `Stream`

, and it is exactly what we need for applying a linear filter to the data stream.

In Scala, Cats is a library providing type classes from Category theory, and instances of those type classes for parametrised types in the standard library. In particular, it provides us with comonadic functionality for the standard Scala `Stream`

. Let’s start by defining a stream corresponding to the logistic map.

import cats._ import cats.implicits._ val lam = 3.7 def s = Stream.iterate(0.5)(x => lam*x*(1-x)) s.take(10).toList // res0: List[Double] = List(0.5, 0.925, 0.25668749999999985, // 0.7059564011718747, 0.7680532550204203, 0.6591455741499428, ...

Let us now suppose that we want to apply a linear filter to this stream, in order to smooth the values. The idea behind using comonads is that you figure out how to generate *one* desired value, and let `coflatMap`

take care of applying the same logic to the rest of the structure. So here, we need a function to generate the *first* filtered value (since `extract`

is focused on the head of the stream). A simple first attempt a function to do this might look like the following.

def linearFilterS(weights: Stream[Double])(s: Stream[Double]): Double = (weights, s).parMapN(_*_).sum

This aligns each weight in parallel with a corresponding value from the stream, and combines them using multiplication. The resulting (hopefully finite length) stream is then summed (with addition). We can test this with

linearFilterS(Stream(0.25,0.5,0.25))(s) // res1: Double = 0.651671875

and let `coflatMap`

extend this computation to the rest of the stream with something like:

s.coflatMap(linearFilterS(Stream(0.25,0.5,0.25))).take(5).toList // res2: List[Double] = List(0.651671875, 0.5360828502929686, ...

This is all completely fine, but our `linearFilterS`

function is specific to the `Stream`

comonad, despite the fact that all we’ve used about it in the function is that it is a parallelly composable and foldable. We can make this much more generic as follows:

def linearFilter[F[_]: Foldable, G[_]]( weights: F[Double], s: F[Double] )(implicit ev: NonEmptyParallel[F, G]): Double = (weights, s).parMapN(_*_).fold

This uses some fairly advanced Scala concepts which I don’t want to get into right now (I should also acknowledge that I had trouble getting the syntax right for this, and got help from Fabio Labella (@SystemFw) on the Cats gitter channel). But this version is more generic, and can be used to linearly filter other data structures than `Stream`

. We can use this for regular `Streams`

as follows:

s.coflatMap(s => linearFilter(Stream(0.25,0.5,0.25),s)) // res3: scala.collection.immutable.Stream[Double] = Stream(0.651671875, ?)

But we can apply this new filter to other collections. This could be other, more sophisticated, streams such as provided by FS2, Monix or Akka streams. But it could also be a non-stream collection, such as `List`

:

val sl = s.take(10).toList sl.coflatMap(sl => linearFilter(List(0.25,0.5,0.25),sl)) // res4: List[Double] = List(0.651671875, 0.5360828502929686, ...

Assuming that we have the Breeze scientific library available, we can plot the raw and smoothed trajectories.

def myFilter(s: Stream[Double]): Double = linearFilter(Stream(0.25, 0.5, 0.25),s) val n = 500 import breeze.plot._ import breeze.linalg._ val fig = Figure(s"The (smoothed) logistic map (lambda=$lam)") val p0 = fig.subplot(3,1,0) p0 += plot(linspace(1,n,n),s.take(n)) p0.ylim = (0.0,1.0) p0.title = s"The logistic map (lambda=$lam)" val p1 = fig.subplot(3,1,1) p1 += plot(linspace(1,n,n),s.coflatMap(myFilter).take(n)) p1.ylim = (0.0,1.0) p1.title = "Smoothed by a simple linear filter" val p2 = fig.subplot(3,1,2) p2 += plot(linspace(1,n,n),s.coflatMap(myFilter).coflatMap(myFilter).coflatMap(myFilter).coflatMap(myFilter).coflatMap(myFilter).take(n)) p2.ylim = (0.0,1.0) p2.title = "Smoothed with 5 applications of the linear filter" fig.refresh

Streaming data is in no way the only context in which a comonadic approach facilitates an elegant approach to scientific and statistical computing. Comonads crop up anywhere where we want to extend a computation that is local to a small part of a data structure to the full data structure. Another commonly cited area of application of comonadic approaches is image processing (I should acknowledge that this section of the post is very much influenced by a blog post on comonadic image processing in Haskell). However, the kinds of operations used in image processing are in many cases very similar to the operations used in finite difference approaches to numerical integration of partial differential equations (PDEs) such as the heat equation, so in this section I will blur (sic) the distinction between the two, and numerically integrate the 2D heat equation in order to Gaussian blur a noisy image.

First we need a simple image type which can have pixels of arbitrary type `T`

(this is very important – all functors must be fully type polymorphic).

import scala.collection.parallel.immutable.ParVector case class Image[T](w: Int, h: Int, data: ParVector[T]) { def apply(x: Int, y: Int): T = data(x*h+y) def map[S](f: T => S): Image[S] = Image(w, h, data map f) def updated(x: Int, y: Int, value: T): Image[T] = Image(w,h,data.updated(x*h+y,value)) }

Here I’ve chosen to back the image with a parallel immutable vector. This wasn’t necessary, but since this type has a `map`

operation which automatically parallelises over multiple cores, any `map`

operations applied to the image will be automatically parallelised. This will ultimately lead to all of our statistical computations being automatically parallelised without us having to think about it.

As it stands, this image isn’t comonadic, since it doesn’t implement `extract`

or `coflatMap`

. Unlike the case of `Stream`

, there isn’t really a uniquely privileged pixel, so it’s not clear what `extract`

should return. For many data structures of this type, we make them comonadic by adding a “cursor” pointing to a “current” element of interest, and use this as the focus for computations applied with `coflatMap`

. This is simplest to explain by example. We can define our “pointed” image type as follows:

case class PImage[T](x: Int, y: Int, image: Image[T]) { def extract: T = image(x, y) def map[S](f: T => S): PImage[S] = PImage(x, y, image map f) def coflatMap[S](f: PImage[T] => S): PImage[S] = PImage( x, y, Image(image.w, image.h, (0 until (image.w * image.h)).toVector.par.map(i => { val xx = i / image.h val yy = i % image.h f(PImage(xx, yy, image)) })))

There is missing a closing brace, as I’m not quite finished. Here `x`

and `y`

represent the location of our cursor, so `extract`

returns the value of the pixel indexed by our cursor. Similarly, `coflatMap`

forms an image where the value of the image at each location is the result of applying the function `f`

to the image which had the cursor set to that location. Clearly `f`

should use the cursor in some way, otherwise the image will have the same value at every pixel location. Note that `map`

and `coflatMap`

operations will be automatically parallelised. The intuitive idea behind `coflatMap`

is that it extends local computations. For the stream example, the local computation was a linear combination of nearby values. Similarly, in image analysis problems, we often want to apply a linear filter to nearby pixels. We can get at the pixel at the cursor location using `extract`

, but we probably also want to be able to move the cursor around to nearby locations. We can do that by adding some appropriate methods to complete the class definition.

def up: PImage[T] = { val py = y-1 val ny = if (py >= 0) py else (py + image.h) PImage(x,ny,image) } def down: PImage[T] = { val py = y+1 val ny = if (py < image.h) py else (py - image.h) PImage(x,ny,image) } def left: PImage[T] = { val px = x-1 val nx = if (px >= 0) px else (px + image.w) PImage(nx,y,image) } def right: PImage[T] = { val px = x+1 val nx = if (px < image.w) px else (px - image.w) PImage(nx,y,image) } }

Here each method returns a new pointed image with the cursor shifted by one pixel in the appropriate direction. Note that I’ve used periodic boundary conditions here, which often makes sense for numerical integration of PDEs, but makes less sense for real image analysis problems. Note that we have embedded all “indexing” issues inside the definition of our classes. Now that we have it, none of the statistical algorithms that we develop will involve any explicit indexing. This makes it much less likely to develop algorithms containing bugs corresponding to “off-by-one” or flipped axis errors.

This class is now fine for our requirements. But if we wanted Cats to understand that this structure is really a comonad (perhaps because we wanted to use derived methods, such as `coflatten`

), we would need to provide evidence for this. The details aren’t especially important for this post, but we can do it simply as follows:

implicit val pimageComonad = new Comonad[PImage] { def extract[A](wa: PImage[A]) = wa.extract def coflatMap[A,B](wa: PImage[A])(f: PImage[A] => B): PImage[B] = wa.coflatMap(f) def map[A,B](wa: PImage[A])(f: A => B): PImage[B] = wa.map(f) }

It’s handy to have some functions for converting Breeze dense matrices back and forth with our image class.

import breeze.linalg.{Vector => BVec, _} def BDM2I[T](m: DenseMatrix[T]): Image[T] = Image(m.cols, m.rows, m.data.toVector.par) def I2BDM(im: Image[Double]): DenseMatrix[Double] = new DenseMatrix(im.h,im.w,im.data.toArray)

Now we are ready to see how to use this in practice. Let’s start by defining a very simple linear filter.

def fil(pi: PImage[Double]): Double = (2*pi.extract+ pi.up.extract+pi.down.extract+pi.left.extract+pi.right.extract)/6.0

This simple filter can be used to “smooth” or “blur” an image. However, from a more sophisticated viewpoint, exactly this type of filter can be used to represent one time step of a numerical method for time integration of the 2D heat equation. Now we can simulate a noisy image and apply our filter to it using `coflatMap`

:

import breeze.stats.distributions.Gaussian val bdm = DenseMatrix.tabulate(200,250){case (i,j) => math.cos( 0.1*math.sqrt((i*i+j*j))) + Gaussian(0.0,2.0).draw} val pim0 = PImage(0,0,BDM2I(bdm)) def pims = Stream.iterate(pim0)(_.coflatMap(fil))

Note that here, rather than just applying the filter once, I’ve generated an infinite stream of pointed images, each one representing an additional application of the linear filter. Thus the sequence represents the time solution of the heat equation with initial condition corresponding to our simulated noisy image.

We can render the first few frames to check that it seems to be working.

import breeze.plot._ val fig = Figure("Diffusing a noisy image") pims.take(25).zipWithIndex.foreach{case (pim,i) => { val p = fig.subplot(5,5,i) p += image(I2BDM(pim.image)) }}

Note that the numerical integration is carried out in parallel on all available cores automatically. Other image filters can be applied, and other (parabolic) PDEs can be numerically integrated in an essentially similar way.

Another place where the concept of extending a local computation to a global computation crops up is in the context of Gibbs sampling a high-dimensional probability distribution by cycling through the sampling of each variable in turn from its full-conditional distribution. I’ll illustrate this here using the Ising model, so that I can reuse the pointed image class from above, but the principles apply to any Gibbs sampling problem. In particular, the Ising model that we consider has a conditional independence structure corresponding to a graph of a square lattice. As above, we will use the comonadic structure of the square lattice to construct a Gibbs sampler. However, we can construct a Gibbs sampler for arbitrary graphical models in an essentially identical way by using a graph comonad.

Let’s begin by simulating a random image containing +/-1s:

import breeze.stats.distributions.{Binomial,Bernoulli} val beta = 0.4 val bdm = DenseMatrix.tabulate(500,600){ case (i,j) => (new Binomial(1,0.2)).draw }.map(_*2 - 1) // random matrix of +/-1s val pim0 = PImage(0,0,BDM2I(bdm))

We can use this to initialise our Gibbs sampler. We now need a Gibbs kernel representing the update of each pixel.

def gibbsKernel(pi: PImage[Int]): Int = { val sum = pi.up.extract+pi.down.extract+pi.left.extract+pi.right.extract val p1 = math.exp(beta*sum) val p2 = math.exp(-beta*sum) val probplus = p1/(p1+p2) if (new Bernoulli(probplus).draw) 1 else -1 }

So far so good, but there a couple of issues that we need to consider before we plough ahead and start coflatMapping. The first is that pure functional programmers will object to the fact that this function is not pure. It is a stochastic function which has the side-effect of mutating the random number state. I’m just going to duck that issue here, as I’ve previously discussed how to fix it using probability monads, and I don’t want it to distract us here.

However, there is a more fundamental problem here relating to parallel versus sequential application of Gibbs kernels. `coflatMap`

is conceptually parallel (irrespective of how it is implemented) in that all computations used to build the new comonad are based solely on the information available in the starting comonad. OTOH, detailed balance of the Markov chain will only be preserved if the kernels for each pixel are applied sequentially. So if we `coflatMap`

this kernel over the image we will break detailed balance. I should emphasise that this has nothing to do with the fact that I’ve implemented the pointed image using a parallel vector. Exactly the same issue would arise if we switched to backing the image with a regular (sequential) immutable `Vector`

.

The trick here is to recognise that if we coloured alternate pixels black and white using a chequerboard pattern, then all of the black pixels are conditionally independent given the white pixels and vice-versa. Conditionally independent pixels *can* be updated by parallel application of a Gibbs kernel. So we just need separate kernels for updating odd and even pixels.

def oddKernel(pi: PImage[Int]): Int = if ((pi.x+pi.y) % 2 != 0) pi.extract else gibbsKernel(pi) def evenKernel(pi: PImage[Int]): Int = if ((pi.x+pi.y) % 2 == 0) pi.extract else gibbsKernel(pi)

Each of these kernels can be coflatMapped over the image preserving detailed balance of the chain. So we can now construct an infinite stream of MCMC iterations as follows.

def pims = Stream.iterate(pim0)(_.coflatMap(oddKernel). coflatMap(evenKernel))

We can animate the first few iterations with:

import breeze.plot._ val fig = Figure("Ising model Gibbs sampler") fig.width = 1000 fig.height = 800 pims.take(50).zipWithIndex.foreach{case (pim,i) => { print(s"$i ") fig.clear val p = fig.subplot(1,1,0) p.title = s"Ising model: frame $i" p += image(I2BDM(pim.image.map{_.toDouble})) fig.refresh }} println

Here I have a movie showing the first 1000 iterations. Note that youtube seems to have over-compressed it, but you should get the basic idea.

Again, note that this MCMC sampler runs in parallel on all available cores, automatically. This issue of odd/even pixel updating emphasises another issue that crops up a lot in functional programming: very often, thinking about how to express an algorithm functionally leads to an algorithm which parallelises naturally. For general graphs, figuring out which groups of nodes can be updated in parallel is essentially the graph colouring problem. I’ve discussed this previously in relation to parallel MCMC in:

Wilkinson, D. J. (2005) Parallel Bayesian Computation, Chapter 16 in E. J. Kontoghiorghes (ed.) *Handbook of Parallel Computing and Statistics*, Marcel Dekker/CRC Press, 481-512.

There are quite a few blog posts discussing comonads in the context of Haskell. In particular, the post on comonads for image analysis I mentioned previously, and this one on cellular automata. Bartosz’s post on comonads gives some connection back to the mathematical origins. Runar’s Scala comonad tutorial is the best source I know for comonads in Scala.

Full runnable code corresponding to this blog post is available from my blog repo.

]]>As discussed in the previous post, I’ve recently constructed and delivered a short course on statistical computing with Scala. Much of the course is concerned with writing statistical algorithms in Scala, typically making use of the scientific and numerical computing library, Breeze. Breeze has all of the essential tools necessary for building statistical algorithms, but doesn’t contain any higher level modelling functionality. As part of the course, I walked through how to build a small library for regression modelling on top of Breeze, including all of the usual regression diagnostics (such as standard errors, t-statistics, p-values, F-statistics, etc.). While preparing the course materials it occurred to me that it would be useful to package and document this code properly for general use. In advance of the course I packaged the code up into a bare-bones library, but since then I’ve fleshed it out, tidied it up and documented it properly, so it’s now ready for people to use.

The library covers PCA, linear regression modelling and simple one-parameter GLMs (including logistic and Poisson regression). The underlying algorithms are fairly efficient and numerically stable (eg. linear regression uses the QR decomposition of the model matrix, and the GLM fitting uses QR within each IRLS step), though they are optimised more for clarity than speed. The library also includes a few utility functions and procedures, including a pairs plot (scatter-plot matrix).

Plenty of documentation is available from the scala-glm github repo which I won’t repeat here. But to give a rough idea of how things work, I’ll run through an interactive session for the linear regression example.

First, download a dataset from the UCI ML Repository to disk for subsequent analysis (caching the file on disk is good practice, as it avoids unnecessary load on the UCI server, and allows running the code off-line):

import scalaglm._ import breeze.linalg._ val url = "http://archive.ics.uci.edu/ml/machine-learning-databases/00291/airfoil_self_noise.dat" val fileName = "self-noise.csv" // download the file to disk if it hasn't been already val file = new java.io.File(fileName) if (!file.exists) { val s = new java.io.PrintWriter(file) val data = scala.io.Source.fromURL(url).getLines data.foreach(l => s.write(l.trim. split('\t').filter(_ != ""). mkString("", ",", "\n"))) s.close }

Once we have a CSV file on disk, we can load it up and look at it.

val mat = csvread(new java.io.File(fileName)) // mat: breeze.linalg.DenseMatrix[Double] = // 800.0 0.0 0.3048 71.3 0.00266337 126.201 // 1000.0 0.0 0.3048 71.3 0.00266337 125.201 // 1250.0 0.0 0.3048 71.3 0.00266337 125.951 // ... println("Dim: " + mat.rows + " " + mat.cols) // Dim: 1503 6 val figp = Utils.pairs(mat, List("Freq", "Angle", "Chord", "Velo", "Thick", "Sound")) // figp: breeze.plot.Figure = breeze.plot.Figure@37718125

We can then regress the response in the final column on the other variables.

val y = mat(::, 5) // response is the final column // y: DenseVector[Double] = DenseVector(126.201, 125.201, ... val X = mat(::, 0 to 4) // X: breeze.linalg.DenseMatrix[Double] = // 800.0 0.0 0.3048 71.3 0.00266337 // 1000.0 0.0 0.3048 71.3 0.00266337 // 1250.0 0.0 0.3048 71.3 0.00266337 // ... val mod = Lm(y, X, List("Freq", "Angle", "Chord", "Velo", "Thick")) // mod: scalaglm.Lm = // Lm(DenseVector(126.201, 125.201, ... mod.summary // Estimate S.E. t-stat p-value Variable // --------------------------------------------------------- // 132.8338 0.545 243.866 0.0000 * (Intercept) // -0.0013 0.000 -30.452 0.0000 * Freq // -0.4219 0.039 -10.847 0.0000 * Angle // -35.6880 1.630 -21.889 0.0000 * Chord // 0.0999 0.008 12.279 0.0000 * Velo // -147.3005 15.015 -9.810 0.0000 * Thick // Residual standard error: 4.8089 on 1497 degrees of freedom // Multiple R-squared: 0.5157, Adjusted R-squared: 0.5141 // F-statistic: 318.8243 on 5 and 1497 DF, p-value: 0.00000 val fig = mod.plots // fig: breeze.plot.Figure = breeze.plot.Figure@60d7ebb0

There is a `.predict`

method for generating point predictions (and standard errors) given a new model matrix, and fitting GLMs is very similar – these things are covered in the quickstart guide for the library.

scala-glm is a small Scala library built on top of the Breeze numerical library which enables simple and convenient regression modelling in Scala. It is reasonably well documented and usable in its current form, but I intend to gradually add additional features according to demand as time permits.

]]>