nimble-dev / nimble

The base NIMBLE package for R
http://R-nimble.org
BSD 3-Clause "New" or "Revised" License
158 stars 24 forks source link

passing variables (including an element of a vector) as second arg to getParam gives unhelpful error message #1344

Closed paciorek closed 11 months ago

paciorek commented 1 year ago

Here's a user's code where they were trying to parameterize usage of getParam. They're trying to use positional value as the second arg, but even if trying to use a vector of strings, the issue still occurs. It's occurring because we are creating new setup code and eval'ing it in the context of the an environment in which i is not defined/meaningful.

We should give a better error message. But we might also want to think more about what the user is trying to do.

N <- 1000 # number of observations
num.X = 10 # number of predictors
beta0 <- 1  # intercept
sigma <- rexp(1) # prior variance
beta1 <- rnorm(num.X, 0, sigma) # slopes
X <- matrix(rnorm(n=N*num.X),ncol = num.X)  # standard normal predictors
eta <- beta0 + X%*%beta1 # linear  predictor
lambda <- exp(eta)  # link function
set.seed(1);y <- rpois(n=N, lambda=lambda) # generate data 

#################################
########### BUGS code ###########
#################################

pois.glm <- nimbleCode({
  ## Likelihood
    for(i in 1:N){
      y[i] ~ dpois(lambda[i])
      log(lambda[i]) <- eta[i]
      eta[i] <- inprod(beta[1:k],X[i,])
    }
    for(l in 1:k){
      beta[l] ~ dnorm(0,1)
    }
})

#################################
############ sampler ############
#################################

test_sampler <- nimbleFunction(
  contains = sampler_BASE,
  name = 'test_sampler',
  setup = function(model, mvSaved, target, control) {
    targetAsScalar <- model$expandNodeNames(target, returnScalarComponents = T)
    priorParID <- integer(length(targetAsScalar))
    for(i in 1:length(targetAsScalar)){
      priorParID[i] <- which(names(getDistributionInfo(model$getDistribution(targetAsScalar[i]))$paramIDs)%in%c("var","cov"))
    }
    priorCov <- double(length(targetAsScalar))
  },
  run = function(){
      for(i in 1:length(targetAsScalar)){
          priorCov[i] <- model$getParam(targetAsScalar[i], priorParID[i])
          print(priorCov[i])
      }

    copy(from = model, to = mvSaved, row = 1, 
         nodes = target, logProb = TRUE)
  },
method = list(reset = function () {}))

#################################
############# fit it ############
#################################

dat <- list(X=cbind(1,X),
            y=y)
const <- list(
  N=N,
  k = num.X+1)
# construct model object
model <- nimbleModel(pois.glm, const, dat, buildDerivs = T)
mod <- nimble::compileNimble(model)
# briefly test sampler
#test_sampler(model,model,"beta")$priorParID # works
test_sampler(model,model,"beta")$run() # works

# assign sampler
nimbleMCMCconf <- configureMCMC(mod, monitors = "beta", print = FALSE)
nimbleMCMCconf$removeSamplers()
nimbleMCMCconf$addSampler("beta",type="test_sampler", print = T)
nimbleMCMCb <- buildMCMC(nimbleMCMCconf)
nimbleMCMCc <- compileNimble(nimbleMCMCb, project = mod) # eek: getParam throws error
Error in eval(newSetupCodeOneExpr, envir = instance) : 
  object 'i' not found
paciorek commented 11 months ago

This is occurring in nfProcessing$evalNewSetupLinesOneInstance.

Perhaps more concerningly, we also have a scoping problem here. If one sets i in the global env, then the eval will use it.

I'm adding a check in the getParam keyword processor to make sure that all vars in the param expression are found in the nfProc symbol table.

One note is the uncompiled execution will still look for i (in the example) in run and then outside of the nimbleFunction if not otherwise found.

paciorek commented 11 months ago

Confusingly I named the branch fix_1144.

paciorek commented 11 months ago

Trapped in PR #1370