Add `nchains` option to `mcmc`
Closed this issue · 2 comments
So far in https://github.com/Roche/crmPack/blob/main/R/mcmc.R#L75 we are not using the nchains
argument of rjags::jags.model
, therefore we can only run a single MCMC chain at the moment.
We will need some downstream updates when adding the nchains
functionality, where maybe we can borrow inspiration from the way rstan
or cmdstanr
handles this.
To do:
- Design doc
- Separate small issues and PRs as much as possible
Ideas:
- Put chains as the last dimension of
Samples
and derived objects- e.g. a matrix with
nsamples
rows andnchains
columns
- e.g. a matrix with
- Have a
collapse()
method on theSamples
objects to collapse all chains into a single one upon request - For plots, by default if there is more than one chain present, use faceting across chains to visualize the different results and compare them
- e.g. multiple dose-response curves with one set of median and credible interval lines for each chain
- adapt the
get()
method to transform correctly intoggmcmc
object representing multiple chains - adapt the
tidy()
methods to convert multiple chains to tidy tibbles
I fully support this idea. It will allow us to add more diagnostics to confirm that the chains have converged correctly.
My first impression is that a comprehensive solution will be more difficult than it might at first seem. Adding the option to run more than one chain - and adapting downstream operations to deal with multiple chains - should be relatively straightforward. The greater difficulty may well be in in initialising the chains appropriately.
Because of the way JAGS works, it may not be necessary to introduce a new parameter. JAGS will run one chain for each list of initialisation parameters it is passed. At the moment,
crmPack
passes a list with one element for each parameter that JAGS needs. So if we pass a list of such lists as theinit
parameter ofjags.model
, then we will get a different chain of samples for each element of the outer list.
To test for convergence of the MCMC process robustly, different chains need to have different initial values. Ideally, the starting values should be in some way "extreme" to give credence to the belief that the chain will always converge (and to a constant value), regardless of how it is initialised. Thus, the initial values of a chain's parameters are model dependent, and should be different even for chains that are fitting the same model.
I cannot yet think of a simple way to do this, let alone to do this in a manner that does not break existing code. I believe this problem has two main parts:
- The fact that
crmPack
uses S4 objects - The way in which
crmPack
handles the interface between R and the JAGS executable
Currently, JAGS obtains initial values for model parameters via the init
slot of a Model
object. For example, here is the init
slot for LogisticLogNormal
:
init = function() {
list(theta = c(0, 1))
}
So the same initial values are used for all LogisticLogNormal
fits, regardless of the model's prior. The same is true for all other Model
classes.
So, first we need to be able to generate starting values that are "extreme" relative the prior defined in this specific Model
object. Initially, I thought we could do this using the same object's prior
model slot. But this is not possible for two reasons:
- There appears to be no way for one slot of an S4 object to access another slot of the same object.
S4
has no concept ofself::
or$this
. - The
priormodel
slot contains pseudocode. It's JAGS code, not R code. A call similar tomodel@priormodel()
with throw an exception, even if the model is correctly instantiated. For example:
my_model <- LogisticLogNormal(
mean = c(-0.85, 1),
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2),
ref_dose = 56
)
my_model@priormodel()
Error in my_model@priormodel() : object 'theta' not found
In this case, the priormodel
slot is
priormodel = function() {
theta ~ dmnorm(mean, prec)
alpha0 <- theta[1]
alpha1 <- exp(theta[2])
}
theta ~ dmnorm(mean, prec)
is not syntactically valid R code and, even if it were, mean
and prec
are not defined in scope.
One workaround might be to introduce a new slot chain_initialiser
or similar, which would be a function that could return appropriate initial values for the particular instance of the model. It could pick up appropriate parameter values from the parameters passed to the Model
's constructor.
The issue then would be to ensure consistency following subsequent changes to the model's prior.
I will think some more on this and start drafting a design document once #829 is complete.