Hello,
I'm having an issue where nimble behaves correctly when running a sampler with a custom update in R, but not after it is compiled. I do not get any errors from Nimble, so I don't know what I've done in the custom update that does not work after compilation.
The model is a spatial capture-recapture model for individuals identified by their left and right flanks, but in the case where some or all of the flank matches are not known. The custom sampler involves updating the latent left-flank and right-flank capture histories. When I run the sampler in R through nimble (Rmcmc$run()), all the flanks are being updated, but after it compiles (Cmcmc$run()), only some of the flanks are being updated. This is the model code:
NimModel <- nimbleCode({
#detection function priors
p0B~dunif(0,1)
#if you do not want to share p0L and p0R
# p0L~dunif(0,1)
# p0R~dunif(0,1)
# #if you want to share p0L and p0R
p0S ~ dunif(0,1)
p0L <- p0S
p0R <- p0S
#Make sure upper bound on sigma uniform prior is appropriate
sigma~dunif(0,20)
#data augmentation prior
psi~dunif(0,1)
#likelihoods (except for s priors)
for(i in 1:M) {
z[i] ~ dbern(psi)
s[i,1] ~ dunif(xlim[1],xlim[2])
s[i,2] ~ dunif(ylim[1],ylim[2])
d2[i,1:J] <- GetD2(s = s[i,1:2], X = X[1:J,1:2],z=z[i])
pd.B[i,1:J] <- GetDetectionProb(p0=p0B,sigma=sigma,d2=d2[i,1:J],z=z[i])
pd.L[i,1:J] <- GetDetectionProb(p0=p0L,sigma=sigma,d2=d2[i,1:J],z=z[i])
pd.R[i,1:J] <- GetDetectionProb(p0=p0R,sigma=sigma,d2=d2[i,1:J],z=z[i])
y.B.true[i,1:J,1:K] ~ dBernoulliVectorBoth(pd.B[i,1:J],K2D=K2D[1:J,1:K],z=z[i])
y.L.true[i,1:J,1:K] ~ dBernoulliVectorSingle(pd.L[i,1:J],K2D=K2D[1:J,1:K],z=z[i])
y.R.true[i,1:J,1:K] ~ dBernoulliVectorSingle(pd.R[i,1:J],K2D=K2D[1:J,1:K],z=z[i])
}
#have to trick Nimble to know ID is part of model by using it somewhere in model statement
IDdummy <- IDdummyfun(ID.L=ID.L[1:n.L],ID.R=ID.R[1:n.R])
N <- sum(z[1:M])
})# end model
and this is the left flank update. The right flank update is exactly the same, except it considers the other flank. I replace the default nimble update for y.L.true with this. With no known flank linkages, "n.fixed" will be 0, so I should be looping over 1:n.L.
#Left flank update
IDLSampler <- nimbleFunction(
contains = sampler_BASE,
setup = function(model, mvSaved, target, control) {
K2D <- control$K2D
n.L <- control$n.L
n.fixed <- control$n.fixed
prop.scale <- control$prop.scale #can scale the distance proposal, but a value of 1 should be a good choice.
calcNodes <- model$getDependencies(target)
},
run = function() {
if(n.L>0){ #skip if no lefts
z <- model$z
s <- model$s
y.L.true <- model$y.L.true
ID.L<- model$ID.L
pd.L <- model$pd.L
M <- nimDim(y.L.true)[1]
J <- nimDim(y.L.true)[2]
K <- nimDim(y.L.true)[3]
#precalculate log likelihoods. Can pull from nimble, but structure depends on nimble model structure (vectorized or not)
ll.y.L <- array(0,dim=c(M,J,K))
for(i in 1:M){
if(z[i]==1){
for(j in 1:J){
for(k in 1:K){
if(K2D[j,k]==1){
ll.y.L[i,j,k]=dbinom(y.L.true[i,j,k],prob=pd.L[i,j],size=1,log=TRUE)
}else if(K2D[j,k]==2){
ll.y.L[i,j,k]=dbinom(y.L.true[i,j,k],prob=2*pd.L[i,j]-pd.L[i,j]^2,size=1,log=TRUE)
}
}
}
}
}
ll.y.L.cand <- ll.y.L
for(l in (n.fixed+1):n.L){
y.L.true.cand <- y.L.true
this.i=ID.L[l]
#select an individual to swap it to
d2=(s[this.i,1]-s[,1])^2+(s[this.i,2]-s[,2])^2
propprobs=exp(-d2/(2*(prop.scale*model$sigma)^2)) #distance-based proposal
propprobs[this.i]=0 #don't choose focal
propprobs[z==0]=0 #must select an individual with z==1
if(n.fixed>0){
propprobs[1:n.fixed]=0 #cannot select an individual with fixed flanks
}
propprobs=propprobs/sum(propprobs)
cand.i=rcat(1,propprobs)
cand.ID.idx=which(ID.L==cand.i)#which ID.L index is this individual. Will be empty if not in ID.L
y.L.true.cand[this.i,,] <- y.L.true[cand.i,,]
y.L.true.cand[cand.i,,] <- y.L.true[this.i,,]
#update ll.y.L
for(j in 1:J){
for(k in 1:K){
if(K2D[j,k]==1){
ll.y.L.cand[this.i,j,k]=dbinom(y.L.true.cand[this.i,j,k],prob=pd.L[this.i,j],size=1,log=TRUE)
ll.y.L.cand[cand.i,j,k]=dbinom(y.L.true.cand[cand.i,j,k],prob=pd.L[cand.i,j],size=1,log=TRUE)
}else if(K2D[j,k]==2){
ll.y.L.cand[this.i,j,k]=dbinom(y.L.true.cand[this.i,j,k],prob=2*pd.L[this.i,j]-pd.L[this.i,j]^2,size=1,log=TRUE)
ll.y.L.cand[cand.i,j,k]=dbinom(y.L.true.cand[cand.i,j,k],prob=2*pd.L[cand.i,j]-pd.L[cand.i,j]^2,size=1,log=TRUE)
}
}
}
#calculate backwards proposal probs
d2=(s[cand.i,1]-s[,1])^2+(s[cand.i,2]-s[,2])^2
backprobs=exp(-d2/(2*(prop.scale*model$sigma)^2)) #distance-based proposal
backprobs[cand.i]=0 #don't choose focal
backprobs[z==0]=0 #must select an individual with z==1
if(n.fixed>0){
backprobs[1:n.fixed]=0 #cannot select an individual with fixed flanks
}
backprobs=backprobs/sum(backprobs)
lp_initial <- sum(ll.y.L[this.i,,])+sum(ll.y.L[cand.i,,])
lp_proposed <- sum(ll.y.L.cand[this.i,,])+sum(ll.y.L.cand[cand.i,,])
log_MH_ratio <- (lp_proposed+log(backprobs[this.i])) - (lp_initial+log(propprobs[cand.i]))
accept <- decide(log_MH_ratio)
if(accept){
y.L.true[this.i,,]=y.L.true.cand[this.i,,]
y.L.true[cand.i,,]=y.L.true.cand[cand.i,,]
ll.y.L[this.i,,]=ll.y.L.cand[this.i,,]
ll.y.L[cand.i,,]=ll.y.L.cand[cand.i,,]
#swap flank indices
ID.L[l]=cand.i
#if cand.i was in ID.L, update this ID index
tmp=nimDim(cand.ID.idx)[1]
if(tmp>0){
ID.L[cand.ID.idx]=this.i
}
}
}
#put everything back into model$stuff
model$y.L.true <<- y.L.true
model$ID.L <<- ID.L
model$calculate(calcNodes) #update logprob
copy(from = model, to = mvSaved, row = 1, nodes = calcNodes, logProb = TRUE)
}
},
methods = list( reset = function () {} )
)
I can provide a reproducible example, but perhaps someone can easily spot what I've done here that does not work after compiling (and escapes nimble's error checks).
Any help would be much appreciated.
Ben Augustine