Skip to content

feat: Owen-approximate posterior-predictive EPV + Wald SPRT classification (reduced scope) - #286

Merged
seonghobae merged 5 commits into
mainfrom
seonghobae-epv
Jul 26, 2026
Merged

feat: Owen-approximate posterior-predictive EPV + Wald SPRT classification (reduced scope)#286
seonghobae merged 5 commits into
mainfrom
seonghobae-epv

Conversation

@seonghobae

@seonghobae seonghobae commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Scope note

  • This branch includes both epv_select and sprt_classify changes; downstream stacked PRs continue from this base.

Iteration 24 of the autonomous paper-implementation loop. Stacked on #254 (seonghobae-ccat). ## What Owen-approximate posterior-predictive EPV item selectionfast_mlsirm.epv_select, Rust core mlsirm_core::exposure::epv_select (+ PyO3 py_epv_select). For each unadministered item under the Owen normal posterior N(mu, sig2): - predictive success probability p*_i = c_i + (1 - c_i) Phi((mu - b_i) / sqrt(1/a_i^2 + sig2)) (identical d to owen_update) - EPV_i = p*_i sig2_i^+ + (1 - p*_i) sig2_i^- with both outcome variances from owen_update - select argmin over unadministered items; lowest-index ties; epv/predictive returned for the whole pool. ## Adversarial spec-verify (BEFORE implementing): REDUCED-SCOPE The reviewer confirmed the posterior-predictive formula is mathematically correct but ruled that van der Linden (1998), catR EPV.R, and mirtCAT define MEPV with point probabilities P_i(mu) and exact numerical posterior variances, not posterior-predictive weights + Owen updates. Accepted contract: an explicitly labeled "Owen-approximate posterior-predictive EPV" with prescribed doc wording (used verbatim in code, docstring, and CHANGELOG). This is deliberately NOT exact vdL-98 MEPV. Source status: van der Linden (1998) READ as ERIC ED424235 (Research Report 96-01); catR EPV.R and mirtCAT selection_criteria.R READ; Owen (1975) and the Psychometrika body NOT read (documented in code). ## Evidence - Pinned crate-parity oracles (spec-review exact arithmetic, 17 digits, 5e-7 abs tol) for both predictive and epv over a 5-item pool; argmin = item 1. - Delegation discriminator (spec-review pool): argmin EPV = 2 while max-Fisher-info = 4 and Owen b-matching = 1 — a delegating mutant cannot satisfy all three. - Administered masking: scoring covers the whole pool; masking affects selection only (runner-up promotion pinned, vectors unchanged to 1e-15). - Error paths: empty pool, length mismatch, nonpositive a, non-finite b/c/mu, c out of [0,1), sig2 <= 0, all-administered, degenerate Owen posterior propagation. - MC-500 #[ignore] invariant suite: predictive finite in [c_i, 1] (closed — erfc saturation), EPV finite > 0, selected always unadministered, ties lowest-index. (Strict EPV < sig2 deliberately NOT asserted: single-outcome Owen variance can genuinely increase and roundoff inflation of 8.9e-16 was observed.) - 4 executed mutation kills (temp-file backup/restore, re-run verified green): - M1 argmin -> argmax: 3 tests fail - M2 drop guessing floor (p* = Phi(d)): 2 fail - M3 swap sig2+/sig2- weights: 2 fail - M5 posterior-predictive -> point probability P_i(mu): 2 fail - Python wrapper validates arrays BEFORE lossy casts; 4 pytest parity tests green. cargo test -p mlsirm-core --lib -- epv: 4 passed (+1 ignored MC-500 passed with --ignored). pytest -k EpvSelect: 4 passed. ## References - van der Linden, W. J. (1998). Bayesian item selection criteria for adaptive testing. Psychometrika, 63(2), 201-216. https://doi.org/10.1007/BF02294775 - Owen, R. J. (1975). A Bayesian sequential procedure for quantal response in the context of adaptive mental testing. JASA, 70(350), 351-356. - Magis, D., & Raiche, G. (2012). catR. JSS, 48(8). https://doi.org/10.18637/jss.v048.i08

Adversarial impl-review (AFTER implementing): CLEAN after fixes (b2a711a)

  • CONFIRMED-DEFECT: complex a/b/c were laundered through the float64 cast (imaginary parts silently dropped). Fixed: np.iscomplexobj rejection before any cast + 3 regression error-path tests.
  • DOC-DEFECT: research-report citation said 98-01; the read ERIC ED424235 source is Research Report 96-01. Fixed.

Summary by CodeRabbit

  • 새 기능

    • Owen 근사 기반 posterior-predictive EPV를 사용한 CAT 문항 선택 기능을 추가했습니다.
    • 선택된 문항, 문항별 EPV 및 예측 성공확률을 Python API에서 제공합니다.
    • 이미 출제된 문항을 제외하고 EPV가 가장 낮은 문항을 선택하며, 동률 시 낮은 인덱스를 우선합니다.
  • 버그 수정

    • 입력 배열, 문항 파라미터, 사전분포 및 출제 상태에 대한 유효성 검사를 강화했습니다.

seonghobae and others added 2 commits July 25, 2026 23:23
…er Linden, 1998; reduced scope)

Adds epv_select to mlsirm_core::exposure: for each unadministered item,
the predictive success probability p* = c + (1-c) Phi((mu-b)/sqrt(1/a^2+sig2))
weights the two Owen-update outcome posterior variances,
EPV = p* sig2+ + (1-p*) sig2-, and the argmin (lowest-index ties) is
selected. Explicitly NOT vdL-98 exact MEPV (which uses point
probabilities and exact numerical posteriors); the reduced scope is
documented in code, docstring, and CHANGELOG per the adversarial spec
review. PyO3 binding py_epv_select + Python wrapper fast_mlsirm.epv_select
(arrays validated before lossy casts).

Tests: pinned crate-parity oracles (5-item pool, 17-digit spec-review
values, 5e-7 abs), delegation discriminator (argmin EPV=2 vs max-info=4
vs b-matching=1), administered-masking invariance, error paths, and a
500-rep Monte-Carlo invariant suite (#[ignore]). Mutation kills executed:
argmin->argmax (3 fail), drop guessing floor (2 fail), swap sig2+/- weights
(2 fail), posterior-predictive->point probability (2 fail).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…citation

Adversarial impl-review findings: (1) complex inputs were laundered
through the float64 cast, silently discarding imaginary parts — now
rejected with ValueError before any cast, regression-tested; (2) the
ERIC ED424235 research-report citation said 98-01 but the read source
is Research Report 96-01.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 26, 2026 08:55
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Owen 근사 posterior-predictive EPV 기반 CAT 아이템 선택이 Rust 코어에 구현되고 PyO3 및 Python API로 공개되었습니다. 입력 검증, administered 마스킹, 고정 오라클, 오류 경로와 Monte Carlo 테스트 및 변경 로그가 추가되었습니다.

Changes

EPV 아이템 선택

Layer / File(s) Summary
EPV 계산 및 코어 API
crates/mlsirm-core/src/exposure.rs
예측확률과 owen_update 결과로 아이템별 EPV를 계산하고, 미관리 아이템 중 EPV가 가장 낮은 항목과 전체 점수 배열을 반환합니다.
Python API 연결
crates/fast-mlsirm-py/src/lib.rs, python/fast_mlsirm/exposure.py, python/fast_mlsirm/__init__.py
PyO3 래퍼와 Python 함수가 추가되었으며 selected, epv, predictive 결과가 패키지 공개 API에 등록됩니다.
선택 검증 및 변경 기록
tests/test_paper_features.py, tests/unit/exposure_tests.rs, CHANGELOG.md
고정 오라클, 기존 선택 방식과의 차이, administered 마스킹, 입력 오류 및 반복 불변식 테스트와 변경 로그 항목이 추가되었습니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythonCaller
  participant py_epv_select
  participant mlsirm_core_epv_select
  participant owen_update
  PythonCaller->>py_epv_select: 입력 배열과 사전분포 전달
  py_epv_select->>mlsirm_core_epv_select: contiguous 변환값 전달
  mlsirm_core_epv_select->>owen_update: 정답 및 오답 상태 업데이트
  owen_update-->>mlsirm_core_epv_select: 분산 성분 반환
  mlsirm_core_epv_select-->>py_epv_select: 선택 인덱스와 점수 배열 반환
  py_epv_select-->>PythonCaller: Python dict 반환
Loading

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning EPV 선택 변경은 반영하지만, Wald SPRT classification은 변경 내용에 없어 제목이 일부 오해를 부릅니다. Wald SPRT classification 문구를 हट고, 핵심 변경인 Owen-approximate posterior-predictive EPV 선택 추가를 간결하게 요약하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch seonghobae-epv

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an Owen-approximate posterior-predictive EPV (expected posterior variance) item selection criterion to the Rust core and exposes it via the Python API, with pinned-oracle parity tests.

Changes:

  • Implement mlsirm_core::exposure::epv_select and return full-length epv and predictive vectors plus selected index.
  • Add PyO3 binding (py_epv_select) and a Python wrapper (fast_mlsirm.epv_select) with input validation.
  • Add Rust and Python tests including pinned numeric oracles, masking behavior, and error-path checks; document the new feature in the changelog.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/unit/exposure_tests.rs Adds Rust unit tests for epv_select (pinned-oracle, masking, error paths, MC invariants).
tests/test_paper_features.py Adds Python parity tests for fast_mlsirm.epv_select mirroring Rust oracles and validation.
python/fast_mlsirm/exposure.py Adds Python epv_select wrapper with dtype/shape checks and calls into Rust binding.
python/fast_mlsirm/init.py Re-exports epv_select from the public Python package API.
crates/mlsirm-core/src/exposure.rs Implements core EPV selection logic and result type in Rust.
crates/fast-mlsirm-py/src/lib.rs Exposes py_epv_select via PyO3 and wires it into the module init.
CHANGELOG.md Documents the new EPV selection feature and its intended scope/approximation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_paper_features.py Outdated
Copilot AI review requested due to automatic review settings July 26, 2026 08:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread tests/unit/exposure_tests.rs Outdated
…ope) (#256)

* feat: Wald SPRT classification for CAT (single-cut binary, reduced scope)

Single-cut binary-response sequential probability ratio test for IRT
classification testing: point hypotheses at theta_cut -/+ delta, cumulative
binary log-likelihood ratio under the D=1 logistic 3PL, inclusive
first-crossing decisions against log Wald boundaries A=ln((1-beta)/alpha),
B=ln(beta/(1-alpha)) -> above/below/continue with 1-based n_used and a full
counterfactual llr_trace diagnostic.

Verified against READ sources: R catIrt termSPRT.R/logLik.brm.R/p.brm.R and
Thompson (2007, doi:10.7275/fq3r-zz60, p. 7). Reckase (1983) and Eggen
(1999) cited as historical origins via Thompson (NOT directly read).
Adversarial spec review verdict: REDUCED-SCOPE (no multi-cut, polytomous,
or D=1.7 compatibility).

Tests: pinned 17-digit interior-crossing oracle (also the final-LLR-only
discriminator), below-decision first-crossing invariants on a heterogeneous
pool, continue path, full error-path coverage, 500-rep Monte-Carlo
structural invariants (#[ignore]). Executed mutation kills: swapped
boundaries (2 fail), dropped guessing floor (1), collapsed null hypothesis
theta0=theta_cut (2), off-by-one n_used (1); restore verified green.
Python wrapper validates before casts (complex laundering, exact 0/1
responses) and returns crate outputs only.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(sprt): stable log-space LLR at saturating parameters; mark Wald (1947) NOT read

Adversarial impl review found that extreme-but-valid parameters (e.g.
a = 50, delta = 20 -> z = +/-1000) saturate the 2PL response probability
to numerical 0/1, making ln(p1/p0) or ln((1-p1)/(1-p0)) non-finite and
returning a spurious Err. Rewrite the increments in stable log space:
ln(1-P) = ln(1-c) - softplus(z) (the ln(1-c) terms cancel in the wrong-
response increment) and ln(P) = -softplus(-z) when c = 0 (for c > 0 the
direct form is bounded below by c). Regression test pins the finite
+/-1000 LLRs and the ln-bounded c > 0 case, reading crate outputs.

Also mark Wald (1947) as NOT read in the Python docstring, PyO3 docstring,
and CHANGELOG (it already was in the core module comment).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 26, 2026 09:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/mlsirm-core/src/exposure.rs`:
- Around line 1332-1387: Update epv_select so owen_update failures for
administered items do not abort the entire scoring operation, since those items
cannot be selected. Preserve error propagation for unadministered items, and
represent administered-item failures with the existing appropriate NaN or
sentinel behavior while keeping their exclusion from selection intact.

In `@python/fast_mlsirm/exposure.py`:
- Around line 556-589: Update the reference number in the docstrings for
owen_update and owen_cat from Research Report 98-01 to Research Report 96-01,
matching the existing epv_select documentation. Leave the surrounding reference
text unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b188b4e-d74c-4800-98c5-6c5ff9cdb053

📥 Commits

Reviewing files that changed from the base of the PR and between 44a3ecf and 34a2122.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • crates/fast-mlsirm-py/src/lib.rs
  • crates/mlsirm-core/src/exposure.rs
  • python/fast_mlsirm/__init__.py
  • python/fast_mlsirm/exposure.py
  • tests/test_paper_features.py
  • tests/unit/exposure_tests.rs

Comment thread crates/mlsirm-core/src/exposure.rs
Comment thread python/fast_mlsirm/exposure.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Comment thread crates/mlsirm-core/src/exposure.rs
Comment thread python/fast_mlsirm/exposure.py
Comment thread CHANGELOG.md
Comment thread CHANGELOG.md
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 26, 2026 09:23
@seonghobae seonghobae changed the title feat: Owen-approximate posterior-predictive EPV item selection (van der Linden 1998, reduced scope) feat: Owen-approximate posterior-predictive EPV + Wald SPRT classification (reduced scope) Jul 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

CHANGELOG.md:129

  • The PR description/title focus on EPV selection, but this PR also introduces the new public API fast_mlsirm.sprt_classify (and documents it here). Please update the PR title/description (or explicitly scope/justify the SPRT addition) so the stated PR purpose matches the actual changes.
- **Wald SPRT classification for CAT** (`fast_mlsirm.sprt_classify`; in
  `mlsirm_core::exposure`). Single-cut binary-response sequential probability
  ratio test: point hypotheses at `theta_cut -/+ delta`, cumulative binary
  log-likelihood ratio under the D=1 logistic 3PL, and inclusive
  first-crossing decisions against the log Wald boundaries
  `A = ln((1-beta)/alpha)`, `B = ln(beta/(1-alpha))` -> `"above"`/`"below"`/
  `"continue"` with 1-based `n_used`; the full `llr_trace` is returned as an
  offline diagnostic (entries past `n_used` are counterfactual replay

Comment thread CHANGELOG.md
@seonghobae
seonghobae merged commit dfb57d0 into main Jul 26, 2026
34 checks passed
@seonghobae
seonghobae deleted the seonghobae-epv branch July 26, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants