Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

## [Unreleased]

- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, §4.3, pp. 9–10; Eq. 3–5, pp. 4–5; Table 2, p. 12; p. 16; §7.2, pp. 20–21; JSS PDF re-opened 2026-08-23T20:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar lagged covariance of §4.3 predetermined `T0VAR`. Section 4.3 treats the first time point as predetermined when no assumptions are made about the process prior to the initial time point. Free `T0VAR` `p_0` is then estimated. Equation 3 writes `η(t) = exp(A Δt) η(t0) + …`. Equation 4 writes `cov(η_t, η_{t-1}) = A_Δt cov(η_{t-1})`. Trait variance and `addedTIPREDVAR` are time-invariant between-subject and do not decay with `e^{a Δt}`. The lagged composition is `trait + e^{a Δt} p_0 + (B / a)² v`. Form the lagged free first-occasion covariance first, then include the trait, then include the TI extra variance, then add. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not this map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state (`e^{a Δt}` of that total) is not this map. Free `T0VAR` `p_0` is not this map. The later-occasion map `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` includes `Q_Δt` and is not this map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. A zero-diffusion carry with `a ≥ 0` is `e^{a Δt} p_0` and is kept. Trait-only variance does not require a stable drift. The interval must be event time and strictly positive. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. Independent `ε_t` does not enter. `MANIFESTVAR` is not that lagged observed covariance. The predetermined lagged latent covariance is not the predetermined lagged observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Meredith (1993) remains unread (Unpaywall 2026-08-23T20:20Z: `is_oa: false`; title *Measurement Invariance, Factor Analysis and Factorial Invariance*). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-23T20:20Z: `is_oa: false`; title *Randomization-Based Inference about Latent Variables from Complex Samples*).
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, §4.3, pp. 9–10; Eq. 3–5, pp. 4–5; Table 2, p. 12; p. 16; §7.2, pp. 20–21; JSS PDF re-opened 2026-08-23T20:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar later-occasion variance of §4.3 predetermined `T0VAR`. Section 4.3 treats the first time point as predetermined when no assumptions are made about the process prior to the initial time point. Free `T0VAR` `p_0` is then estimated. The process gradually transitions from the variances of the initial parameters toward those of the parameters when the model is stationary. Equation 3 writes `η(t) = exp(A Δt) η(t0) + … +` the stochastic integral. Equation 4 writes that the integral exhibits covariance `Q_Δt`. The law of total variance on the within-subject state is `e^{2 a Δt} p_0 + Q_Δt`. Trait variance and `addedTIPREDVAR` are time-invariant between-subject and do not enter that process-noise integral. The later-occasion composition is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v`. Form the evolved free first-occasion variance first, then include the trait, then include the TI extra variance, then add. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later-occasion variance uses `−q / (2 a)` in place of `p_0` and is not this map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state (`e^{2 a Δt}` of that total plus `Q_Δt`) is not this map. Free `T0VAR` `p_0` is not this map. As `Δt → ∞` with stable `a < 0` the carried `p_0` vanishes and `Q_Δt` approaches `−q / (2 a)`, so the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. The interval must be event time and strictly positive. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that later-occasion observed variance. The predetermined later-occasion latent variance is not the predetermined later-occasion observed variance. Stationary later-occasion observed variance is not that observed variance when `p_0` is free. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Meredith (1993) remains unread (Unpaywall 2026-08-23T20:20Z: `is_oa: false`; title *Measurement Invariance, Factor Analysis and Factorial Invariance*). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-23T20:20Z: `is_oa: false`; title *Randomization-Based Inference about Latent Variables from Complex Samples*).
- Branch coverage JSON now unique-folds `files[].branches` True/False counts across instantiations. Nightly totals on #49 head `1e3e2eb` reported `event_time.rs` 505/506 while every unique site had both arms taken (253 sites × 2 instantiations). Summary-only reports without branch arrays still fail closed on totals. The 100% contract is unique production arms, matching the LCOV authored-line gate. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
- `psychometric_core` maps overflowing `expm1(a Δt)` / `expm1(2 a Δt)` in `recover_discrete_constant_predictor_effect` and `recover_discrete_process_noise` through the log-space rewrite without a redundant `if !argument.is_finite()` after overflow. Local crate llvm-cov on #49 head `559e7b399473ee90ba3234677dd9ef7f05f7fd2e` was 509/510: the same LLVM `exp`/`expm1` finite-argument proof as L768/L5040. Existing rewrite (`a = 800` / `a = 400`) and overflow (`a = 1e308`) tests remain the contract. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md

Large diffs are not rendered by default.

97 changes: 97 additions & 0 deletions crates/psychometric_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,36 @@ pub enum PsychometricError {
/// as predetermined later-occasion observed variance. Stationary
/// later variance uses `−q / (2 a)`, not free `p_0`.
StationaryLaterObservedVarianceIsNotPredeterminedLaterObservedVariance,
/// Driver §4.3 predetermined lagged covariance was treated as lagged
/// stationary `T0VAR`. Free `T0VAR` is not `−q / (2 a)`.
PredeterminedLaggedLatentCovarianceIsNotStationaryLaggedCovariance,
/// Driver §4.3 predetermined lagged covariance was treated as
/// predetermined later-occasion variance. Lagged covariance omits
/// `Q_Δt` and uses `e^{a Δt} p_0`, not `e^{2 a Δt} p_0`.
PredeterminedLaggedLatentCovarianceIsNotLaterLatentVariance,
/// Driver §4.3 predetermined lagged covariance was treated as the
/// decayed total `e^{a Δt}(trait + p_0 + (B / a)² v)`. Trait
/// variance and `addedTIPREDVAR` do not decay.
PredeterminedLaggedLatentCovarianceIsNotDecayedTotal,
/// Driver §4.3 predetermined lagged covariance was treated as free
/// first-occasion `T0VAR`. `e^{a Δt} p_0` is not `p_0`.
PredeterminedLaggedLatentCovarianceIsNotInitialLatentVariance,
/// Driver §4.3 predetermined lagged covariance was treated as
/// predetermined lagged observed covariance. Equation 5 maps
/// `cov(y_t, y_{t-1}) = λ²` of that covariance plus `ψ`.
PredeterminedLaggedLatentCovarianceIsNotObservedCovariance,
/// Driver Eq. 5 measurement error was treated as predetermined
/// lagged observed covariance. Independent `ε_t` does not enter
/// `cov(y_t, y_{t-1})`.
MeasurementErrorIsNotPredeterminedLaggedObservedCovariance,
/// Driver Eq. 5 of predetermined later-occasion `T0VAR` was treated
/// as predetermined lagged observed covariance. Later variance
/// includes `Q_Δt` and `θ`.
PredeterminedLaterObservedVarianceIsNotPredeterminedLaggedObservedCovariance,
/// Driver Eq. 5 of lagged §4.3 stationary `T0VAR` was treated as
/// predetermined lagged observed covariance. Stationary lagged
/// covariance uses `−q / (2 a)`, not free `p_0`.
StationaryLaggedObservedCovarianceIsNotPredeterminedLaggedObservedCovariance,
}

impl fmt::Display for PsychometricError {
Expand Down Expand Up @@ -938,6 +968,30 @@ impl fmt::Display for PsychometricError {
Self::StationaryLaterObservedVarianceIsNotPredeterminedLaterObservedVariance => {
"stationary later-occasion observed variance is not the predetermined later-occasion observed variance"
}
Self::PredeterminedLaggedLatentCovarianceIsNotStationaryLaggedCovariance => {
"predetermined lagged latent covariance is not the stationary lagged latent covariance"
}
Self::PredeterminedLaggedLatentCovarianceIsNotLaterLatentVariance => {
"predetermined lagged latent covariance is not the predetermined later-occasion latent variance"
}
Self::PredeterminedLaggedLatentCovarianceIsNotDecayedTotal => {
"predetermined lagged latent covariance is not the decayed predetermined total"
}
Self::PredeterminedLaggedLatentCovarianceIsNotInitialLatentVariance => {
"predetermined lagged latent covariance is not the free first-occasion latent variance"
}
Self::PredeterminedLaggedLatentCovarianceIsNotObservedCovariance => {
"predetermined lagged latent covariance is not the predetermined lagged observed covariance"
}
Self::MeasurementErrorIsNotPredeterminedLaggedObservedCovariance => {
"measurement-error variance is not the predetermined lagged observed covariance"
}
Self::PredeterminedLaterObservedVarianceIsNotPredeterminedLaggedObservedCovariance => {
"predetermined later-occasion observed variance is not the predetermined lagged observed covariance"
}
Self::StationaryLaggedObservedCovarianceIsNotPredeterminedLaggedObservedCovariance => {
"stationary lagged observed covariance is not the predetermined lagged observed covariance"
}
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -1585,4 +1639,47 @@ mod tests {
"stationary later-occasion observed variance is not the predetermined later-occasion observed variance"
);
}

#[test]
fn predetermined_lagged_covariance_boundary_messages_are_stable() {
assert_eq!(
PsychometricError::PredeterminedLaggedLatentCovarianceIsNotStationaryLaggedCovariance
.to_string(),
"predetermined lagged latent covariance is not the stationary lagged latent covariance"
);
assert_eq!(
PsychometricError::PredeterminedLaggedLatentCovarianceIsNotLaterLatentVariance
.to_string(),
"predetermined lagged latent covariance is not the predetermined later-occasion latent variance"
);
assert_eq!(
PsychometricError::PredeterminedLaggedLatentCovarianceIsNotDecayedTotal.to_string(),
"predetermined lagged latent covariance is not the decayed predetermined total"
);
assert_eq!(
PsychometricError::PredeterminedLaggedLatentCovarianceIsNotInitialLatentVariance
.to_string(),
"predetermined lagged latent covariance is not the free first-occasion latent variance"
);
assert_eq!(
PsychometricError::PredeterminedLaggedLatentCovarianceIsNotObservedCovariance
.to_string(),
"predetermined lagged latent covariance is not the predetermined lagged observed covariance"
);
assert_eq!(
PsychometricError::MeasurementErrorIsNotPredeterminedLaggedObservedCovariance
.to_string(),
"measurement-error variance is not the predetermined lagged observed covariance"
);
assert_eq!(
PsychometricError::PredeterminedLaterObservedVarianceIsNotPredeterminedLaggedObservedCovariance
.to_string(),
"predetermined later-occasion observed variance is not the predetermined lagged observed covariance"
);
assert_eq!(
PsychometricError::StationaryLaggedObservedCovarianceIsNotPredeterminedLaggedObservedCovariance
.to_string(),
"stationary lagged observed covariance is not the predetermined lagged observed covariance"
);
}
}
Loading