Skip to content
Open
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
^\.jules(/.*)?$
^\.trivyignore\.yaml$
^trivy\.yaml$
^\.semgrepignore$
5 changes: 3 additions & 2 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ jobs:
ACTIONLINT_VERSION="1.7.10"
ACTIONLINT_FILE="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
ACTIONLINT_BASE_URL="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}"
curl -sSLo "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}"
curl -sSLo actionlint_checksums.txt "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt"
curl --retry 5 --retry-connrefused -sL -o "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}"
curl --retry 5 -sL -o actionlint_checksums.txt \
"${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt"
grep "$ACTIONLINT_FILE" actionlint_checksums.txt | sha256sum -c -
tar -xzf "$ACTIONLINT_FILE" actionlint
chmod +x actionlint
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ jobs:
- name: Install gitleaks
run: |
GITLEAKS_FILE="gitleaks_8.24.2_linux_x64.tar.gz"
curl -sSLo "$GITLEAKS_FILE" "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/$GITLEAKS_FILE"
curl -sSLo gitleaks_checksums.txt "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/gitleaks_8.24.2_checksums.txt"
curl --retry 5 --retry-connrefused -sL -o "$GITLEAKS_FILE" \
"https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/$GITLEAKS_FILE"
curl --retry 5 -sL -o gitleaks_checksums.txt \
"https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/gitleaks_8.24.2_checksums.txt"
grep "$GITLEAKS_FILE" gitleaks_checksums.txt | sha256sum -c -
tar -xzf "$GITLEAKS_FILE" gitleaks
chmod +x gitleaks
Expand All @@ -40,8 +42,9 @@ jobs:
ACTIONLINT_VERSION="1.7.10"
ACTIONLINT_FILE="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
ACTIONLINT_BASE_URL="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}"
curl -sSLo "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}"
curl -sSLo actionlint_checksums.txt "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt"
curl --retry 5 --retry-connrefused -sL -o "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}"
curl --retry 5 -sL -o actionlint_checksums.txt \
"${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt"
grep "$ACTIONLINT_FILE" actionlint_checksums.txt | sha256sum -c -
tar -xzf "$ACTIONLINT_FILE" actionlint
chmod +x actionlint
Expand Down
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
## 2025-02-12 - R 언어에서 반복적인 mirt 모델 생성 시 불필요한 데이터프레임 부분집합 추출 최적화
**Learning:** R에서 데이터프레임의 특정 열을 추출하는 작업(`df[cols]`)은 O(N)의 메모리 복사를 수반합니다. `autoFIPC`에서 `mirt` 모델의 파라미터를 설정하거나 호출하는 과정 중에 `newformXDataK[colnames(newFormModel@Data$data)]` 코드가 반복해서 사용되었고, 심지어 `ncol()`을 위해 단순히 개수를 구할 때도 사용되어 불필요한 메모리 할당과 오버헤드를 초래했습니다.
**Action:** 조건문이나 반복문 내부에서 불필요하게 데이터프레임 부분집합 연산이 반복되지 않도록 외부에서 한 번만 `linkedFormData <- newformXDataK[colnames(newFormModel@Data$data)]`로 캐싱(caching)한 뒤, `ncol(linkedFormData)`와 `data = linkedFormData` 형태로 재사용하여 메모리 복사와 O(N) 오버헤드를 방지해야 합니다.
## 2024-07-20 - R 언어에서 데이터 프레임 값 할당 시 2D 서브셋팅 오버헤드 최적화
**Learning:** R에서 데이터 프레임의 값을 수정할 때 `df[idx, "col"] <- val`와 같은 2D 서브셋팅 방식을 사용하면, `[<-.data.frame` 메소드 디스패치와 차원/팩터 레벨 검사 등 부가적인 오버헤드가 발생하여 성능이 크게 저하될 수 있습니다. (특히 반복문 내에서 심각함)
**Action:** `df$col[idx] <- val`와 같이 컬럼 벡터를 직접 참조하여 값을 할당하는 1D 백터 할당 방식을 사용하면, 내부적으로 리스트 접근(O(1))과 C-레벨 벡터 수정으로 처리되므로 속도가 훨씬 빨라집니다.
44 changes: 22 additions & 22 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -598,15 +598,15 @@ autoFIPC <-
# Preserve mirt's structural estimability flags. Forcing every row TRUE
# frees boundary parameters such as 2PL g/u and makes the Hessian unstable.

NewScaleParms[NewScaleParms$item == 'GROUP', "est"] <- FALSE
OldScaleParms[OldScaleParms$item == 'GROUP', "est"] <- FALSE
NewScaleParms$est[NewScaleParms$item == 'GROUP'] <- FALSE
OldScaleParms$est[OldScaleParms$item == 'GROUP'] <- FALSE

NewScaleParms[NewScaleParms$name == "COV_11", "est"] <- TRUE
OldScaleParms[OldScaleParms$name == "COV_11", "est"] <- TRUE
NewScaleParms$est[NewScaleParms$name == "COV_11"] <- TRUE
OldScaleParms$est[OldScaleParms$name == "COV_11"] <- TRUE

if (itemtype == 'Rasch') {
NewScaleParms[NewScaleParms$name == "a1", "est"] <- FALSE
OldScaleParms[OldScaleParms$name == "a1", "est"] <- FALSE
NewScaleParms$est[NewScaleParms$name == "a1"] <- FALSE
OldScaleParms$est[OldScaleParms$name == "a1"] <- FALSE
}

#IPD
Expand Down Expand Up @@ -786,14 +786,14 @@ autoFIPC <-
oldIdx <- oldScaleParmsItemIdxCache[[oldFormItemStr]]

# ⚡ Bolt: Remove unnecessary paste0() array string generation overhead
message(' Newform Parms: ', paste(NewScaleParms[newIdx, "value"], collapse = ' '))
message(' Oldform Parms: ', paste(OldScaleParms[oldIdx, "value"], collapse = ' '))
message(' Newform Parms: ', paste(NewScaleParms$value[newIdx], collapse = ' '))
message(' Oldform Parms: ', paste(OldScaleParms$value[oldIdx], collapse = ' '))

NewScaleParms[newIdx, "value"] <-
OldScaleParms[oldIdx, "value"]
message(' Linkedform Parms: ', paste(NewScaleParms[newIdx, "value"], collapse = ' '), '\n')
NewScaleParms$value[newIdx] <-
OldScaleParms$value[oldIdx]
message(' Linkedform Parms: ', paste(NewScaleParms$value[newIdx], collapse = ' '), '\n')

NewScaleParms[newIdx, "est"] <-
NewScaleParms$est[newIdx] <-
FALSE
} else {
message(
Expand All @@ -813,17 +813,17 @@ autoFIPC <-
newBetaIdx <- NewScaleParms$item == 'BETA'
oldBetaIdx <- OldScaleParms$item == 'BETA'

NewScaleParms[newBetaIdx, "value"] <-
OldScaleParms[oldBetaIdx, "value"]
NewScaleParms[newBetaIdx, "est"] <-
NewScaleParms$value[newBetaIdx] <-
OldScaleParms$value[oldBetaIdx]
NewScaleParms$est[newBetaIdx] <-
FALSE

message('applying BETA parameter as linking')

message(
' Linkedform Parms: ',
paste0(
NewScaleParms[newBetaIdx, "value"],
NewScaleParms$value[newBetaIdx],
' '
),
'\n'
Expand Down Expand Up @@ -858,13 +858,13 @@ autoFIPC <-
new_mean11_idx <- NewScaleParms$name == "MEAN_11"
old_mean11_idx <- OldScaleParms$name == "MEAN_11"

NewScaleParms[new_cov11_idx, "est"] <- FALSE
OldScaleParms[old_cov11_idx, "est"] <- FALSE
NewScaleParms[new_mean11_idx, "est"] <- FALSE
OldScaleParms[old_mean11_idx, "est"] <- FALSE
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean11_idx] <- FALSE
OldScaleParms$est[old_mean11_idx] <- FALSE

NewScaleParms[new_cov11_idx, "value"] <- 1
OldScaleParms[old_mean11_idx, "value"] <- 0
NewScaleParms$value[new_cov11_idx] <- 1
OldScaleParms$value[old_mean11_idx] <- 0
Comment on lines +861 to +867

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

forceNormalZeroOne의 평균값 할당 대상을 수정해야 합니다.

Line 867은 OldScaleParmsMEAN_11만 0으로 설정합니다. 그러나 이후 연결 모델에는 NewScaleParmspars로 전달되므로, NewScaleParms$MEAN_11은 기존 값으로 남아 평균이 0으로 고정되지 않습니다.

       NewScaleParms$value[new_cov11_idx] <- 1
-      OldScaleParms$value[old_mean11_idx] <- 0
+      NewScaleParms$value[new_mean11_idx] <- 0

두 파라미터 테이블 모두를 정규화하려는 의도라면 OldScaleParms$value[old_mean11_idx] <- 0도 별도로 유지해야 합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean11_idx] <- FALSE
OldScaleParms$est[old_mean11_idx] <- FALSE
NewScaleParms[new_cov11_idx, "value"] <- 1
OldScaleParms[old_mean11_idx, "value"] <- 0
NewScaleParms$value[new_cov11_idx] <- 1
OldScaleParms$value[old_mean11_idx] <- 0
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean11_idx] <- FALSE
OldScaleParms$est[old_mean11_idx] <- FALSE
NewScaleParms$value[new_cov11_idx] <- 1
NewScaleParms$value[new_mean11_idx] <- 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@R/aFIPC.R` around lines 861 - 867, forceNormalZeroOne의 파라미터 초기화에서
NewScaleParms의 MEAN_11 평균값 할당이 누락되었습니다. NewScaleParms$est 설정 인근의 값 초기화 로직에
NewScaleParms의 new_mean11_idx를 0으로 설정하는 처리를 추가하고,
OldScaleParms$value[old_mean11_idx] <- 0 할당은 두 테이블을 모두 정규화하도록 유지하세요.

}
if (freeMEAN == T) {
LinkedModelSyntax <-
Expand Down
Loading