Skip to content

feat(psychometric): recover 2017-era commented discreteTRAITVAR on main - #343

Draft
seonghobae wants to merge 2 commits into
mainfrom
agent/psychometric-discrete-trait-var
Draft

feat(psychometric): recover 2017-era commented discreteTRAITVAR on main#343
seonghobae wants to merge 2 commits into
mainfrom
agent/psychometric-discrete-trait-var

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Summary

Independent-on-main scalar recovery of the 2017-era commented discreteTRAITVAR map from cran/ctsem 2.5.0 summary.ctsemFit.R.

Driver, Oud, and Voelkle (2017, Eq. 1, p. 4; Table 2, p. 12 TRAITVAR φ_ξ; §4.3, p. 9; JSS PDF re-opened 2026-08-31T05:15Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) write dη = (Aη + ξ + Bz + Mx) dt + G dW. At a stable equilibrium η_∞ = −A^{-1} ξ, so process-mean variance from that random intercept is trait / a². The 2017-era source comments:

asymTRAITVAR <- solve(DRIFT) %*% TRAITVAR %*% t(solve(DRIFT))
discreteTRAITVAR <- (I − OpenMx::expm(DRIFT * timeInterval)) %*% asymTRAITVAR %*% t(...)

(not in the active outlist). The scalar map is (1 − e^{a Δt})² · (trait / a²).

Form 1 / a first, then square, then multiply by trait (do not import unpublished #330 recover_asymptotic_trait_variance). Then form expm1(a Δt) and square, then multiply. Do not call recover_discrete_lag_from_log_rate: binary64 underflow of e^{a Δt} to +0 is not a discrete lag, but the Δt → ∞ limit of this map is trait / a² and is kept.

Contract

  • Event clock only (EventTimeRequired otherwise).
  • Strictly positive event interval (NonPositiveInterval otherwise).
  • Zero trait is exactly 0 even if a ≥ 0.
  • a ≥ 0 with a nonzero trait fails closed (DiscreteTraitVarianceRequiresStableDrift).
  • Negative / non-finite trait fails closed.
  • Overflow of 1 / a fails closed.
  • Numeric: a = −0.5, trait = 1, Δt = 1 recovers (1 − e^{−0.5})² · 4.
  • Underflow: a = −800, Δt = 1, trait = 1 keeps 1 / 640000; recover_discrete_lag_from_log_rate fails on the same inputs.

Refusals

  • Table 2 TRAITVAR φ_ξ is not discreteTRAITVAR.
  • Unpublished 2017-era asymTRAITVAR trait / a² is the Δt → ∞ limit and remains a distinct named quantity even when numbers coincide after underflow.
  • Driver Eq. 3 Q_Δt is not discreteTRAITVAR.

Do not duplicate queued #330 (trait / a²), #340 (two-term p + trait / a²), #341 (three-term total), or #342 (asymTOTALVARstd).

Meredith (1993) remains unread (Springer/Cambridge paywalled; Unpaywall historically is_oa: false). Mislevy (1991, Psychometrika, 56, 177–196, DOI 10.1007/bf02294457) remains unread on the same terms.

This is not a Kalman filter, not a matrix expm, not ESEM estimation, not DSEM, and not ctsem estimation.

Test plan

  • cargo fmt -p psychometric_core
  • cargo clippy -p psychometric_core --all-targets -- -D warnings
  • cargo test -p psychometric_core --all-targets (lib 189, crate_contract 2, multilevel 108, scientific_claim_boundary 53, plus focused discrete_trait_variance* green)
  • RUSTDOCFLAGS='-D warnings' cargo doc -p psychometric_core --no-deps

Do not self-approve. Do not convert #310. Do not merge without independent non-author APPROVE and exact-head required-check success.

Scalar map of the commented ctsem 2.5.0 summary.ctsemFit.R transform
(I − expm(DRIFT Δt)) %*% asymTRAITVAR %*% t(...). Form trait/a² inline
then (1 − e^{a Δt})². Zero trait is exactly zero. a ≥ 0 with a nonzero
trait fails closed. Underflow of e^{a Δt} to +0 keeps trait/a².
Distinct from TRAITVAR φ_ξ, unpublished asymTRAITVAR, and Q_Δt.
Independent of queued #330/#340/#341/#342.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Resolve the CHANGELOG.md append conflict by keeping both entries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@seonghobae

Copy link
Copy Markdown
Contributor Author

Restack on protected main (a243f18)

Non-force merge of origin/main (merge commit 72f57211); the only conflict was the CHANGELOG.md append, both entries kept.

Local evidence on the pushed head (toolchain 1.98.0): cargo test -p psychometric_core 367 passed / 0 failed, clippy -D warnings clean, cargo fmt --all --check clean, documentation/workspace/docstring contracts PASS, git diff --check clean.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant