Great! I was able to figure out how to complete the sampler. As a small note, the
values function, when compiled, returns a vector and threw an error. I thus replaced:
value <- values(model, dependentNodes[i])
with
value <- values(model, dependentNodes[i])[1]
which seems to work (as an aside, how do you get fixed width text in Google groups?)
The following code appears to work, checking against the known conjugacy result (in the univariate case). Feel free to post on as an example if you wish. I appreciate the help!
set.seed(123)
## If Y = X 1{a < X < b}. Let f = PDF of Y and g/G = PDF/CDF of X.
## f(y) = 1 / [G(b) - G(a)] g(y) 1{a < y < b}
dtruncbeta <- nimbleFunction(
run = function(
x = double(0), shape1 = double(0), shape2 = double(0),
a = double(0, default = 0), b = double(0, default = 1),
p <- exp(p)
}
if ( p <= 0 ) {
return(NaN)
} else if ( p >= 1) {
return(NaN)
} else {
CDFa <- pbeta(a, shape1, shape2, log.p = FALSE)
CDFb <- pbeta(b, shape1, shape2, log.p = FALSE)
return(
qbeta( CDFa + (CDFb - CDFa) * p , shape1 = shape1, shape2 = shape2)
)
}
}
)
## Use inverse-CDF method for random number generation
rtruncbeta <- nimbleFunction(
run = function(
n = integer(0), shape1 = double(0), shape2 = double(0),
a = double(0,default = 0), b = double(0, default = 1)
){
returnType(double(0))
return(qtruncbeta(runif(1), shape1 = shape1, shape2 = shape2, a = a, b = b, log.p = FALSE))
}
)
## Generate binomial data
n = 100
y <- rbinom(n, size = 1, prob = 0.25)
## Custom MCMC sampler for truncated beta
conjTruncBeta <- nimbleFunction(
name = 'conjTruncBeta',
contains = sampler_BASE,
setup = function(model, mvSaved, target, control) {
## query the model, to get all the stochastic dependents
## of the 'target' node:
dependentNodes <- model$getDependencies(target, stochOnly = TRUE, self = FALSE)
## just for the for-loop in the run code:
nDependentNodes <- length(dependentNodes)
},
run = function() {
shape1_post <- model$getParam(target, 'shape1') ## initialize to prior
shape2_post <- model$getParam(target, 'shape2') ## initialize to prior
for(i in 1:nDependentNodes) {
## get parameters of the dependent node distributions:
size <- model$getParam(dependentNodes[i], 'size') ## size of y[i]
value <- values(model, dependentNodes[i])[1] ## num successes in y[i]
## Compute posterior quantities based on conjugacy
shape1_post <- shape1_post + value
shape2_post <- shape2_post + (size - value)
}
## Draw from truncated beta using conjugacy
draw <- rtruncbeta(
1, shape1 = shape1_post, shape2 = shape2_post,
a = model$getParam(target, 'a'),
b = model$getParam(target, 'b')
)
## Store draw in model
model[[target]] <- draw
## Store draw in mvsaved
copy(from = model, to = mvSaved, row = 1,
nodes = dependentNodes, logProb = TRUE)
},
methods = list(
reset = function() { }
)
)
## Code for model
code <- nimbleCode({
gamma ~ dtruncbeta(2, 2, 0.10, 0.75)
for ( i in 1:n ) {
y[i] ~ dbinom(size = 1, prob = gamma)
}
})
## Configure the model and MCMC and compile both
mod <- nimbleModel(
code,
constants = list('n' = n),
data = list('y' = y),
init = list('gamma' = 0.2)
)
mod.comp <- compileNimble(mod)
mod.conf <- configureMCMC(mod)
mod.conf$removeSampler('gamma')
mod.conf$addSampler('gamma', 'conjTruncBeta')
mod.mcmc <- buildMCMC(mod.conf)
mod.mcmc.comp <- compileNimble(mod.mcmc, project = mod)
smpl <- runMCMC(mod.mcmc.comp, niter = 100000)
## Compare with direct sampling
nsucc <- sum(y)
nfail <- n - nsucc
hist(smpl, freq = F)
x <- seq(0.10, 0.75, length.out = 5000)
fx <- sapply(x, function(x) dtruncbeta(x, nsucc + 2.0, nfail + 2.0, a = 0.10, b = 0.75))
lines(x = x, y = fx, col = 'blue')