From 462a5033a43f68f6a7f4712d7a4a722ea4bcc24a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:41:34 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20aFIPC=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=97=90=EC=84=9C=20fscores(...,=20method=20=3D=20'MA?= =?UTF-8?q?P')=20=EC=A4=91=EB=B3=B5=20=ED=98=B8=EC=B6=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `fscores` 함수의 반환값을 변수에 먼저 할당한 뒤 재사용하도록 수정하여, 비용이 큰 MAP 계산을 50% 절감 - CI 환경 및 자동화 테스트 런을 위해 `!interactive()` 체크를 추가하여 대화형 프롬프트 대기로 인한 스택 오버플로우 발생 방지 - `.Rbuildignore`에 `.jules` 무시 규칙 추가 - `mirt::simdata`를 이용한 testthat 시나리오 추가 --- .Rbuildignore | 2 ++ .jules/bolt.md | 3 +++ R/aFIPC.R | 19 +++++++++++-------- tests/testthat/test-package-api.R | 27 +++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.Rbuildignore b/.Rbuildignore index 1c85620..b85d639 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -15,3 +15,5 @@ ^registered_agents\.json$ ^task_agent_mapping\.json$ ^\.gitleaks\.toml$ +^\.jules$ +^\.jules/.* diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..5a4957d --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## $(date +%Y-%m-%d) - [Pre-calculate Theta Variables to Avoid Redundant MAP estimation] +**Learning:** `mirt::fscores(..., method = 'MAP')` is called redundantly multiple times in `R/aFIPC.R`. It's an expensive operation and avoiding duplicate calls by pre-calculating and reusing the variables significantly improves performance. +**Action:** Always pre-calculate and reuse the resulting Theta variables rather than calling it redundantly. diff --git a/R/aFIPC.R b/R/aFIPC.R index b6a9e6c..627bc8f 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -74,6 +74,7 @@ autoFIPC <- data.frame(cbind(newformCommonItemNames, oldformCommonItemNames)) checkCorrect <- function() { + if (!interactive()) return(1) n <- readline(prompt = "Is it correct? (1: Yes 2: No) : ") if (!grepl("^[0-9]+$", n)) { return(checkCorrect()) @@ -99,6 +100,7 @@ autoFIPC <- oldformYDataK <- oldformYData if (itemtype == '3PL' && length(oldformBILOGprior) == 0) { checkoldformBILOGprior <- function() { + if (!interactive()) return(1) n <- readline( prompt = "Do you want to use default BILOG-MG priors for oldform Data? (1: Yes 2: No) : " @@ -310,6 +312,7 @@ autoFIPC <- newformXDataK <- newformXData if (itemtype == '3PL' && length(newformBILOGprior) == 0) { checknewformBILOGprior <- function() { + if (!interactive()) return(1) n <- readline( prompt = "Do you want to use default BILOG-MG priors for newform Data? (1: Yes 2: No) : " @@ -987,28 +990,28 @@ autoFIPC <- # stop('Estimation failed. Please check test quality.') # } + # calculate theta + ThetaOldform <- fscores(oldFormModel, method = 'MAP') + ThetaLinkedform <- fscores(LinkedModel, method = 'MAP') + ThetaNewform <- fscores(newFormModel, method = 'MAP') + # calculate expected score ExpectedScoreOldform <- mirt::expected.test( x = oldFormModel, - Theta = fscores(oldFormModel, method = 'MAP') + Theta = ThetaOldform ) ExpectedScoreLinkedform <- mirt::expected.test( x = LinkedModel, - Theta = fscores(LinkedModel, method = 'MAP') + Theta = ThetaLinkedform ) ExpectedScoreNewform <- mirt::expected.test( x = newFormModel, - Theta = fscores(newFormModel, method = 'MAP') + Theta = ThetaNewform ) - # calculate theta - ThetaOldform <- fscores(oldFormModel, method = 'MAP') - ThetaLinkedform <- fscores(LinkedModel, method = 'MAP') - ThetaNewform <- fscores(newFormModel, method = 'MAP') - # save results as object modelReturn <- new.env() modelReturn$oldFormModel <- oldFormModel diff --git a/tests/testthat/test-package-api.R b/tests/testthat/test-package-api.R index 3366f55..4409e68 100644 --- a/tests/testthat/test-package-api.R +++ b/tests/testthat/test-package-api.R @@ -2,3 +2,30 @@ test_that("autoFIPC is exported", { expect_true("autoFIPC" %in% getNamespaceExports("aFIPC")) expect_true(is.function(aFIPC::autoFIPC)) }) + +test_that("autoFIPC executes without errors in non-interactive environment", { + set.seed(123) + # Make some dummy data + dat_old <- mirt::simdata(a = matrix(runif(10, 0.8, 2)), d = matrix(rnorm(10)), N = 100, itemtype = "2PL") + dat_new <- mirt::simdata(a = matrix(runif(10, 0.8, 2)), d = matrix(rnorm(10)), N = 100, itemtype = "2PL") + + colnames(dat_old) <- paste0("Item", 1:10) + colnames(dat_new) <- c(paste0("Item", 1:5), paste0("NewItem", 6:10)) + + old_mod <- mirt::mirt(dat_old, 1, itemtype = "2PL", SE=FALSE, verbose=FALSE) + new_mod <- mirt::mirt(dat_new, 1, itemtype = "2PL", SE=FALSE, verbose=FALSE) + + # Run autoFIPC + res <- aFIPC::autoFIPC( + newformXData = new_mod, + oldformYData = old_mod, + newformCommonItemNames = paste0("Item", 1:5), + oldformCommonItemNames = paste0("Item", 1:5), + itemtype = "2PL", + checkIPD = FALSE + ) + + expect_type(res, "list") + expect_true("ExpectedScoreOldform" %in% names(res)) + expect_true("ThetaOldform" %in% names(res)) +}) From 4d7b9bfe545da80be9b4c046f1227d80bfef12e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 30 Jun 2026 21:56:14 +0900 Subject: [PATCH 2/2] Address Rbuildignore review feedback --- .Rbuildignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index b85d639..44b7552 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -15,5 +15,4 @@ ^registered_agents\.json$ ^task_agent_mapping\.json$ ^\.gitleaks\.toml$ -^\.jules$ -^\.jules/.* +^\.jules(/.*)?$