This vignette details the sampling of different subsets, i.e. blocks, of the parameter vector by different log-Density functions. The background is described in Wutzler & Carvalhais 2014 (doi: 10.1002/2014jg002650).
The used model and snythethic observations data are provided with data set twTwoDenEx1
.
library(twDEMC)
data(twTwoDenEx1)
set.seed(0815) # for reproducable results
twTwoDenEx1$fModel = function(
theta ##<< model parameters a and b
, xSparse ##<< numeric vector of Sparse input/output relationship
, xRich ##<< numeric vector of rich input/output relationship
, thresholdCovar=0 ##<< model structural deficiency
){
list(
y1 = as.numeric(theta[1]*xSparse + theta[2]*mean(xRich)/10)
,y2 = as.numeric(theta[1]*xSparse[1] + theta[2]*pmax(0,xRich-thresholdCovar) )
)
}
To give some meaning to this artificial example, model output y1
may represents a sparse (n=10) series of annual observations. It uses all covariates xSparse
but is based solely on a long-term average of fast changing covariate xRich
. Model output y2
may represent a short measurement campaign, during which much information was collected (n=1000). During this campaign, the output varies with covariate xRich
but xSparse
is assumed to be constant during the campaign.
The synthetic observations in twTwoDenEx1
have been generated with parameter thresholdCovar=0.3
. However, in order to demonstrate some effects of using a slighly biased model, this parameter is assumed to be zero during the model inversion.
#thresholdCovar = 0.3 # the true value
thresholdCovar = 0 # the effective biased model that glosses over this threshold
The first parameter, a
(theta[1]
), is more closely related to one observation stream y1
and the other model parameter, b
(theta[2]
), is more closely related to the other observation stream y2
. Here, the two parameters will be calibrated by different observation data streams.
The first density is based on the misfit to the sparse observations y1
only. Similarly, the second density is based on the rich observations y2
only.
denSparse <- function( theta, twTwoDenEx=twTwoDenEx1, ... ){
pred <- twTwoDenEx$fModel(theta, xSparse=twTwoDenEx$xSparse, xRich=twTwoDenEx$xRich, ...)
misfit <- twTwoDenEx$obs$y1 - pred$y1
structure( -1/2 * sum((misfit/twTwoDenEx$sdObs$y1)^2), names='y1' )
}
denRich <- function( theta, twTwoDenEx=twTwoDenEx1, ... ){
pred <- twTwoDenEx$fModel(theta, xSparse=twTwoDenEx$xSparse, xRich=twTwoDenEx$xRich, ...)
misfit <- twTwoDenEx$obs$y2 - pred$y2
structure( -1/2 * sum((misfit/twTwoDenEx$sdObs$y2)^2), names='y2' )
}
The description of used densities to in argument dInfos
of twDEMCSA
has to include two entries.
dInfos=list(
dSparse=list(fLogDen=denSparse,
argsFLogDen=list(twTwoDenEx=twTwoDenEx1,
thresholdCovar=thresholdCovar)),
dRich=list(fLogDen=denRich,
argsFLogDen=list(twTwoDenEx=twTwoDenEx1,
thresholdCovar=thresholdCovar))
)
Argument blocks
of function twDEMCSA
describes which parameter are updated by which block-update functions. By default, the Metropolis-rule is based on a single density for all parameters. The following specification tells to use Metropolis-update rule but with different densities for the two parameter blocks (each consisting of a single parameter). The identifiers given by dInfoPos must correspond to list entries in log-Density specification dInfos
.
blocks = list(
a=list(compPos="a", dInfoPos="dSparse"),
b=list(compPos="b", dInfoPos="dRich")
)
With these specifications the simulated annealing and the sampling of the converged chains at target temperature can be performed, plot the progress, and assess the properties of the posterior parameter from the sample.
resBlock <- resBlock0 <- twDEMCSA(
theta=twTwoDenEx1$thetaTrue,
covarTheta=diag((twTwoDenEx1$thetaTrue*0.3)^2),
dInfos=list(
dSparse=list(fLogDen=denSparse,
argsFLogDen=list(twTwoDenEx=twTwoDenEx1,
thresholdCovar=thresholdCovar)),
dRich=list(fLogDen=denRich,
argsFLogDen=list(twTwoDenEx=twTwoDenEx1,
thresholdCovar=thresholdCovar))
),
blocks = list(
a=list(compPos="a", dInfoPos="dSparse"),
b=list(compPos="b", dInfoPos="dRich")
),
nObs = c( y1=length(twTwoDenEx1$obs$y1),
y2=length(twTwoDenEx1$obs$y2) ),
nGen=2048
)
resConv <- twDEMCBlock( resBlock0, nGen=ceiling(256 * resBlock$thin / getNChains(resBlock)), extendRun=FALSE )
plot(as.mcmc.list(resBlock), smooth=FALSE)
# plot(as.mcmc.list(resConv), smooth=FALSE)
ss <- stackChains(resConv)
summary(ss)
## dSparse dRich a b
## Min. :-58.142 Min. :-32750 Min. :0.8638 Min. :0.8847
## 1st Qu.:-13.960 1st Qu.: -3846 1st Qu.:1.0129 1st Qu.:1.1455
## Median : -8.164 Median : -2476 Median :1.0659 Median :1.2382
## Mean :-11.932 Mean : -3613 Mean :1.0716 Mean :1.2293
## 3rd Qu.: -5.325 3rd Qu.: -2042 3rd Qu.:1.1287 3rd Qu.:1.3112
## Max. : -4.112 Max. : -1245 Max. :1.2952 Max. :1.5672
The two data streams are conflicting giving the somewhat biased model. Therefore, the two pdfs dSparse
and dRich
peak at different locations in parameter space. The color code of the following figure ranks the samples by different criteria.
oldPar <- par(mfrow=c(1,5), par(mar=c(2.0,3.3,2.0,0)+0.3 ) )
colh <- heat.colors(nrow(ss))
plot( b ~ a, data=as.data.frame(ss[ rev(order(ss[,"dSparse"])),])
, col=colh, main="rank dSparse" )
plot( b ~ a, data=as.data.frame(ss[ rev(order(ss[,"dRich"])),])
, col=colh, main="rank dRich" )
plot( b ~ a, data=as.data.frame(ss[ rev(order(rowSums(ss[,c("dSparse","dRich")]))),])
, col=colh, main="rank sum" )
plot( b ~ a, data=as.data.frame(ss[ rev(order(rowSums(apply(ss[,c("dSparse","dRich")],2,rank)) )),])
, col=colh, main="sum ranks" )
plot( b ~ a, data=as.data.frame(ss[ orderLogDen(ss),])
, col=colh, main="min ranks" )
The right plot shows that the sparse data stream effectively constrains parameter a. The second plot shows that the rich data stream constrains a combination of both parameters with best parameters having very low values of a.
The third plot shows that the influcence of the sparse data stream is negligible in the sum of the two densities.
Only with the last two plots, the color code roughly matches the density of the sample.
Instead of ranking the sum of the densities, the sum or the minimum of both density ranks is a good criterion for determining the best parameters based on given set of densities. The last criterion is implemented in function orderLogDen
.
Note that the forward model twTwoDenEx$fModel
is called from both blocks in respective log-Density-functions denSparse
and denRich
. Often, running the forward model is computationally expensive, so it would be wise to save unnecessary runs.
E.g. when block b changes parameter b, the log-Density of block a needs to be recalculated with updated parameter b. However, the corresponding model forward model run has already been performed in block b. This information should be transferred to block a.
The package provides a way of transferring intermediate results that are in common between blocks. Blocks can communicate the intermediate results by attaching an attribute intermediate
to its return value. This will then be provided to other block update functions with argument intermediate
.
If several blocks share an intermediate result, care must be taken, that the intermediate is updated, each time when parameters for the forward run have changed. E.g., the Metropolis update function will provide a NULL intermediate to the density function after proposing a new set of parameters.
The updated log-Density function of the parameters and the updated residual function read:
denSparseInt <- function( theta, intermediate=list(), twTwoDenEx=twTwoDenEx1, ... ){
if( !length(intermediate) ){
intermediate <- twTwoDenEx$fModel(theta, xSparse=twTwoDenEx$xSparse, xRich=twTwoDenEx$xRich, ...)
}
pred <- intermediate
misfit <- twTwoDenEx$obs$y1 - pred$y1
structure( -1/2 * sum((misfit/twTwoDenEx$sdObs$y1)^2), names='y1', intermediate=intermediate )
}
denRichInt <- function( theta, intermediate=list(), twTwoDenEx=twTwoDenEx1, ... ){
if( !length(intermediate) ){
intermediate <- twTwoDenEx$fModel(theta, xSparse=twTwoDenEx$xSparse, xRich=twTwoDenEx$xRich, ...)
}
pred <- intermediate
misfit <- twTwoDenEx$obs$y2 - pred$y2
structure( -1/2 * sum((misfit/twTwoDenEx$sdObs$y2)^2), names='y2', intermediate=intermediate )
}
Note the additional function argument intermediate
, and the attachment as an attribute to the result of the function.
In order to tell twDEMCSA
which intermediates need to be exchanged, specify the same entry intermediateId
in the blocks descriptions.
resBlock <- resBlockInt <- twDEMCSA(
theta=twTwoDenEx1$thetaTrue,
covarTheta=diag((twTwoDenEx1$thetaTrue*0.3)^2),
dInfos=list(
dSparse=list(fLogDen=denSparseInt,
argsFLogDen=list(twTwoDenEx=twTwoDenEx1,
thresholdCovar=thresholdCovar)),
dRich=list(fLogDen=denRichInt,
argsFLogDen=list(twTwoDenEx=twTwoDenEx1,
thresholdCovar=thresholdCovar))
),
blocks = list(
a=list(compPos="a", dInfoPos="dSparse", intermediateId="resFModel"),
b=list(compPos="b", dInfoPos="dRich", intermediateId="resFModel")
),
nObs = c( y1=length(twTwoDenEx1$obs$y1),
y2=length(twTwoDenEx1$obs$y2) ),
nGen=2048
)
resConvInt <- twDEMCBlock( resBlockInt, extendRun=FALSE,
nGen=ceiling(256 * resBlock$thin / getNChains(resBlock)))
During the sampling of the converged chains, only about 2000 of the 3000 calls to the forward functions are required compared to not reusing the intermediate result.
ssInt <- stackChains(resConvInt)
summary(ssInt)
## dSparse dRich a b
## Min. :-78.495 Min. :-24163 Min. :0.8353 Min. :0.8257
## 1st Qu.:-15.089 1st Qu.: -3986 1st Qu.:1.0076 1st Qu.:1.1568
## Median : -7.780 Median : -2653 Median :1.0649 Median :1.2258
## Mean :-13.190 Mean : -3461 Mean :1.0704 Mean :1.2314
## 3rd Qu.: -5.279 3rd Qu.: -2014 3rd Qu.:1.1267 3rd Qu.:1.3148
## Max. : -3.981 Max. : -1133 Max. :1.3573 Max. :1.5568