feat(estimator): MMLE-EM estimator axis robust to missing data#98
Conversation
Penalized JMLE jointly estimates every persons ability, which is biased under
missing/sparse responses. Add a marginal-MLE (EM) path that integrates ability
over Gauss-Hermite quadrature so each person contributes only their observed
items - missing-at-random safe, no imputation.
- crates/mlsirm-core/src/mmle.rs: Rust MMLE-EM for unidimensional 2PL (embedded
21-node GH rule, observed-only E-step, per-item Newton M-step, EAP ability).
- config.py: FitConfig.estimator {jmle|mmle|em|bayes}, default jmle; validate().
- fit.py: dispatch estimator; _fit_mmle prefers Rust core, falls back to the
pure-numpy reference; em/bayes raise NotImplementedError.
- tests: Rust unit tests + tests/test_estimator_mmle.py (recovery, monotone
loglik, reserved-estimator and invalid-value guards).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2Mig78TxShv22uBcr7pCo
The coverage-evidence gate enforces 100% Rust line coverage (cargo llvm-cov --fail-under-lines 100). mmle.rs:181 — the det.abs() < 1e-12 break protecting the per-item Newton step from a singular Hessian — was never exercised, so the gate failed. Add a test that feeds a zero-information (fully unobserved) item column with the ridge disabled, making the Newton Hessian exactly singular and driving execution through the guard. Restores 100% line coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
|
Review follow-up pushed at Issue found while checking the PR scope: the MMLE implementation is documented as unidimensional 2PL, but Fix:
Local verification:
Merge order note: this remains behind lower-numbered PRs #18, #27, #47, and #95. |
OpenCode Review Overview
Pull request overviewOpenCode reviewed the current-head bounded evidence and found no blocking issues. FindingsNo blocking findings. SummaryApproval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Changed-File Evidence Mapflowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file (6 files)"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file (6 files)"]
R1 --> V1["required checks"]
Evidence --> S2["Test: test_estimator_mmle.py"]
S2 --> I2["regression suite"]
I2 --> R2["Review risk: Test: test_estimator_mmle.py"]
R2 --> V2["targeted test run"]
|
There was a problem hiding this comment.
Pull request overview
OpenCode reviewed the current-head bounded evidence and found no blocking issues.
Findings
No blocking findings.
Summary
Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including crates/mlsirm-core/src/lib.rs, crates/mlsirm-core/src/mmle.rs, python/fast_mlsirm/config.py, python/fast_mlsirm/estimators/init.py, python/fast_mlsirm/estimators/mmle.py, and 2 more.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects crates/mlsirm-core/src/lib.rs to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source checks are delegated to configured OpenCode web_search/Context7/DeepWiki sources when applicable; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: Playwright visual, DOM locator, ARIA snapshot, console, and responsive evidence were checked when a web UI surface was present; for non-web surfaces, API/CLI/log/docs/workflow interaction evidence was reviewed instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.
- Result: APPROVE
- Reason: The PR introduces a robust MMLE-EM estimator for handling missing data in unidimensional 2PL models, with thorough testing and documentation.
- Head SHA:
3dcc454395b1e98d9264e43c50c573c7507ec1e8 - Workflow run: 29013658756
- Workflow attempt: 2
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file (6 files)"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file (6 files)"]
R1 --> V1["required checks"]
Evidence --> S2["Test: test_estimator_mmle.py"]
S2 --> I2["regression suite"]
I2 --> R2["Review risk: Test: test_estimator_mmle.py"]
R2 --> V2["targeted test run"]
|
Current-head review refresh:
Remaining blocker is not a test/security failure: org rulesets require native PR approvals ( |
There was a problem hiding this comment.
Pull request overview
OpenCode reviewed the current-head bounded evidence and found no blocking issues.
Findings
No blocking findings.
Summary
Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including crates/mlsirm-core/src/lib.rs, crates/mlsirm-core/src/mmle.rs, python/fast_mlsirm/config.py, python/fast_mlsirm/estimators/init.py, python/fast_mlsirm/estimators/mmle.py, and 2 more.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects crates/mlsirm-core/src/lib.rs to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source checks are delegated to configured OpenCode web_search/Context7/DeepWiki sources when applicable; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: Playwright visual, DOM locator, ARIA snapshot, console, and responsive evidence were checked when a web UI surface was present; for non-web surfaces, API/CLI/log/docs/workflow interaction evidence was reviewed instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.
- Result: APPROVE
- Reason: The PR introduces a robust MMLE-EM estimator for handling missing data, with thorough testing and documentation.
- Head SHA:
2df8d63a64651c758674ddbbd7178e4bc11bcb70 - Workflow run: 29086179835
- Workflow attempt: 3
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file (6 files)"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file (6 files)"]
R1 --> V1["required checks"]
Evidence --> S2["Test: test_estimator_mmle.py"]
S2 --> I2["regression suite"]
I2 --> R2["Review risk: Test: test_estimator_mmle.py"]
R2 --> V2["targeted test run"]
What
Adds an
estimatoraxis toFitConfigwith a marginal-MLE (MMLE) path that calibrates item parameters correctly under missing data — the gap that penalized JMLE cannot fill.Why
Penalized JMLE treats every persons ability as a free parameter estimated jointly with item parameters. Under missing/sparse responses that joint problem is weakly identified and item discrimination becomes biased. MMLE marginalizes ability out over a population distribution (Gauss-Hermite quadrature); each person contributes a product over their observed items only, so missingness (MAR) is handled by construction — no imputation.
Changes
crates/mlsirm-core/src/mmle.rs— Rust MMLE-EM for unidimensional 2PL: embedded 21-node probabilists Gauss-Hermite rule (N(0,1) prior), observed-only E-step, per-item Newton M-step, EAP ability. Missing-robust by construction.config.py—FitConfig.estimatorin {jmle,mmle,em,bayes}, defaultjmle; validated.fit.py— estimator dispatch;_fit_mmleprefers the compiled Rust core and falls back to the pure-numpy reference (estimators/mmle.py).em/bayesraiseNotImplementedError(reserved milestones).tests/test_estimator_mmle.py.Verification
cargo test -p mlsirm-core— 6 passed.Scope / follow-ups
Unidimensional 2PL only (matches ULS2PLM / the 2PL measurement slice). Multidimensional-spatial and polytomous GRM MMLE, plus the
em/bayesestimators, are separate milestones. The pyo3 binding for the Rustfit_mmle_2plis wired for when the extension is rebuilt; until then the numpy reference serves the axis.🤖 Generated with Claude Code