I have implemented a kind of NR person parameter estimation scheme and to validate it, I compared it to mirt:fscores. The functions agree for 1 dim 2PL, 1 dim GRM, but unfortunately, there is a slight discrepancy for a 2 dim GRM, and I seem to be able to obtain a slightly better likelihood (2nd decimal place). Below is a code example (excuse the messy code for the log-likelhood).
From what I read in the source code of fscores.internal, it could just be that nlm converges too early, but this is just a wild guess.
# log lik of a GRM in a slightly different parametrization as in mirt (the thresholds have a negative sign)
log_lik <- function(loadings, # D columns of loadings
thresh, # matrix of thresholds of same number of rows as loadings.
x, # response pattern, coded from 0, ..., K-1.
theta){
I <- nrow(loadings)
stopifnot(I == nrow(thresh))
D <- ncol(loadings)
thresh <- cbind(rep(-Inf,I), thresh, rep(Inf,I))
idx <- x +1 # easier for indexing
delta <- matrix(thresh[cbind(rep(1:I,2),c(idx,idx+1))],ncol = 2)
nu <- matrix(loadings %*% theta, nrow=I,ncol =2) - delta
u <- 1/(1+exp(-nu))
p <- u[,1]-u[,2]
sum(log(p))
}
# try two dim model (is this too complex a model for 2 dimensons?)
library(mirt)
pmod2 <- mirt(Science, 2)
loadings <- coef(pmod2,simplify = TRUE)$items[,1:2]
thresh <- -coef(pmod2,simplify = TRUE)$items[,3:5] # sign change needed, since mirt has intercept parametrization
X <- as.matrix(Science) - 1 # code from zero to K-1
scores <- fscores(pmod2,"ML",rotate = "none")
log_lik(loadings, thresh, X[2,], scores[2,])
log_lik(loadings, thresh, X[2,], c(0.03681344, 0.31167518)) # slightly better (values obtained with other method)
# optim's value is identical apart from the 8th decimal place
optim(c(0.03681344, 0.31167518),function(theta)log_lik(loadings,thresh,X[2,],theta), lower = c(0,0), upper = c(0.5,0.5), method = "L-BFGS-B")