a nimbleFunction to convert a binary matrix to logical matrix

164 views
Skip to first unread message

Athul Sudheesh

unread,
Jan 2, 2024, 11:16:47 PM1/2/24
to nimble-users
So inside the nimbleCode, I want to convert a binary matrix to a logical matrix. I tried as.logical r function but I was getting error and in the error message, it was recommending me to implement a nimbleFunction to do the conversion. So I did it as shown below: 

convertToLogical <- nimbleFunction(
run = function(Q = double(2)){
J = dim(Q)[1]
K = dim(Q)[2]
ans = matrix(logical(0),nrow=J,ncol=K)
for (j in 1:J){
for (k in 1:K){
if (Q[j,k] == 1){
ans[j,k] = TRUE
}
else{
ans[j,k] = FALSE
}
}
}
return(ans)
returnType(logical(2))
})

But for some reason, the return type is still double and not logical..and hence when I am trying to compile the above code, I am getting the following error:

Error: returnType was declared void() (default) (or something invalid), which is not consistent with the object you are trying to return. This occurred for: return(ans) This was part of the call: { J = dim(ARG1_Q_)[1] K = dim(ARG1_Q_)[2] ans = nimNewMatrixD(value=Interm_6,init=TRUE,recycle=TRUE,nrow=J,ncol=K) for(j in 1:J) { for(k in 1:K) { if(ARG1_Q_[j, k] == 1) { ans[j, k] = TRUE } else { ans[j, k] = FALSE } } } return(ans) } Traceback: 1. compileNimble(convertToLogical) 2. project$compileRCfun(units[[i]], control = control, showCompilerOutput = showCompilerOutput) 3. needRCfunCppClass(fun, genNeededTypes = TRUE, initialTypeInference = initialTypeInference, . control = control) 4. RCfunInfos[[className]]$RCfunProc$process(debug = control$debug, . debugCpp = control$debugCpp, initialTypeInferenceOnly = FALSE,
...
8. eval(call(sizeCall, code, symTab, typeEnv)) 9. sizeReturn(<environment>, new("symbolTable", .xData = <environment>), . <environment>) 10. stop(exprClassProcessingErrorMsg(code, "returnType was declared void() (default) (or something invalid), which is not consistent with the object you are trying to return."), . call. = FALSE)

I want the Q to be a logical matrix because, I am using rows from the Q-Matrix to select elements from another matrix. For e.g., A[1,Q[1,]] and if Q[1,] is 0 0 1, then only the third element of A[1,] is returned. 

PS: Sorry, I am very new to nimble. 
But I am very much enjoying nimble. I have tried a couple of bayesian packages both in R and Julia  and nimble is my favorite now. Dear developers & maintainers of Nimble, thank you so much for creating an amazing bayesian modeling package!!! 

Athul Sudheesh

unread,
Jan 2, 2024, 11:19:03 PM1/2/24
to nimble-users
Sorry this is the correct error message

Error: Type double of the return() argument does not match type logical given in the returnType() statement (void is default). This occurred for: return(ans) This was part of the call: { J = dim(ARG1_Q_)[1] K = dim(ARG1_Q_)[2] ans = nimNewMatrixD(value=Interm_7,init=TRUE,recycle=TRUE,nrow=J,ncol=K) for(j in 1:J) { for(k in 1:K) { if(ARG1_Q_[j, k] == 1) { ans[j, k] = TRUE } else { ans[j, k] = FALSE } } } return(ans) } Traceback: 1. compileNimble(convertToLogical) 2. project$compileRCfun(units[[i]], control = control, showCompilerOutput = showCompilerOutput) 3. needRCfunCppClass(fun, genNeededTypes = TRUE, initialTypeInference = initialTypeInference, . control = control) 4. RCfunInfos[[className]]$RCfunProc$process(debug = control$debug, . debugCpp = control$debugCpp, initialTypeInferenceOnly = FALSE,
...
7. eval(call(sizeCall, code, symTab, typeEnv)) 8. eval(call(sizeCall, code, symTab, typeEnv)) 9. sizeReturn(<environment>, new("symbolTable", .xData = <environment>), . <environment>) 10. stop(exprClassProcessingErrorMsg(code, failMsg), call. = FALSE)

Perry de Valpine

unread,
Jan 3, 2024, 11:28:02 AM1/3/24
to Athul Sudheesh, nimble-users
Hi Athul,

Thanks for the questions. I will respond to this one here and then go to the more recent question you posted.

I think there are a couple of things going on for you. The first is that while the "matrix" (and "numeric", "integer", and "logical") function in nimble is similar to that in R, it is not identical. If you do "help(matrix)" and then select the nimble option, you will see "nimMatrix" (synonym for "matrix" in nimbleFunction code) documented. That shows that there is an explicit type argument.

The following will do what I think you want:

convertToLogical <- nimbleFunction(
run = function(Q = double(2)){
J = dim(Q)[1]
K = dim(Q)[2]
ans = matrix(type="logical",nrow=J,ncol=K)

for (j in 1:J){
for (k in 1:K){
if (Q[j,k] == 1){
ans[j,k] = TRUE
}
else{
ans[j,k] = FALSE
}
}
}
return(ans)
returnType(logical(2))
})

CconvertToLogical <- compileNimble(convertToLogical)

You can also do it like this:

convertToLogical <- nimbleFunction(
run = function(Q = double(2)){
ans <- Q==1 # ans will automatically be a logical matrix
return(ans)
returnType(logical(2))
})

CconvertToLogical <- compileNimble(convertToLogical)

On your later thread, it looks like you are calling this from a model. Variables in models are always numeric (double). The above convertToLogical would work if called from another nimbleFunction. However, in a model, the returned object would be converted back to numeric (or might simply error out), resulting in 0s and 1s. I will say more in response to that thread in a few minutes.

In case it is helpful, there is more information about these topics in section 11.2.2.1 of our User Manual at r-nimble.org.

HTH
Perry

--
You received this message because you are subscribed to the Google Groups "nimble-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nimble-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nimble-users/a02ed16c-12f5-4251-a774-393c566538ccn%40googlegroups.com.

Athul Sudheesh

unread,
Jan 3, 2024, 12:57:03 PM1/3/24
to nimble-users
Hello Dr. Valpine, 

I can actually do the conversion outside the nimble model and pass it as data or constant. If I pass the logical-Q Matrix as  data, will that still be converted to numeric inside the nimble model?

If I want to pass the logical-Q as a constant, how will I do that? I tried passing it the matrix the same way scalar/vector constants are passed and it didn't work. 

Thanks in advance :) 

Perry de Valpine

unread,
Jan 3, 2024, 1:11:58 PM1/3/24
to Athul Sudheesh, nimble-users
I think these questions are related to the other thread, about use in models.

On Wed, Jan 3, 2024 at 9:57 AM Athul Sudheesh <athul...@gmail.com> wrote:
Hello Dr. Valpine, 

I can actually do the conversion outside the nimble model and pass it as data or constant. If I pass the logical-Q Matrix as  data, will that still be converted to numeric inside the nimble model?

Yes.
 

If I want to pass the logical-Q as a constant, how will I do that? I tried passing it the matrix the same way scalar/vector constants are passed and it didn't work. 

I'm not sure, so I'd have to see a reproducible example. But in this case it should be fine to pass it as data. You'd want to pass it as constants if using it for specific indexing.

Perry

 

Athul Sudheesh

unread,
Feb 8, 2024, 6:55:11 PM2/8/24
to nimble-users
Hello Dr. Valpine, 

This is an example code of what I want to accomplish. The code will fail because of the "which" function inside nimble code. The below code is similar to a multidimensional item response theory model but with the latent skills being discrete variable.

library
(CDM)
library(nimble)
data( data.fraction1 )
X = data.fraction1$data
Q = data.fraction1$q.matrix



fullModel <- nimbleCode({
#coeff. for the skills
for(k in 1:K){
m_A[k] ~ dbeta(1,1)
}


# Prior for item difficulty
for(j in 1:J){
D[j] ~ dbeta(1,1)
}

for (i in 1:N){


for(k in 1:K){
lambda[i,k] ~ dbeta(1,1)
skills[i,k] ~ dbern(lambda[i,k])
}

}


for(i in 1:N){
for(j in 1:J){

# Modeling item response probability ========
# Logic to compute effective ability ========
q_j[1:K] <- which(Q[j,1:K] == 1)
eff_ability[i,j] <- pmin(m_A[k]*skills[i,q_j])
# =========================================
theta[i,j] <- eff_ability[i,j] - D[j]
pi[i,j] <- ilogit(1.7*theta[i,j])

# Item-Response Likelihood
X[i,j] ~ dbern(pi[i,j])
# ===============================================
}
}
})

modelConstants <- list(
N = dim(X)[1],
J = dim(X)[2],
K = dim(Q)[2]
)
data <- list(
X = X,
Q = Q
)
initial_values <- NULL

M0 <- nimbleModel(
code = fullModel,
data = data,
inits = initial_values,
constants = modelConstants,
name = "baseline model"
)


dataNodes <- M0$getNodeNames(dataOnly = TRUE)
parentNodes <- M0$getParents(dataNodes, stochOnly = TRUE)
simNodes <- M0$getDependencies(parentNodes, self = FALSE)

n.iter <- 15000
n.burnun <- 2000
n.chain <- 3
params.to.save <- parentNodes

mcmc_fit<- nimbleMCMC( model = cmodel,
monitors = params.to.save,
niter = n.iter,
nburnin = n.burnun,
nchains = n.chain,
samplesAsCodaMCMC=TRUE, WAIC=TRUE)


I am also attaching the code as an attachment.

cdm.r

Perry de Valpine

unread,
Feb 8, 2024, 7:03:41 PM2/8/24
to Athul Sudheesh, nimble-users
Dear Athul,
Thanks for posting.
I haven't run your code, but I will give a quick suggestion.
Since Q is data, can you simply make the index calculations you need outside the model and provide them as constants or data? A logical vector will not work for indexing in a model. All variables in a model are represented as doubles (numeric), even if they will only hold integers. 
I am not easily seeing what you want to do. I think pmin would return a vector, but you are assigning it to a scalar. If you are still stuck and want to say what eff_ability[i,j] should be, someone can suggest how to accomplish it.
HTH
Perry


Athul Sudheesh

unread,
Feb 8, 2024, 7:29:37 PM2/8/24
to nimble-users
Thank you for the quick reply Dr. Valpine. 

I will try that also. Yes..I think I should be having the min function there instead of the pmin. 

eff_ability[i,j] is a value that should be in the range of 0-1. m_A[k] is the coefficient for the k-th skill.
Suppose we have an assessment with J items and they assess K skills, then the J x K Q-Matrix is a binary indicator matrix that specify which items assess which skills. 

Athul Sudheesh

unread,
Feb 8, 2024, 8:24:28 PM2/8/24
to nimble-users
I tried to do the index calculation outside the model with the following logic:

J = dim(X)[2]
Jindices = rep(NA,J)
for(j in 1:J){
Jindices[j] = list(which(Q[j,]==1))
}


and send it as data/constant to the model. But I was getting the following error: 
Error in nimbleModel(code = fullModel, data = data, inits = initial_values,  :
  BUGSmodel: elements of 'data' must be numeric



Chris Paciorek

unread,
Feb 13, 2024, 11:48:57 AM2/13/24
to Athul Sudheesh, nimble-users
Hi Athul,

I haven't fully followed the thread here, but note that you can't provide a list as one of the elements of the `data` argument. `data` itself is a list, but each of the elements of the list should be vectors (potentially of length 1), matrices, or arrays. So if in your email you meant that you pass `Jindices` as an element of `data`  that won't work.

-chris

Athul Sudheesh

unread,
Feb 13, 2024, 2:18:00 PM2/13/24
to nimble-users
Hello Dr. Paciorek, 

Thank you for your reply. I realized that after some of my email threads with Dr. Valpine. 
He suggested a solution to my problem and it worked!

These discussions happened in a different thread: 
Reply all
Reply to author
Forward
0 new messages