From 48665653ce235b58f45ae108efea95345147650d Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 29 Jun 2026 04:10:28 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Remove=20redundant=20matrix?= =?UTF-8?q?=20inversions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated `calcAB` to return `Ainv` (the asymptotic covariance matrix directly, without inversion) and updated the `B` matrix construction. - Updated `calcLambda` to use `Ainv` directly, removing `chol2inv(chol(AB1$A))` and `chol2inv(chol(AB2$A))` and avoiding redundant matrix inversions. --- .jules/bolt.md | 5 +++++ R/vuongtest.R | 21 ++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..e53b99d --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,5 @@ +## 2024-05-24 - Unnecessary Matrix Inversions + +**Learning:** Mathematical operations that effectively cancel each other out (like computing the inverse of an inverse matrix) can significantly degrade performance in statistical libraries due to $O(K^3)$ matrix inversion complexity. + +**Action:** Whenever identifying an inversion function (`chol2inv`, `solve`, etc.) being passed between functions, look for where the original matrix might already contain the solution, and pass the required state directly to avoid redundant inversions. diff --git a/R/vuongtest.R b/R/vuongtest.R index 57c5f6b..5edcca7 100644 --- a/R/vuongtest.R +++ b/R/vuongtest.R @@ -231,7 +231,12 @@ calcAB <- function(object, n, scfun, vc){ ## in case mirt vcov was not estimated if(nrow(tmpvc) == 1 & is.na(tmpvc[1,1])) stop("Please re-estimate the mirt model with SE=TRUE") } - A <- chol2inv(chol(tmpvc)) + + # Bolt: performance improvement - avoid unnecessary matrix inversion + # A <- chol2inv(chol(tmpvc)) + # A is the inverse of tmpvc, but later in calcLambda we compute chol2inv(chol(A)) + # By saving tmpvc as Ainv we can avoid computing inverse of an inverse in calcLambda + Ainv <- tmpvc ## Eq (2.2) if(!is.null(scfun)){ @@ -251,9 +256,9 @@ calcAB <- function(object, n, scfun, vc){ sc <- estfun(object) } sc.cp <- crossprod(sc)/n - B <- matrix(sc.cp, nrow(A), nrow(A)) + B <- matrix(sc.cp, nrow(Ainv), nrow(Ainv)) - list(A=A, B=B, sc=sc) + list(Ainv=Ainv, B=B, sc=sc) } ## a function to get the cross-product from Eq (2.7) @@ -271,10 +276,12 @@ calcLambda <- function(object1, object2, n, score1, score2, vc1, vc2) { AB2 <- calcAB(object2, n, score2, vc2) Bc <- calcBcross(AB1$sc, AB2$sc, n) - W <- cbind(rbind(-AB1$B %*% chol2inv(chol(AB1$A)), - t(Bc) %*% chol2inv(chol(AB1$A))), - rbind(-Bc %*% chol2inv(chol(AB2$A)), - AB2$B %*% chol2inv(chol(AB2$A)))) + # Bolt: performance improvement - use Ainv directly instead of chol2inv(chol(AB$A)) + # This eliminates matrix inversions and computing the inverse of an inverse. + W <- cbind(rbind(-AB1$B %*% AB1$Ainv, + t(Bc) %*% AB1$Ainv), + rbind(-Bc %*% AB2$Ainv, + AB2$B %*% AB2$Ainv)) lamstar <- eigen(W, only.values=TRUE)$values ## Discard imaginary part, as it only occurs for tiny eigenvalues?